Skip to content
Open
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
21 changes: 21 additions & 0 deletions Cargo.lock

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

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ members = [
"crates/peerinfo",
"crates/frost",
"crates/priority",
"crates/infosync",
]
# Vendored fork consumed only via [patch.crates-io]; excluded so it builds/tests
# standalone (its upstream code isn't written to this workspace's lints) without
Expand Down Expand Up @@ -139,6 +140,8 @@ pluto-tracing = { path = "crates/tracing" }
pluto-p2p = { path = "crates/p2p" }
pluto-peerinfo = { path = "crates/peerinfo" }
pluto-frost = { path = "crates/frost" }
pluto-priority = { path = "crates/priority" }
pluto-infosync = { path = "crates/infosync" }

[workspace.lints.rust]
missing_docs = "deny"
Expand Down
89 changes: 87 additions & 2 deletions crates/core/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -303,15 +303,71 @@ impl TryFrom<&pbcore::Duty> for Duty {
}

/// The type of proposal.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
///
/// An open set: values not recognised by this binary are preserved as
/// [`ProposalType::Unknown`] rather than dropped, so cluster-agreed proposal
/// types from newer peers survive round-trips.
///
/// (De)serialized as its wire-format string via the `String` conversions below,
/// so unknown values round-trip verbatim.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(from = "String", into = "String")]
pub enum ProposalType {
/// Full proposal type.
Full,
/// Builder proposal type.
Builder,
/// Synthetic proposal type.
Synthetic,
/// A proposal type not recognised by this binary, holding its raw wire
/// string.
Unknown(String),
}

impl ProposalType {
/// Returns the wire-format string for this proposal type.
///
/// The strings for the known variants MUST NOT change: they are exchanged
/// on the wire (e.g. by the priority/infosync protocols) and changing
/// them breaks compatibility.
pub fn as_str(&self) -> &str {
match self {
ProposalType::Full => "full",
ProposalType::Builder => "builder",
ProposalType::Synthetic => "synthetic",
ProposalType::Unknown(s) => s,
}
}
}

impl From<String> for ProposalType {
/// Parses a wire string, mapping unrecognised values to
/// [`ProposalType::Unknown`] and reusing the allocation.
fn from(value: String) -> Self {
match value.as_str() {
"full" => ProposalType::Full,
"builder" => ProposalType::Builder,
"synthetic" => ProposalType::Synthetic,
_ => ProposalType::Unknown(value),
}
}
}

impl From<&str> for ProposalType {
fn from(value: &str) -> Self {
ProposalType::from(value.to_owned())
}
}

impl From<ProposalType> for String {
/// Returns the wire-format string, reusing the [`ProposalType::Unknown`]
/// allocation.
fn from(value: ProposalType) -> Self {
match value {
ProposalType::Unknown(s) => s,
other => other.as_str().to_owned(),
}
}
}

// In golang implementation they use pk_len = 98, which is 0x + [48 bytes]
Expand Down Expand Up @@ -975,6 +1031,35 @@ mod tests {
assert_eq!(DutyType::InfoSync.to_string(), "info_sync");
}

#[test]
fn proposal_type_wire_round_trip() {
for (pt, s) in [
(ProposalType::Full, "full"),
(ProposalType::Builder, "builder"),
(ProposalType::Synthetic, "synthetic"),
] {
assert_eq!(pt.as_str(), s);
assert_eq!(ProposalType::from(s), pt);
}

// Unrecognised wire strings are preserved as Unknown, not dropped.
let unknown = ProposalType::from("future_type");
assert_eq!(unknown, ProposalType::Unknown("future_type".to_owned()));
assert_eq!(unknown.as_str(), "future_type");
}

#[test]
fn proposal_type_serde_is_wire_string() {
assert_eq!(
serde_json::to_string(&ProposalType::Builder).expect("serialize"),
"\"builder\""
);
assert_eq!(
serde_json::from_str::<ProposalType>("\"future_type\"").expect("deserialize"),
ProposalType::Unknown("future_type".to_owned())
);
}

#[test]
fn duty_type_is_valid() {
assert!(!DutyType::Unknown.is_valid());
Expand Down
29 changes: 29 additions & 0 deletions crates/infosync/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[package]
name = "pluto-infosync"
version.workspace = true
edition.workspace = true
repository.workspace = true
license.workspace = true
publish.workspace = true

[dependencies]
pluto-core.workspace = true
pluto-featureset.workspace = true
pluto-priority.workspace = true
tokio-util.workspace = true
tracing.workspace = true

[dev-dependencies]
async-trait.workspace = true
chrono.workspace = true
futures.workspace = true
libp2p.workspace = true
pluto-consensus.workspace = true
pluto-p2p.workspace = true
pluto-parsigex.workspace = true
pluto-peerinfo.workspace = true
pluto-testutil.workspace = true
tokio = { workspace = true, features = ["rt-multi-thread", "macros", "time", "sync"] }

[lints]
workspace = true
Loading
Loading