Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 28 additions & 26 deletions code/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion code/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ itf = "0.2.3"
libp2p = { version = "0.56.0", features = ["macros", "identify", "tokio", "ed25519", "ecdsa", "tcp", "quic", "noise", "yamux", "gossipsub", "dns", "ping", "metrics", "request-response", "cbor", "serde", "kad"] }
libp2p-identity = "0.2.12"
libp2p-broadcast = { version = "0.3.0", package = "libp2p-scatter" }
libp2p-gossipsub = { version = "0.49.0", features = ["metrics"] }
libp2p-gossipsub = { version = "0.49.2", features = ["metrics"] }
multiaddr = "0.18.2"
multihash = { version = "0.19.3", default-features = false }
nix = { version = "0.29.0", features = ["signal"] }
Expand Down
2 changes: 2 additions & 0 deletions code/crates/app/src/spawn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,8 @@ fn make_network_config(cfg: &ConsensusConfig, value_sync_cfg: &ValueSyncConfig)
mesh_n_low: config.mesh_n_low(),
mesh_outbound_min: config.mesh_outbound_min(),
enable_peer_scoring: config.enable_peer_scoring(),
enable_explicit_peering: config.enable_explicit_peering(),
enable_flood_publish: config.enable_flood_publish(),
},
config::PubSubProtocol::Broadcast => GossipSubConfig::default(),
},
Expand Down
87 changes: 70 additions & 17 deletions code/crates/config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -298,12 +298,23 @@ pub struct GossipSubConfig {

/// Enable peer scoring to prioritize nodes based on their type in mesh formation
enable_peer_scoring: bool,

/// Enable explicit peering for persistent peers (validators).
/// When enabled, persistent peers are added as explicit peers in GossipSub,
/// ensuring guaranteed message delivery outside the mesh.
/// This eliminates mesh partitioning and backoff issues between validators.
enable_explicit_peering: bool,

/// Enable flood publishing (send messages to all known peers, not just mesh peers).
/// When enable_explicit_peering is true, enable_flood_publish is forced to false.
/// Otherwise, this setting controls flood_publish behavior.
enable_flood_publish: bool,
}

impl Default for GossipSubConfig {
fn default() -> Self {
// Peer scoring disabled by default
Self::new(6, 12, 4, 2, false)
// Peer scoring enabled by default, explicit peering disabled by default, flood_publish true by default
Self::new(6, 12, 4, 2, true, false, true)
}
}

Expand All @@ -315,17 +326,19 @@ impl GossipSubConfig {
mesh_n_low: usize,
mesh_outbound_min: usize,
enable_peer_scoring: bool,
enable_explicit_peering: bool,
enable_flood_publish: bool,
) -> Self {
let mut result = Self {
// Note: adjust() is disabled to allow mesh_n = 0 for testing gossip-only mode
Self {
mesh_n,
mesh_n_high,
mesh_n_low,
mesh_outbound_min,
enable_peer_scoring,
};

result.adjust();
result
enable_explicit_peering,
enable_flood_publish,
}
}

/// Adjust the configuration values.
Expand Down Expand Up @@ -371,22 +384,60 @@ impl GossipSubConfig {
pub fn enable_peer_scoring(&self) -> bool {
self.enable_peer_scoring
}

pub fn enable_explicit_peering(&self) -> bool {
self.enable_explicit_peering
}

pub fn enable_flood_publish(&self) -> bool {
self.enable_flood_publish
}
}

mod gossipsub {
use super::utils::bool_from_anything;
use super::utils::{bool_from_anything, usize_from_anything};

fn default_enable_peer_scoring() -> bool {
true
}

fn default_enable_explicit_peering() -> bool {
false
}

fn default_enable_flood_publish() -> bool {
true
}

fn default_zero() -> usize {
0
}

#[derive(serde::Deserialize)]
pub struct RawConfig {
#[serde(default)]
#[serde(default = "default_zero", deserialize_with = "usize_from_anything")]
mesh_n: usize,
#[serde(default)]
#[serde(default = "default_zero", deserialize_with = "usize_from_anything")]
mesh_n_high: usize,
#[serde(default)]
#[serde(default = "default_zero", deserialize_with = "usize_from_anything")]
mesh_n_low: usize,
#[serde(default)]
#[serde(default = "default_zero", deserialize_with = "usize_from_anything")]
mesh_outbound_min: usize,
#[serde(default, deserialize_with = "bool_from_anything")]
#[serde(
default = "default_enable_peer_scoring",
deserialize_with = "bool_from_anything"
)]
enable_peer_scoring: bool,
#[serde(
default = "default_enable_explicit_peering",
deserialize_with = "bool_from_anything"
)]
enable_explicit_peering: bool,
#[serde(
default = "default_enable_flood_publish",
deserialize_with = "bool_from_anything"
)]
enable_flood_publish: bool,
}

impl From<RawConfig> for super::GossipSubConfig {
Expand All @@ -397,6 +448,8 @@ mod gossipsub {
raw.mesh_n_low,
raw.mesh_outbound_min,
raw.enable_peer_scoring,
raw.enable_explicit_peering,
raw.enable_flood_publish,
)
}
}
Expand Down Expand Up @@ -1007,9 +1060,9 @@ mod tests {
}

#[test]
fn gossipsub_config_default_disables_peer_scoring() {
fn gossipsub_config_default_enables_peer_scoring() {
let config = GossipSubConfig::default();
assert!(!config.enable_peer_scoring());
assert!(config.enable_peer_scoring());
}

#[test]
Expand All @@ -1022,12 +1075,12 @@ mod tests {

let cases = [
TestCase {
name: "missing field defaults to false",
name: "missing field defaults to true",
toml: r#"
[p2p.protocol]
type = "gossipsub"
"#,
expected: false,
expected: true,
},
TestCase {
name: "explicit true",
Expand Down
42 changes: 42 additions & 0 deletions code/crates/config/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,45 @@ where

deserializer.deserialize_any(BoolVisitor)
}

/// Deserializes a usize value from either a native integer or a string
pub fn usize_from_anything<'de, D>(deserializer: D) -> Result<usize, D::Error>
where
D: serde::Deserializer<'de>,
{
struct UsizeVisitor;

impl<'de> de::Visitor<'de> for UsizeVisitor {
type Value = usize;

fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(formatter, "a usize or a string representing a usize")
}

fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
where
E: de::Error,
{
usize::try_from(v)
.map_err(|_| E::custom(format!("u64 value {} out of range for usize", v)))
}

fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
where
E: de::Error,
{
usize::try_from(v)
.map_err(|_| E::custom(format!("i64 value {} out of range for usize", v)))
}

fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
v.parse::<usize>()
.map_err(|_| E::custom(format!("invalid usize string: {}", v)))
}
}

deserializer.deserialize_any(UsizeVisitor)
}
Loading
Loading