From a9b94b0ad671e4d8e3129b1e2da86325a114896f Mon Sep 17 00:00:00 2001 From: cloudhead Date: Wed, 1 May 2024 16:06:23 +0200 Subject: [PATCH] node: New relay configuration for nodes Relay now has three values: "always", "never" and "auto". The first two operate like the previous `true`/`false`. The new (default) value turns relaying on for nodes with public (external) addresses and off for nodes without. This should reduce redundant traffic on the network, that was causing the rate limiters to trigger. --- radicle-cli/examples/rad-config.md | 2 +- radicle-cli/examples/rad-sync.md | 28 +++--- radicle-cli/tests/commands.rs | 50 ++++++----- radicle-httpd/src/api/v1/profile.rs | 2 +- radicle-node/src/service.rs | 37 ++++---- radicle-node/src/tests/e2e.rs | 127 ++++++++++++++++------------ radicle/src/node/config.rs | 21 ++++- radicle/src/serde_ext.rs | 10 +++ 8 files changed, 165 insertions(+), 112 deletions(-) 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::*;