2020-04-27 11:06:00 -07:00
|
|
|
use crate::crds_value::MAX_WALLCLOCK;
|
2018-11-15 13:23:26 -08:00
|
|
|
use solana_sdk::pubkey::Pubkey;
|
2019-04-12 18:17:34 -07:00
|
|
|
#[cfg(test)]
|
2019-03-16 17:43:39 -07:00
|
|
|
use solana_sdk::rpc_port;
|
2020-04-27 11:06:00 -07:00
|
|
|
use solana_sdk::sanitize::{Sanitize, SanitizeError};
|
2019-04-12 18:17:34 -07:00
|
|
|
#[cfg(test)]
|
2020-02-20 13:28:55 -08:00
|
|
|
use solana_sdk::signature::{Keypair, Signer};
|
2018-11-16 08:45:59 -08:00
|
|
|
use solana_sdk::timing::timestamp;
|
2019-01-02 00:46:15 -08:00
|
|
|
use std::cmp::{Ord, Ordering, PartialEq, PartialOrd};
|
2019-05-03 11:01:35 -07:00
|
|
|
use std::net::{IpAddr, SocketAddr};
|
2018-11-15 13:23:26 -08:00
|
|
|
|
|
|
|
/// Structure representing a node on the network
|
2020-07-06 04:22:23 -07:00
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug, AbiExample)]
|
2018-11-15 13:23:26 -08:00
|
|
|
pub struct ContactInfo {
|
|
|
|
pub id: Pubkey,
|
|
|
|
/// gossip address
|
2018-12-06 12:52:47 -08:00
|
|
|
pub gossip: SocketAddr,
|
2018-11-15 13:23:26 -08:00
|
|
|
/// address to connect to for replication
|
|
|
|
pub tvu: SocketAddr,
|
2019-10-31 18:23:50 -07:00
|
|
|
/// address to forward shreds to
|
2019-08-20 17:16:06 -07:00
|
|
|
pub tvu_forwards: SocketAddr,
|
2020-01-31 14:23:51 -08:00
|
|
|
/// address to send repair responses to
|
2019-10-31 18:23:50 -07:00
|
|
|
pub repair: SocketAddr,
|
2018-11-15 13:23:26 -08:00
|
|
|
/// transactions address
|
|
|
|
pub tpu: SocketAddr,
|
2019-03-11 12:46:30 -07:00
|
|
|
/// address to forward unprocessed transactions to
|
2019-07-30 14:50:02 -07:00
|
|
|
pub tpu_forwards: SocketAddr,
|
2020-08-07 07:45:17 -07:00
|
|
|
/// address to which to send bank state requests
|
2020-12-01 19:25:09 -08:00
|
|
|
pub unused: SocketAddr,
|
2018-11-15 13:23:26 -08:00
|
|
|
/// address to which to send JSON-RPC requests
|
|
|
|
pub rpc: SocketAddr,
|
|
|
|
/// websocket for JSON-RPC push notifications
|
|
|
|
pub rpc_pubsub: SocketAddr,
|
2020-01-31 14:23:51 -08:00
|
|
|
/// address to send repair requests to
|
|
|
|
pub serve_repair: SocketAddr,
|
2018-11-15 13:23:26 -08:00
|
|
|
/// latest wallclock picked
|
|
|
|
pub wallclock: u64,
|
2020-01-13 14:59:31 -08:00
|
|
|
/// node shred version
|
|
|
|
pub shred_version: u16,
|
2018-11-15 13:23:26 -08:00
|
|
|
}
|
|
|
|
|
2020-04-27 11:06:00 -07:00
|
|
|
impl Sanitize for ContactInfo {
|
|
|
|
fn sanitize(&self) -> std::result::Result<(), SanitizeError> {
|
|
|
|
if self.wallclock >= MAX_WALLCLOCK {
|
2020-04-29 18:12:51 -07:00
|
|
|
return Err(SanitizeError::ValueOutOfBounds);
|
2020-04-27 11:06:00 -07:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-02 00:46:15 -08:00
|
|
|
impl Ord for ContactInfo {
|
|
|
|
fn cmp(&self, other: &Self) -> Ordering {
|
|
|
|
self.id.cmp(&other.id)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl PartialOrd for ContactInfo {
|
|
|
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
|
|
Some(self.cmp(other))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl PartialEq for ContactInfo {
|
|
|
|
fn eq(&self, other: &Self) -> bool {
|
|
|
|
self.id == other.id
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Eq for ContactInfo {}
|
|
|
|
|
2018-11-15 13:23:26 -08:00
|
|
|
#[macro_export]
|
|
|
|
macro_rules! socketaddr {
|
|
|
|
($ip:expr, $port:expr) => {
|
2019-05-03 11:01:35 -07:00
|
|
|
std::net::SocketAddr::from((std::net::Ipv4Addr::from($ip), $port))
|
2018-11-15 13:23:26 -08:00
|
|
|
};
|
|
|
|
($str:expr) => {{
|
2019-03-08 19:28:19 -08:00
|
|
|
let a: std::net::SocketAddr = $str.parse().unwrap();
|
2018-11-15 13:23:26 -08:00
|
|
|
a
|
|
|
|
}};
|
|
|
|
}
|
|
|
|
#[macro_export]
|
|
|
|
macro_rules! socketaddr_any {
|
|
|
|
() => {
|
|
|
|
socketaddr!(0, 0)
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for ContactInfo {
|
|
|
|
fn default() -> Self {
|
|
|
|
ContactInfo {
|
|
|
|
id: Pubkey::default(),
|
2018-12-06 12:52:47 -08:00
|
|
|
gossip: socketaddr_any!(),
|
2018-11-15 13:23:26 -08:00
|
|
|
tvu: socketaddr_any!(),
|
2019-08-20 17:16:06 -07:00
|
|
|
tvu_forwards: socketaddr_any!(),
|
2019-10-31 18:23:50 -07:00
|
|
|
repair: socketaddr_any!(),
|
2018-11-15 13:23:26 -08:00
|
|
|
tpu: socketaddr_any!(),
|
2019-07-30 14:50:02 -07:00
|
|
|
tpu_forwards: socketaddr_any!(),
|
2020-12-01 19:25:09 -08:00
|
|
|
unused: socketaddr_any!(),
|
2018-11-15 13:23:26 -08:00
|
|
|
rpc: socketaddr_any!(),
|
|
|
|
rpc_pubsub: socketaddr_any!(),
|
2020-01-31 14:23:51 -08:00
|
|
|
serve_repair: socketaddr_any!(),
|
2018-11-15 13:23:26 -08:00
|
|
|
wallclock: 0,
|
2020-01-13 14:59:31 -08:00
|
|
|
shred_version: 0,
|
2018-11-15 13:23:26 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ContactInfo {
|
2020-02-12 12:58:51 -08:00
|
|
|
pub fn new_localhost(id: &Pubkey, now: u64) -> Self {
|
2019-03-08 17:15:08 -08:00
|
|
|
Self {
|
2019-03-09 19:28:43 -08:00
|
|
|
id: *id,
|
2020-02-12 12:58:51 -08:00
|
|
|
gossip: socketaddr!("127.0.0.1:1234"),
|
|
|
|
tvu: socketaddr!("127.0.0.1:1235"),
|
|
|
|
tvu_forwards: socketaddr!("127.0.0.1:1236"),
|
|
|
|
repair: socketaddr!("127.0.0.1:1237"),
|
|
|
|
tpu: socketaddr!("127.0.0.1:1238"),
|
|
|
|
tpu_forwards: socketaddr!("127.0.0.1:1239"),
|
2020-12-01 19:25:09 -08:00
|
|
|
unused: socketaddr!("127.0.0.1:1240"),
|
2020-02-12 12:58:51 -08:00
|
|
|
rpc: socketaddr!("127.0.0.1:1241"),
|
|
|
|
rpc_pubsub: socketaddr!("127.0.0.1:1242"),
|
|
|
|
serve_repair: socketaddr!("127.0.0.1:1243"),
|
2018-11-15 13:23:26 -08:00
|
|
|
wallclock: now,
|
2020-01-13 14:59:31 -08:00
|
|
|
shred_version: 0,
|
2018-11-15 13:23:26 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-15 08:38:04 -08:00
|
|
|
/// New random ContactInfo for tests and simulations.
|
|
|
|
pub(crate) fn new_rand<R: rand::Rng>(rng: &mut R, pubkey: Option<Pubkey>) -> Self {
|
|
|
|
let delay = 10 * 60 * 1000; // 10 minutes
|
|
|
|
let now = timestamp() - delay + rng.gen_range(0, 2 * delay);
|
|
|
|
let pubkey = pubkey.unwrap_or_else(solana_sdk::pubkey::new_rand);
|
|
|
|
ContactInfo::new_localhost(&pubkey, now)
|
|
|
|
}
|
|
|
|
|
2018-11-15 13:23:26 -08:00
|
|
|
#[cfg(test)]
|
|
|
|
/// ContactInfo with multicast addresses for adversarial testing.
|
|
|
|
pub fn new_multicast() -> Self {
|
|
|
|
let addr = socketaddr!("224.0.1.255:1000");
|
|
|
|
assert!(addr.ip().is_multicast());
|
2020-02-12 12:58:51 -08:00
|
|
|
Self {
|
2020-10-19 12:12:08 -07:00
|
|
|
id: solana_sdk::pubkey::new_rand(),
|
2020-02-12 12:58:51 -08:00
|
|
|
gossip: addr,
|
|
|
|
tvu: addr,
|
|
|
|
tvu_forwards: addr,
|
|
|
|
repair: addr,
|
|
|
|
tpu: addr,
|
|
|
|
tpu_forwards: addr,
|
2020-12-01 19:25:09 -08:00
|
|
|
unused: addr,
|
2020-02-12 12:58:51 -08:00
|
|
|
rpc: addr,
|
|
|
|
rpc_pubsub: addr,
|
|
|
|
serve_repair: addr,
|
|
|
|
wallclock: 0,
|
|
|
|
shred_version: 0,
|
|
|
|
}
|
2018-11-15 13:23:26 -08:00
|
|
|
}
|
2019-04-12 18:17:34 -07:00
|
|
|
|
|
|
|
#[cfg(test)]
|
2019-07-09 22:06:47 -07:00
|
|
|
pub(crate) fn new_with_pubkey_socketaddr(pubkey: &Pubkey, bind_addr: &SocketAddr) -> Self {
|
2019-04-12 18:17:34 -07:00
|
|
|
fn next_port(addr: &SocketAddr, nxt: u16) -> SocketAddr {
|
|
|
|
let mut nxt_addr = *addr;
|
|
|
|
nxt_addr.set_port(addr.port() + nxt);
|
|
|
|
nxt_addr
|
|
|
|
}
|
|
|
|
|
2020-02-12 12:58:51 -08:00
|
|
|
let tpu = *bind_addr;
|
|
|
|
let gossip = next_port(&bind_addr, 1);
|
|
|
|
let tvu = next_port(&bind_addr, 2);
|
|
|
|
let tpu_forwards = next_port(&bind_addr, 3);
|
|
|
|
let tvu_forwards = next_port(&bind_addr, 4);
|
2019-10-31 18:23:50 -07:00
|
|
|
let repair = next_port(&bind_addr, 5);
|
2020-02-12 12:58:51 -08:00
|
|
|
let rpc = SocketAddr::new(bind_addr.ip(), rpc_port::DEFAULT_RPC_PORT);
|
|
|
|
let rpc_pubsub = SocketAddr::new(bind_addr.ip(), rpc_port::DEFAULT_RPC_PUBSUB_PORT);
|
2020-01-31 14:23:51 -08:00
|
|
|
let serve_repair = next_port(&bind_addr, 6);
|
2020-02-12 12:58:51 -08:00
|
|
|
Self {
|
|
|
|
id: *pubkey,
|
|
|
|
gossip,
|
|
|
|
tvu,
|
|
|
|
tvu_forwards,
|
2019-10-31 18:23:50 -07:00
|
|
|
repair,
|
2020-02-12 12:58:51 -08:00
|
|
|
tpu,
|
|
|
|
tpu_forwards,
|
2020-12-01 19:25:09 -08:00
|
|
|
unused: "0.0.0.0:0".parse().unwrap(),
|
2020-02-12 12:58:51 -08:00
|
|
|
rpc,
|
|
|
|
rpc_pubsub,
|
2020-01-31 14:23:51 -08:00
|
|
|
serve_repair,
|
2020-02-12 12:58:51 -08:00
|
|
|
wallclock: timestamp(),
|
|
|
|
shred_version: 0,
|
|
|
|
}
|
2018-11-15 13:23:26 -08:00
|
|
|
}
|
2019-04-12 18:17:34 -07:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
pub(crate) fn new_with_socketaddr(bind_addr: &SocketAddr) -> Self {
|
2018-11-15 13:23:26 -08:00
|
|
|
let keypair = Keypair::new();
|
2019-03-09 19:28:43 -08:00
|
|
|
Self::new_with_pubkey_socketaddr(&keypair.pubkey(), bind_addr)
|
2018-11-15 13:23:26 -08:00
|
|
|
}
|
2019-03-08 17:15:08 -08:00
|
|
|
|
|
|
|
// Construct a ContactInfo that's only usable for gossip
|
|
|
|
pub fn new_gossip_entry_point(gossip_addr: &SocketAddr) -> Self {
|
2020-02-12 12:58:51 -08:00
|
|
|
Self {
|
|
|
|
id: Pubkey::default(),
|
|
|
|
gossip: *gossip_addr,
|
|
|
|
wallclock: timestamp(),
|
|
|
|
..ContactInfo::default()
|
|
|
|
}
|
2018-11-15 13:23:26 -08:00
|
|
|
}
|
2019-04-12 18:17:34 -07:00
|
|
|
|
2018-11-15 13:23:26 -08:00
|
|
|
fn is_valid_ip(addr: IpAddr) -> bool {
|
|
|
|
!(addr.is_unspecified() || addr.is_multicast())
|
|
|
|
// || (addr.is_loopback() && !cfg_test))
|
|
|
|
// TODO: boot loopback in production networks
|
|
|
|
}
|
2019-04-12 18:17:34 -07:00
|
|
|
|
2018-11-15 13:23:26 -08:00
|
|
|
/// port must not be 0
|
2020-06-17 20:54:52 -07:00
|
|
|
/// ip must be specified and not multicast
|
2018-11-15 13:23:26 -08:00
|
|
|
/// loopback ip is only allowed in tests
|
|
|
|
pub fn is_valid_address(addr: &SocketAddr) -> bool {
|
|
|
|
(addr.port() != 0) && Self::is_valid_ip(addr.ip())
|
|
|
|
}
|
2019-03-12 19:25:52 -07:00
|
|
|
|
|
|
|
pub fn client_facing_addr(&self) -> (SocketAddr, SocketAddr) {
|
|
|
|
(self.rpc, self.tpu)
|
|
|
|
}
|
2019-05-01 17:14:01 -07:00
|
|
|
|
|
|
|
pub fn valid_client_facing_addr(&self) -> Option<(SocketAddr, SocketAddr)> {
|
|
|
|
if ContactInfo::is_valid_address(&self.rpc) && ContactInfo::is_valid_address(&self.tpu) {
|
|
|
|
Some((self.rpc, self.tpu))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
2018-11-15 13:23:26 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_is_valid_address() {
|
|
|
|
let bad_address_port = socketaddr!("127.0.0.1:0");
|
|
|
|
assert!(!ContactInfo::is_valid_address(&bad_address_port));
|
|
|
|
let bad_address_unspecified = socketaddr!(0, 1234);
|
|
|
|
assert!(!ContactInfo::is_valid_address(&bad_address_unspecified));
|
|
|
|
let bad_address_multicast = socketaddr!([224, 254, 0, 0], 1234);
|
|
|
|
assert!(!ContactInfo::is_valid_address(&bad_address_multicast));
|
|
|
|
let loopback = socketaddr!("127.0.0.1:1234");
|
|
|
|
assert!(ContactInfo::is_valid_address(&loopback));
|
|
|
|
// assert!(!ContactInfo::is_valid_ip_internal(loopback.ip(), false));
|
|
|
|
}
|
2019-05-01 17:14:01 -07:00
|
|
|
|
2018-11-15 13:23:26 -08:00
|
|
|
#[test]
|
|
|
|
fn test_default() {
|
|
|
|
let ci = ContactInfo::default();
|
2018-12-06 12:52:47 -08:00
|
|
|
assert!(ci.gossip.ip().is_unspecified());
|
2018-11-15 13:23:26 -08:00
|
|
|
assert!(ci.tvu.ip().is_unspecified());
|
2019-07-30 14:50:02 -07:00
|
|
|
assert!(ci.tpu_forwards.ip().is_unspecified());
|
2018-11-15 13:23:26 -08:00
|
|
|
assert!(ci.rpc.ip().is_unspecified());
|
|
|
|
assert!(ci.rpc_pubsub.ip().is_unspecified());
|
|
|
|
assert!(ci.tpu.ip().is_unspecified());
|
2020-12-01 19:25:09 -08:00
|
|
|
assert!(ci.unused.ip().is_unspecified());
|
2020-01-31 14:23:51 -08:00
|
|
|
assert!(ci.serve_repair.ip().is_unspecified());
|
2018-11-15 13:23:26 -08:00
|
|
|
}
|
|
|
|
#[test]
|
|
|
|
fn test_multicast() {
|
|
|
|
let ci = ContactInfo::new_multicast();
|
2018-12-06 12:52:47 -08:00
|
|
|
assert!(ci.gossip.ip().is_multicast());
|
2018-11-15 13:23:26 -08:00
|
|
|
assert!(ci.tvu.ip().is_multicast());
|
2019-07-30 14:50:02 -07:00
|
|
|
assert!(ci.tpu_forwards.ip().is_multicast());
|
2018-11-15 13:23:26 -08:00
|
|
|
assert!(ci.rpc.ip().is_multicast());
|
|
|
|
assert!(ci.rpc_pubsub.ip().is_multicast());
|
|
|
|
assert!(ci.tpu.ip().is_multicast());
|
2020-12-01 19:25:09 -08:00
|
|
|
assert!(ci.unused.ip().is_multicast());
|
2020-01-31 14:23:51 -08:00
|
|
|
assert!(ci.serve_repair.ip().is_multicast());
|
2018-11-15 13:23:26 -08:00
|
|
|
}
|
|
|
|
#[test]
|
|
|
|
fn test_entry_point() {
|
|
|
|
let addr = socketaddr!("127.0.0.1:10");
|
2019-03-08 17:15:08 -08:00
|
|
|
let ci = ContactInfo::new_gossip_entry_point(&addr);
|
2018-12-06 12:52:47 -08:00
|
|
|
assert_eq!(ci.gossip, addr);
|
2018-11-15 13:23:26 -08:00
|
|
|
assert!(ci.tvu.ip().is_unspecified());
|
2019-07-30 14:50:02 -07:00
|
|
|
assert!(ci.tpu_forwards.ip().is_unspecified());
|
2018-11-15 13:23:26 -08:00
|
|
|
assert!(ci.rpc.ip().is_unspecified());
|
|
|
|
assert!(ci.rpc_pubsub.ip().is_unspecified());
|
|
|
|
assert!(ci.tpu.ip().is_unspecified());
|
2020-12-01 19:25:09 -08:00
|
|
|
assert!(ci.unused.ip().is_unspecified());
|
2020-01-31 14:23:51 -08:00
|
|
|
assert!(ci.serve_repair.ip().is_unspecified());
|
2018-11-15 13:23:26 -08:00
|
|
|
}
|
|
|
|
#[test]
|
|
|
|
fn test_socketaddr() {
|
|
|
|
let addr = socketaddr!("127.0.0.1:10");
|
|
|
|
let ci = ContactInfo::new_with_socketaddr(&addr);
|
|
|
|
assert_eq!(ci.tpu, addr);
|
2018-12-06 12:52:47 -08:00
|
|
|
assert_eq!(ci.gossip.port(), 11);
|
2018-11-15 13:23:26 -08:00
|
|
|
assert_eq!(ci.tvu.port(), 12);
|
2019-07-30 14:50:02 -07:00
|
|
|
assert_eq!(ci.tpu_forwards.port(), 13);
|
2020-01-30 10:13:29 -08:00
|
|
|
assert_eq!(ci.rpc.port(), rpc_port::DEFAULT_RPC_PORT);
|
|
|
|
assert_eq!(ci.rpc_pubsub.port(), rpc_port::DEFAULT_RPC_PUBSUB_PORT);
|
2020-12-01 19:25:09 -08:00
|
|
|
assert!(ci.unused.ip().is_unspecified());
|
2020-01-31 14:23:51 -08:00
|
|
|
assert_eq!(ci.serve_repair.port(), 16);
|
2018-11-15 13:23:26 -08:00
|
|
|
}
|
2020-01-31 14:23:51 -08:00
|
|
|
|
2018-11-15 13:23:26 -08:00
|
|
|
#[test]
|
2018-12-07 14:09:29 -08:00
|
|
|
fn replayed_data_new_with_socketaddr_with_pubkey() {
|
2018-11-15 13:23:26 -08:00
|
|
|
let keypair = Keypair::new();
|
|
|
|
let d1 = ContactInfo::new_with_pubkey_socketaddr(
|
2019-03-09 19:28:43 -08:00
|
|
|
&keypair.pubkey(),
|
2018-11-15 13:23:26 -08:00
|
|
|
&socketaddr!("127.0.0.1:1234"),
|
|
|
|
);
|
|
|
|
assert_eq!(d1.id, keypair.pubkey());
|
2018-12-06 12:52:47 -08:00
|
|
|
assert_eq!(d1.gossip, socketaddr!("127.0.0.1:1235"));
|
2018-11-15 13:23:26 -08:00
|
|
|
assert_eq!(d1.tvu, socketaddr!("127.0.0.1:1236"));
|
2019-07-30 14:50:02 -07:00
|
|
|
assert_eq!(d1.tpu_forwards, socketaddr!("127.0.0.1:1237"));
|
2018-11-15 13:23:26 -08:00
|
|
|
assert_eq!(d1.tpu, socketaddr!("127.0.0.1:1234"));
|
2020-01-30 10:13:29 -08:00
|
|
|
assert_eq!(
|
|
|
|
d1.rpc,
|
|
|
|
socketaddr!(format!("127.0.0.1:{}", rpc_port::DEFAULT_RPC_PORT))
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
d1.rpc_pubsub,
|
|
|
|
socketaddr!(format!("127.0.0.1:{}", rpc_port::DEFAULT_RPC_PUBSUB_PORT))
|
|
|
|
);
|
2020-01-31 14:23:51 -08:00
|
|
|
assert_eq!(d1.tvu_forwards, socketaddr!("127.0.0.1:1238"));
|
|
|
|
assert_eq!(d1.repair, socketaddr!("127.0.0.1:1239"));
|
|
|
|
assert_eq!(d1.serve_repair, socketaddr!("127.0.0.1:1240"));
|
2018-11-15 13:23:26 -08:00
|
|
|
}
|
2019-05-01 17:14:01 -07:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_valid_client_facing() {
|
|
|
|
let mut ci = ContactInfo::default();
|
|
|
|
assert_eq!(ci.valid_client_facing_addr(), None);
|
|
|
|
ci.tpu = socketaddr!("127.0.0.1:123");
|
|
|
|
assert_eq!(ci.valid_client_facing_addr(), None);
|
|
|
|
ci.rpc = socketaddr!("127.0.0.1:234");
|
|
|
|
assert!(ci.valid_client_facing_addr().is_some());
|
|
|
|
}
|
2020-04-29 18:12:51 -07:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_sanitize() {
|
|
|
|
let mut ci = ContactInfo::default();
|
|
|
|
assert_eq!(ci.sanitize(), Ok(()));
|
|
|
|
ci.wallclock = MAX_WALLCLOCK;
|
|
|
|
assert_eq!(ci.sanitize(), Err(SanitizeError::ValueOutOfBounds));
|
|
|
|
}
|
2018-11-15 13:23:26 -08:00
|
|
|
}
|