diff --git a/radicle-cli/examples/rad-config.md b/radicle-cli/examples/rad-config.md index 29ae359b..1697d22e 100644 --- a/radicle-cli/examples/rad-config.md +++ b/radicle-cli/examples/rad-config.md @@ -29,7 +29,7 @@ $ rad config "tor": null, "network": "main", "log": "INFO", - "relay": true, + "relay": "auto", "limits": { "routingMaxSize": 1000, "routingMaxAge": 604800, diff --git a/radicle-cli/examples/rad-sync.md b/radicle-cli/examples/rad-sync.md index e2444639..02a237df 100644 --- a/radicle-cli/examples/rad-sync.md +++ b/radicle-cli/examples/rad-sync.md @@ -11,13 +11,13 @@ If we check the sync status, we see that our peers are out of sync. ``` $ rad sync status --sort-by alias -╭──────────────────────────────────────────────────────────────────────────────────────────╮ -│ ● Node Address Status Tip Timestamp │ -├──────────────────────────────────────────────────────────────────────────────────────────┤ -│ ● alice (you) alice.radicle.xyz:8776 f209c9f [ ... ] │ -│ ● bob z6Mkt67…v4N1tRk bob.radicle.xyz:8776 out-of-sync f209c9f [ ... ] │ -│ ● eve z6Mkux1…nVhib7Z eve.radicle.xyz:8776 out-of-sync f209c9f [ ... ] │ -╰──────────────────────────────────────────────────────────────────────────────────────────╯ +╭──────────────────────────────────────────────────────────────────────────────────────────────╮ +│ ● Node Address Status Tip Timestamp │ +├──────────────────────────────────────────────────────────────────────────────────────────────┤ +│ ● alice (you) alice.radicle.example:8776 f209c9f [ ... ] │ +│ ● bob z6Mkt67…v4N1tRk bob.radicle.example:8776 out-of-sync f209c9f [ ... ] │ +│ ● eve z6Mkux1…nVhib7Z eve.radicle.example:8776 out-of-sync f209c9f [ ... ] │ +╰──────────────────────────────────────────────────────────────────────────────────────────────╯ ``` Now let's run `rad sync`. This will announce the issue refs to the network and @@ -33,13 +33,13 @@ Now, when we run `rad sync status` again, we can see that `bob` and ``` $ rad sync status --sort-by alias -╭─────────────────────────────────────────────────────────────────────────────────────╮ -│ ● Node Address Status Tip Timestamp │ -├─────────────────────────────────────────────────────────────────────────────────────┤ -│ ● alice (you) alice.radicle.xyz:8776 a9ce0d1 [ ... ] │ -│ ● bob z6Mkt67…v4N1tRk bob.radicle.xyz:8776 synced a9ce0d1 [ ... ] │ -│ ● eve z6Mkux1…nVhib7Z eve.radicle.xyz:8776 synced a9ce0d1 [ ... ] │ -╰─────────────────────────────────────────────────────────────────────────────────────╯ +╭─────────────────────────────────────────────────────────────────────────────────────────╮ +│ ● Node Address Status Tip Timestamp │ +├─────────────────────────────────────────────────────────────────────────────────────────┤ +│ ● alice (you) alice.radicle.example:8776 a9ce0d1 [ ... ] │ +│ ● bob z6Mkt67…v4N1tRk bob.radicle.example:8776 synced a9ce0d1 [ ... ] │ +│ ● eve z6Mkux1…nVhib7Z eve.radicle.example:8776 synced a9ce0d1 [ ... ] │ +╰─────────────────────────────────────────────────────────────────────────────────────────╯ ``` If we try to sync again after the nodes have synced, we will already diff --git a/radicle-cli/tests/commands.rs b/radicle-cli/tests/commands.rs index 0c3a050d..2121af6c 100644 --- a/radicle-cli/tests/commands.rs +++ b/radicle-cli/tests/commands.rs @@ -39,6 +39,7 @@ mod config { pub fn seed(alias: &'static str) -> Config { Config { network: Network::Test, + relay: node::config::Relay::Always, limits: Limits { rate: RateLimits { inbound: RateLimit { @@ -52,18 +53,25 @@ mod config { }, ..Limits::default() }, + external_addresses: vec![node::Address::from_str(&format!( + "{alias}.radicle.example:8776" + )) + .unwrap()], + ..node(alias) + } + } + + /// Relay node config. + pub fn relay(alias: &'static str) -> Config { + Config { + relay: node::config::Relay::Always, ..node(alias) } } /// Test node config. pub fn node(alias: &'static str) -> Config { - Config { - external_addresses: vec![ - node::Address::from_str(&format!("{alias}.radicle.xyz:8776")).unwrap(), - ], - ..Config::test(Alias::new(alias)) - } + Config::test(Alias::new(alias)) } /// Test profile config. @@ -965,9 +973,9 @@ fn rad_review_by_hunk() { #[test] fn rad_patch_delete() { let mut environment = Environment::new(); - let alice = environment.node(Config::test(Alias::new("alice"))); - let bob = environment.node(Config::test(Alias::new("bob"))); - let seed = environment.node(Config::test(Alias::new("seed"))); + let alice = environment.node(config::relay("alice")); + let bob = environment.node(config::relay("bob")); + let seed = environment.node(config::relay("seed")); let working = environment.tmp().join("working"); let acme = RepoId::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap(); @@ -1332,9 +1340,9 @@ fn rad_clone_connect() { #[test] fn rad_sync_without_node() { let mut environment = Environment::new(); - let alice = environment.node(Config::test(Alias::new("alice"))); - let bob = environment.node(Config::test(Alias::new("bob"))); - let mut eve = environment.node(Config::test(Alias::new("eve"))); + let alice = environment.node(config::seed("alice")); + let bob = environment.node(config::seed("bob")); + let mut eve = environment.node(config::seed("eve")); let rid = RepoId::from_urn("rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5").unwrap(); eve.policies.seed(&rid, Scope::All).unwrap(); @@ -1738,9 +1746,9 @@ fn test_cob_deletion() { fn rad_sync() { let mut environment = Environment::new(); let working = environment.tmp().join("working"); - let alice = environment.node(config::node("alice")); - let bob = environment.node(config::node("bob")); - let eve = environment.node(config::node("eve")); + let alice = environment.node(config::seed("alice")); + let bob = environment.node(config::seed("bob")); + let eve = environment.node(config::seed("eve")); let acme = RepoId::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap(); fixtures::repository(working.join("acme")); @@ -1784,12 +1792,12 @@ fn rad_sync() { // fn test_replication_via_seed() { let mut environment = Environment::new(); - let alice = environment.node(Config::test(Alias::new("alice"))); - let bob = environment.node(Config::test(Alias::new("bob"))); + let alice = environment.node(config::relay("alice")); + let bob = environment.node(config::relay("bob")); let seed = environment.node(Config { policy: Policy::Allow, scope: Scope::All, - ..Config::test(Alias::new("seed")) + ..config::relay("seed") }); let working = environment.tmp().join("working"); let rid = RepoId::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap(); @@ -1874,9 +1882,9 @@ fn test_replication_via_seed() { #[test] fn rad_remote() { let mut environment = Environment::new(); - let alice = environment.node(Config::test(Alias::new("alice"))); - let bob = environment.node(Config::test(Alias::new("bob"))); - let eve = environment.node(Config::test(Alias::new("eve"))); + let alice = environment.node(config::relay("alice")); + let bob = environment.node(config::relay("bob")); + let eve = environment.node(config::relay("eve")); let working = environment.tmp().join("working"); let home = alice.home.clone(); let rid = RepoId::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap(); diff --git a/radicle-httpd/src/api/v1/profile.rs b/radicle-httpd/src/api/v1/profile.rs index 85a61800..827c2b54 100644 --- a/radicle-httpd/src/api/v1/profile.rs +++ b/radicle-httpd/src/api/v1/profile.rs @@ -92,7 +92,7 @@ mod routes { "tor": null, "network": "main", "log": "INFO", - "relay": true, + "relay": "auto", "limits": { "routingMaxSize": 1000, "routingMaxAge": 604800, diff --git a/radicle-node/src/service.rs b/radicle-node/src/service.rs index e207d905..642b59c5 100644 --- a/radicle-node/src/service.rs +++ b/radicle-node/src/service.rs @@ -25,7 +25,7 @@ use radicle::node; use radicle::node::address; use radicle::node::address::Store as _; use radicle::node::address::{AddressBook, KnownAddress}; -use radicle::node::config::PeerConfig; +use radicle::node::config::{PeerConfig, Relay}; use radicle::node::refs::Store as _; use radicle::node::routing::Store as _; use radicle::node::seed; @@ -1334,7 +1334,7 @@ where &mut self, relayer_addr: &Address, announcement: &Announcement, - ) -> Result { + ) -> Result { if !announcement.verify() { return Err(session::Error::Misbehavior); } @@ -1346,7 +1346,7 @@ where // Ignore our own announcements, in case the relayer sent one by mistake. if announcer == self.nid() { - return Ok(false); + return Ok(Relay::Never); } let now = self.clock; let timestamp = message.timestamp(); @@ -1358,7 +1358,7 @@ where { self.config.relay } else { - false + Relay::Never }; // Don't allow messages from too far in the future. @@ -1379,12 +1379,12 @@ where Ok(node) => { if node.is_none() { debug!(target: "service", "Ignoring announcement from unknown node {announcer} (t={timestamp})"); - return Ok(false); + return Ok(Relay::Never); } } Err(e) => { error!(target: "service", "Error looking up node in address book: {e}"); - return Ok(false); + return Ok(Relay::Never); } } } @@ -1394,12 +1394,12 @@ where Ok(fresh) => { if !fresh { debug!(target: "service", "Ignoring stale announcement from {announcer} (t={timestamp})"); - return Ok(false); + return Ok(Relay::Never); } } Err(e) => { error!(target: "service", "Error updating gossip entry from {announcer}: {e}"); - return Ok(false); + return Ok(Relay::Never); } } @@ -1419,12 +1419,12 @@ where Ok(synced) => { if synced.is_empty() { trace!(target: "service", "No routes updated by inventory announcement from {announcer}"); - return Ok(false); + return Ok(Relay::Never); } } Err(e) => { error!(target: "service", "Error processing inventory from {announcer}: {e}"); - return Ok(false); + return Ok(Relay::Never); } } @@ -1473,7 +1473,7 @@ where // Empty announcements can be safely ignored. let Some(refs) = NonEmpty::from_vec(message.refs.to_vec()) else { debug!(target: "service", "Skipping fetch, no refs in announcement for {} (t={timestamp})", message.rid); - return Ok(false); + return Ok(Relay::Never); }; // We update inventories when receiving ref announcements, as these could come // from a new repository being initialized. @@ -1526,7 +1526,7 @@ where "Ignoring refs announcement from {announcer}: repository {} isn't seeded (t={timestamp})", message.rid ); - return Ok(false); + return Ok(Relay::Never); } // Refs can be relayed by peers who don't have the data in storage, // therefore we only check whether we are connected to the *announcer*, @@ -1600,7 +1600,7 @@ where } } } - Ok(false) + Ok(Relay::Never) } pub fn handle_info(&mut self, remote: NodeId, info: &Info) -> Result<(), session::Error> { @@ -1652,8 +1652,15 @@ where let relayer_addr = peer.addr.clone(); let announcer = ann.node; - // Returning true here means that the message should be relayed. - if self.handle_announcement(&relayer_addr, &ann)? { + let relay = match self.handle_announcement(&relayer_addr, &ann)? { + // In "auto" mode, we only relay if we're a public seed node. + // This reduces traffic for private nodes, as well as message redundancy. + Relay::Auto => !self.config.external_addresses.is_empty(), + Relay::Never => false, + Relay::Always => true, + }; + + if relay { // Choose peers we should relay this message to. // 1. Don't relay to the peer who sent us this message. // 2. Don't relay to the peer who signed this announcement. diff --git a/radicle-node/src/tests/e2e.rs b/radicle-node/src/tests/e2e.rs index 0dec00ad..ccdc9662 100644 --- a/radicle-node/src/tests/e2e.rs +++ b/radicle-node/src/tests/e2e.rs @@ -18,6 +18,19 @@ use crate::storage::git::transport; use crate::test::environment::{converge, Environment, Node}; use crate::test::logger; +mod config { + use super::*; + use radicle::node::config::{Config, Relay}; + + /// Relay node config. + pub fn relay(alias: &'static str) -> Config { + Config { + relay: Relay::Always, + ..Config::test(Alias::new(alias)) + } + } +} + #[test] // // alice -- bob @@ -27,8 +40,8 @@ fn test_inventory_sync_basic() { let tmp = tempfile::tempdir().unwrap(); - let mut alice = Node::init(tmp.path(), Config::test(Alias::new("alice"))); - let mut bob = Node::init(tmp.path(), Config::test(Alias::new("bob"))); + let mut alice = Node::init(tmp.path(), config::relay("alice")); + let mut bob = Node::init(tmp.path(), config::relay("bob")); alice.project("alice", ""); bob.project("bob", ""); @@ -51,9 +64,9 @@ fn test_inventory_sync_bridge() { let tmp = tempfile::tempdir().unwrap(); - let mut alice = Node::init(tmp.path(), Config::test(Alias::new("alice"))); - let mut bob = Node::init(tmp.path(), Config::test(Alias::new("bob"))); - let mut eve = Node::init(tmp.path(), Config::test(Alias::new("eve"))); + let mut alice = Node::init(tmp.path(), config::relay("alice")); + let mut bob = Node::init(tmp.path(), config::relay("bob")); + let mut eve = Node::init(tmp.path(), config::relay("eve")); alice.project("alice", ""); bob.project("bob", ""); @@ -81,9 +94,9 @@ fn test_inventory_sync_ring() { let tmp = tempfile::tempdir().unwrap(); - let mut alice = Node::init(tmp.path(), Config::test(Alias::new("alice"))); - let mut bob = Node::init(tmp.path(), Config::test(Alias::new("bob"))); - let mut eve = Node::init(tmp.path(), Config::test(Alias::new("eve"))); + let mut alice = Node::init(tmp.path(), config::relay("alice")); + let mut bob = Node::init(tmp.path(), config::relay("bob")); + let mut eve = Node::init(tmp.path(), config::relay("eve")); let mut carol = Node::init(tmp.path(), Config::test(Alias::new("carol"))); alice.project("alice", ""); @@ -118,9 +131,9 @@ fn test_inventory_sync_star() { let tmp = tempfile::tempdir().unwrap(); - let mut alice = Node::init(tmp.path(), Config::test(Alias::new("alice"))); - let mut bob = Node::init(tmp.path(), Config::test(Alias::new("bob"))); - let mut eve = Node::init(tmp.path(), Config::test(Alias::new("eve"))); + let mut alice = Node::init(tmp.path(), config::relay("alice")); + let mut bob = Node::init(tmp.path(), config::relay("bob")); + let mut eve = Node::init(tmp.path(), config::relay("eve")); let mut carol = Node::init(tmp.path(), Config::test(Alias::new("carol"))); let mut dave = Node::init(tmp.path(), Config::test(Alias::new("dave"))); @@ -150,8 +163,8 @@ fn test_replication() { logger::init(log::Level::Debug); let tmp = tempfile::tempdir().unwrap(); - let alice = Node::init(tmp.path(), Config::test(Alias::new("alice"))); - let mut bob = Node::init(tmp.path(), Config::test(Alias::new("bob"))); + let alice = Node::init(tmp.path(), config::relay("alice")); + let mut bob = Node::init(tmp.path(), config::relay("bob")); let acme = bob.project("acme", ""); let mut alice = alice.spawn(); @@ -210,8 +223,8 @@ fn test_replication_ref_in_sigrefs() { logger::init(log::Level::Debug); let tmp = tempfile::tempdir().unwrap(); - let alice = Node::init(tmp.path(), Config::test(Alias::new("alice"))); - let mut bob = Node::init(tmp.path(), Config::test(Alias::new("bob"))); + let alice = Node::init(tmp.path(), config::relay("alice")); + let mut bob = Node::init(tmp.path(), config::relay("bob")); let acme = bob.project("acme", ""); // Delete one of the signed refs. @@ -251,8 +264,8 @@ fn test_replication_ref_in_sigrefs() { #[test] fn test_replication_invalid() { let tmp = tempfile::tempdir().unwrap(); - let alice = Node::init(tmp.path(), Config::test(Alias::new("alice"))); - let mut bob = Node::init(tmp.path(), Config::test(Alias::new("bob"))); + let alice = Node::init(tmp.path(), config::relay("alice")); + let mut bob = Node::init(tmp.path(), config::relay("bob")); let carol = MockSigner::default(); let acme = bob.project("acme", ""); let repo = bob.storage.repository_mut(acme).unwrap(); @@ -304,8 +317,8 @@ fn test_migrated_clone() { logger::init(log::Level::Debug); let tmp = tempfile::tempdir().unwrap(); - let mut alice = Node::init(tmp.path(), Config::test(Alias::new("alice"))); - let bob = Node::init(tmp.path(), Config::test(Alias::new("bob"))); + let mut alice = Node::init(tmp.path(), config::relay("alice")); + let bob = Node::init(tmp.path(), config::relay("bob")); let acme = alice.project("acme", ""); let mut alice = alice.spawn(); @@ -357,8 +370,8 @@ fn test_dont_fetch_owned_refs() { logger::init(log::Level::Debug); let tmp = tempfile::tempdir().unwrap(); - let mut alice = Node::init(tmp.path(), Config::test(Alias::new("alice"))); - let bob = Node::init(tmp.path(), Config::test(Alias::new("bob"))); + let mut alice = Node::init(tmp.path(), config::relay("alice")); + let bob = Node::init(tmp.path(), config::relay("bob")); let acme = alice.project("acme", ""); let mut alice = alice.spawn(); @@ -384,8 +397,8 @@ fn test_fetch_followed_remotes() { logger::init(log::Level::Debug); let tmp = tempfile::tempdir().unwrap(); - let mut alice = Node::init(tmp.path(), Config::test(Alias::new("alice"))); - let bob = Node::init(tmp.path(), Config::test(Alias::new("bob"))); + let mut alice = Node::init(tmp.path(), config::relay("alice")); + let bob = Node::init(tmp.path(), config::relay("bob")); let acme = alice.project("acme", ""); let mut signers = Vec::with_capacity(5); { @@ -439,8 +452,8 @@ fn test_missing_remote() { logger::init(log::Level::Debug); let tmp = tempfile::tempdir().unwrap(); - let mut alice = Node::init(tmp.path(), Config::test(Alias::new("alice"))); - let bob = Node::init(tmp.path(), Config::test(Alias::new("bob"))); + let mut alice = Node::init(tmp.path(), config::relay("alice")); + let bob = Node::init(tmp.path(), config::relay("bob")); let acme = alice.project("acme", ""); let mut alice = alice.spawn(); @@ -468,8 +481,8 @@ fn test_fetch_preserve_owned_refs() { logger::init(log::Level::Debug); let tmp = tempfile::tempdir().unwrap(); - let mut alice = Node::init(tmp.path(), Config::test(Alias::new("alice"))); - let bob = Node::init(tmp.path(), Config::test(Alias::new("bob"))); + let mut alice = Node::init(tmp.path(), config::relay("alice")); + let bob = Node::init(tmp.path(), config::relay("bob")); let acme = alice.project("acme", ""); let mut alice = alice.spawn(); let mut bob = bob.spawn(); @@ -514,8 +527,8 @@ fn test_clone() { logger::init(log::Level::Debug); let tmp = tempfile::tempdir().unwrap(); - let alice = Node::init(tmp.path(), Config::test(Alias::new("alice"))); - let mut bob = Node::init(tmp.path(), Config::test(Alias::new("bob"))); + let alice = Node::init(tmp.path(), config::relay("alice")); + let mut bob = Node::init(tmp.path(), config::relay("bob")); let acme = bob.project("acme", ""); let mut alice = alice.spawn(); @@ -572,8 +585,8 @@ fn test_fetch_up_to_date() { logger::init(log::Level::Debug); let tmp = tempfile::tempdir().unwrap(); - let alice = Node::init(tmp.path(), Config::test(Alias::new("alice"))); - let mut bob = Node::init(tmp.path(), Config::test(Alias::new("bob"))); + let alice = Node::init(tmp.path(), config::relay("alice")); + let mut bob = Node::init(tmp.path(), config::relay("bob")); let acme = bob.project("acme", ""); let mut alice = alice.spawn(); @@ -601,8 +614,8 @@ fn test_fetch_unseeded() { logger::init(log::Level::Debug); let tmp = tempfile::tempdir().unwrap(); - let alice = Node::init(tmp.path(), Config::test(Alias::new("alice"))); - let mut bob = Node::init(tmp.path(), Config::test(Alias::new("bob"))); + let alice = Node::init(tmp.path(), config::relay("alice")); + let mut bob = Node::init(tmp.path(), config::relay("bob")); let acme = bob.project("acme", ""); let mut alice = alice.spawn(); @@ -631,8 +644,8 @@ fn test_large_fetch() { let env = Environment::new(); let scale = env.scale(); - let mut alice = Node::init(&env.tmp(), Config::test(Alias::new("alice"))); - let bob = Node::init(&env.tmp(), Config::test(Alias::new("bob"))); + let mut alice = Node::init(&env.tmp(), config::relay("alice")); + let bob = Node::init(&env.tmp(), config::relay("bob")); let tmp = tempfile::tempdir().unwrap(); let (repo, _) = fixtures::repository(tmp.path()); @@ -681,14 +694,16 @@ fn test_concurrent_fetches() { &env.tmp(), service::Config { limits: limits.clone(), - ..service::Config::test(Alias::new("alice")) + relay: radicle::node::config::Relay::Always, + ..config::relay("alice") }, ); let mut bob = Node::init( &env.tmp(), service::Config { limits, - ..service::Config::test(Alias::new("bob")) + relay: radicle::node::config::Relay::Always, + ..config::relay("bob") }, ); @@ -773,8 +788,8 @@ fn test_connection_crossing() { logger::init(log::Level::Debug); let tmp = tempfile::tempdir().unwrap(); - let alice = Node::init(tmp.path(), Config::test(Alias::new("alice"))); - let bob = Node::init(tmp.path(), Config::test(Alias::new("bob"))); + let alice = Node::init(tmp.path(), config::relay("alice")); + let bob = Node::init(tmp.path(), config::relay("bob")); let alice = alice.spawn(); let bob = bob.spawn(); @@ -841,9 +856,9 @@ fn test_non_fastforward_sigrefs() { let tmp = tempfile::tempdir().unwrap(); - let alice = Node::init(tmp.path(), Config::test(Alias::new("alice"))); - let mut bob = Node::init(tmp.path(), Config::test(Alias::new("bob"))); - let eve = Node::init(tmp.path(), Config::test(Alias::new("eve"))); + let alice = Node::init(tmp.path(), config::relay("alice")); + let mut bob = Node::init(tmp.path(), config::relay("bob")); + let eve = Node::init(tmp.path(), config::relay("eve")); let rid = bob.project("acme", ""); @@ -949,9 +964,9 @@ fn test_outdated_sigrefs() { let tmp = tempfile::tempdir().unwrap(); - let mut alice = Node::init(tmp.path(), Config::test(Alias::new("alice"))); - let bob = Node::init(tmp.path(), Config::test(Alias::new("bob"))); - let eve = Node::init(tmp.path(), Config::test(Alias::new("eve"))); + let mut alice = Node::init(tmp.path(), config::relay("alice")); + let bob = Node::init(tmp.path(), config::relay("bob")); + let eve = Node::init(tmp.path(), config::relay("eve")); let rid = alice.project("acme", ""); @@ -1045,9 +1060,9 @@ fn test_outdated_delegate_sigrefs() { let tmp = tempfile::tempdir().unwrap(); - let mut alice = Node::init(tmp.path(), Config::test(Alias::new("alice"))); - let bob = Node::init(tmp.path(), Config::test(Alias::new("bob"))); - let eve = Node::init(tmp.path(), Config::test(Alias::new("eve"))); + let mut alice = Node::init(tmp.path(), config::relay("alice")); + let bob = Node::init(tmp.path(), config::relay("bob")); + let eve = Node::init(tmp.path(), config::relay("eve")); let rid = alice.project("acme", ""); @@ -1133,8 +1148,8 @@ fn missing_default_branch() { let tmp = tempfile::tempdir().unwrap(); - let mut alice = Node::init(tmp.path(), Config::test(Alias::new("alice"))); - let bob = Node::init(tmp.path(), Config::test(Alias::new("bob"))); + let mut alice = Node::init(tmp.path(), config::relay("alice")); + let bob = Node::init(tmp.path(), config::relay("bob")); let rid = alice.project("acme", ""); @@ -1185,9 +1200,9 @@ fn test_background_foreground_fetch() { let tmp = tempfile::tempdir().unwrap(); - let mut alice = Node::init(tmp.path(), Config::test(Alias::new("alice"))); - let bob = Node::init(tmp.path(), Config::test(Alias::new("bob"))); - let eve = Node::init(tmp.path(), Config::test(Alias::new("eve"))); + let mut alice = Node::init(tmp.path(), config::relay("alice")); + let bob = Node::init(tmp.path(), config::relay("bob")); + let eve = Node::init(tmp.path(), config::relay("eve")); let rid = alice.project("acme", ""); @@ -1273,10 +1288,10 @@ fn test_catchup_on_refs_announcements() { logger::init(log::Level::Debug); let tmp = tempfile::tempdir().unwrap(); - let mut alice = Node::init(tmp.path(), Config::test(Alias::new("alice"))); - let bob = Node::init(tmp.path(), Config::test(Alias::new("bob"))); + let mut alice = Node::init(tmp.path(), config::relay("alice")); + let bob = Node::init(tmp.path(), config::relay("bob")); let bob_id = bob.id; - let seed = Node::init(tmp.path(), Config::test(Alias::new("seed"))); + let seed = Node::init(tmp.path(), config::relay("seed")); let acme = alice.project("acme", ""); let mut alice = alice.spawn(); diff --git a/radicle/src/node/config.rs b/radicle/src/node/config.rs index a6f9a32a..fba4d058 100644 --- a/radicle/src/node/config.rs +++ b/radicle/src/node/config.rs @@ -234,6 +234,19 @@ impl Default for PeerConfig { } } +/// Relay configuration. +#[derive(Debug, Copy, Clone, Default, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum Relay { + /// Always relay messages. + Always, + /// Never relay messages. + Never, + /// Relay messages when applicable. + #[default] + Auto, +} + /// Tor configuration. #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "camelCase", tag = "mode")] @@ -276,9 +289,9 @@ pub struct Config { #[serde(default = "defaults::log")] #[serde(with = "crate::serde_ext::string")] pub log: log::Level, - /// Whether or not our node should relay inventories. - #[serde(default = "crate::serde_ext::bool::yes")] - pub relay: bool, + /// Whether or not our node should relay messages. + #[serde(default, deserialize_with = "crate::serde_ext::ok_or_default")] + pub relay: Relay, /// Configured service limits. #[serde(default)] pub limits: Limits, @@ -310,7 +323,7 @@ impl Config { external_addresses: vec![], network: Network::default(), tor: None, - relay: true, + relay: Relay::default(), limits: Limits::default(), workers: DEFAULT_WORKERS, log: defaults::log(), diff --git a/radicle/src/serde_ext.rs b/radicle/src/serde_ext.rs index c5a2fbe9..8e785e53 100644 --- a/radicle/src/serde_ext.rs +++ b/radicle/src/serde_ext.rs @@ -110,6 +110,16 @@ pub fn is_default(t: &T) -> bool { t == &T::default() } +/// Deserialize a value, but if it fails, return the default value. +pub fn ok_or_default<'de, T, D>(deserializer: D) -> Result +where + T: serde::Deserialize<'de> + Default, + D: serde::Deserializer<'de>, +{ + let v: serde_json::Value = serde::Deserialize::deserialize(deserializer)?; + Ok(T::deserialize(v).unwrap_or_default()) +} + #[cfg(test)] mod test { use super::*;