From d830d4cbb970c23a77e9e4f44161ad51beaf148c Mon Sep 17 00:00:00 2001 From: Kyrylo Baibula <64193423+Velnbur@users.noreply.github.com> Date: Wed, 8 Jan 2025 17:15:01 +0200 Subject: [PATCH] Move db and protobuf related code to separate crates (#8) Move `crates/p2p/src/db` to `crates/db`, `crates/p2p/src/wire` to `crates/wire` and then `crates/wire/proto/` out of `wire` to top level `proto`. Also some minor refactor stuff and fixes to make CI happy were applied. --- Cargo.lock | 33 +- Cargo.toml | 36 +- crates/db/Cargo.toml | 23 ++ crates/{p2p/src/db/mod.rs => db/src/lib.rs} | 38 +- crates/db/src/prost_serde.rs | 20 + crates/{p2p/src/db => db/src}/sled.rs | 8 +- crates/{p2p => db}/src/states.rs | 0 crates/p2p/Cargo.toml | 57 ++- crates/p2p/build.rs | 12 - .../strata/bitvm2/p2p/v1/gossipsub.proto | 48 --- crates/p2p/src/commands.rs | 5 +- crates/p2p/src/events.rs | 3 +- crates/p2p/src/lib.rs | 3 - crates/p2p/src/swarm/behavior.rs | 2 +- crates/p2p/src/swarm/mod.rs | 18 +- crates/p2p/src/timeouts.rs | 2 +- crates/p2p/src/wire/mod.rs | 3 - crates/p2p/src/wire/p2p/mod.rs | 1 - crates/p2p/src/wire/p2p/v1.rs | 355 ------------------ crates/p2p/tests/gossipsub.rs | 7 +- crates/wire/Cargo.toml | 13 + crates/wire/build.rs | 12 + crates/wire/src/lib.rs | 1 + crates/wire/src/p2p/mod.rs | 1 + crates/wire/src/p2p/v1.rs | 6 + crates/wire/src/p2p/v1/typed.rs | 349 +++++++++++++++++ .../strata/bitvm2/p2p/v1/getmessage.proto | 2 +- proto/strata/bitvm2/p2p/v1/gossipsub.proto | 48 +++ 28 files changed, 589 insertions(+), 517 deletions(-) create mode 100644 crates/db/Cargo.toml rename crates/{p2p/src/db/mod.rs => db/src/lib.rs} (86%) create mode 100644 crates/db/src/prost_serde.rs rename crates/{p2p/src/db => db/src}/sled.rs (94%) rename crates/{p2p => db}/src/states.rs (100%) delete mode 100644 crates/p2p/build.rs delete mode 100644 crates/p2p/proto/strata/bitvm2/p2p/v1/gossipsub.proto delete mode 100644 crates/p2p/src/wire/mod.rs delete mode 100644 crates/p2p/src/wire/p2p/mod.rs delete mode 100644 crates/p2p/src/wire/p2p/v1.rs create mode 100644 crates/wire/Cargo.toml create mode 100644 crates/wire/build.rs create mode 100644 crates/wire/src/lib.rs create mode 100644 crates/wire/src/p2p/mod.rs create mode 100644 crates/wire/src/p2p/v1.rs create mode 100644 crates/wire/src/p2p/v1/typed.rs rename {crates/p2p/proto => proto}/strata/bitvm2/p2p/v1/getmessage.proto (93%) create mode 100644 proto/strata/bitvm2/p2p/v1/gossipsub.proto diff --git a/Cargo.lock b/Cargo.lock index c2022d5..88e722a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3192,19 +3192,48 @@ dependencies = [ "libp2p", "musig2", "prost", - "prost-build", "rand", "secp256k1", "serde", - "serde_json", "sled", "snafu", + "strata-p2p-db", + "strata-p2p-wire", "tokio", "tokio-util", "tracing", "tracing-subscriber", ] +[[package]] +name = "strata-p2p-db" +version = "0.1.0" +dependencies = [ + "async-trait", + "bitcoin", + "libp2p-identity", + "musig2", + "prost", + "rand", + "secp256k1", + "serde", + "serde_json", + "sled", + "snafu", + "tokio", +] + +[[package]] +name = "strata-p2p-wire" +version = "0.1.0" +dependencies = [ + "bitcoin", + "libp2p-identity", + "musig2", + "prost", + "prost-build", +] + [[package]] name = "subtle" version = "2.6.1" diff --git a/Cargo.toml b/Cargo.toml index 2b0f277..07a3656 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,42 @@ [workspace] -members = ["crates/p2p"] +members = ["crates/db", "crates/p2p", "crates/wire"] default-members = ["crates/p2p"] resolver = "2" [workspace.dependencies] +async-trait = "0.1.83" +bitcoin = { version = "0.32.5", features = ["serde"] } +futures = "0.3.31" +libp2p = { version = "0.54.1", features = [ + "noise", + "gossipsub", + "tcp", + "request-response", + "tokio", + "secp256k1", + "macros", + "ping", + "yamux", + "identify", +] } +libp2p-identity = { version = "0.2.10", default-features = false, features = [ + "secp256k1", + "peerid", + "rand", +] } +musig2 = { version = "0.1.0", features = ["serde"] } +prost = "0.13.4" +serde = { version = "1.0.217", features = ["derive"] } +snafu = { version = "0.8.5", default-features = false, features = [ + "rust_1_81", + "backtrace", + "std", +] } +tokio = { version = "1.42.0", default-features = false, features = [ + "macros", + "time", +] } +tokio-util = { version = "0.7.13", default-features = false } +tracing = { version = "0.1.41", default-features = false } diff --git a/crates/db/Cargo.toml b/crates/db/Cargo.toml new file mode 100644 index 0000000..d029f6a --- /dev/null +++ b/crates/db/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "strata-p2p-db" +version = "0.1.0" +edition = "2024" + +[dependencies] +async-trait.workspace = true +bitcoin.workspace = true +libp2p-identity.workspace = true +musig2.workspace = true +prost.workspace = true +serde.workspace = true +snafu.workspace = true + +# make this implementation optional +sled = "0.34.7" +# temporaty solution for DB serialization. `ciborium` crate is not working, unfortunatly. +serde_json = "1.0.135" + +[dev-dependencies] +tokio = { version = "1.42.0", features = ["rt", "macros"] } +rand = "0.8.5" +secp256k1 = { version = "0.29.0", features = ["rand"] } diff --git a/crates/p2p/src/db/mod.rs b/crates/db/src/lib.rs similarity index 86% rename from crates/p2p/src/db/mod.rs rename to crates/db/src/lib.rs index 1229311..0d05976 100644 --- a/crates/p2p/src/db/mod.rs +++ b/crates/db/src/lib.rs @@ -1,14 +1,16 @@ -mod sled; - use async_trait::async_trait; -use bitcoin::{hashes::sha256, OutPoint, XOnlyPublicKey}; -use libp2p::PeerId; +use bitcoin::{OutPoint, XOnlyPublicKey, hashes::sha256}; +use libp2p_identity::PeerId; use musig2::{PartialSignature, PubNonce}; -use serde::{de::DeserializeOwned, Serialize}; +use serde::{Serialize, de::DeserializeOwned}; use snafu::{ResultExt, Snafu}; use crate::states::PeerDepositState; +mod prost_serde; +mod sled; +pub mod states; + pub type DBResult = Result; #[derive(Debug, Snafu)] @@ -176,29 +178,3 @@ pub struct DepositSetupEntry { pub payload: DSP, pub signature: Vec, } - -mod prost_serde { - use std::io::Cursor; - - use serde::{de::Error, Deserialize, Deserializer, Serializer}; - - pub fn serialize(value: &T, serializer: S) -> Result - where - T: prost::Message, - S: Serializer, - { - serializer.serialize_bytes(&value.encode_to_vec()) - } - - pub fn deserialize<'de, T, D>(deserializer: D) -> Result - where - D: Deserializer<'de>, - T: prost::Message + Default, - { - let bytes = Vec::::deserialize(deserializer)?; - let mut curr = Cursor::new(bytes); - let msg = T::decode(&mut curr).map_err(Error::custom)?; - - Ok(msg) - } -} diff --git a/crates/db/src/prost_serde.rs b/crates/db/src/prost_serde.rs new file mode 100644 index 0000000..45ebf56 --- /dev/null +++ b/crates/db/src/prost_serde.rs @@ -0,0 +1,20 @@ +use serde::{Deserialize, Deserializer, Serializer, de::Error}; + +pub fn serialize(value: &T, serializer: S) -> Result +where + T: prost::Message, + S: Serializer, +{ + serializer.serialize_bytes(&value.encode_to_vec()) +} + +pub fn deserialize<'de, T, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, + T: prost::Message + Default, +{ + let bytes = Vec::::deserialize(deserializer)?; + let msg = T::decode(bytes.as_slice()).map_err(Error::custom)?; + + Ok(msg) +} diff --git a/crates/p2p/src/db/sled.rs b/crates/db/src/sled.rs similarity index 94% rename from crates/p2p/src/db/sled.rs rename to crates/db/src/sled.rs index 2795377..6bb077b 100644 --- a/crates/p2p/src/db/sled.rs +++ b/crates/db/src/sled.rs @@ -27,15 +27,15 @@ impl From for RepositoryError { #[cfg(test)] mod tests { use bitcoin::{ - hashes::{sha256, Hash}, OutPoint, XOnlyPublicKey, + hashes::{Hash, sha256}, }; - use libp2p::PeerId; - use musig2::{sign_partial, AggNonce, KeyAggContext, SecNonce}; + use libp2p_identity::PeerId; + use musig2::{AggNonce, KeyAggContext, SecNonce, sign_partial}; use rand::thread_rng; use secp256k1::{All, Keypair, Secp256k1}; - use crate::db::{GenesisInfoEntry, NoncesEntry, PartialSignaturesEntry, RepositoryExt}; + use crate::{GenesisInfoEntry, NoncesEntry, PartialSignaturesEntry, RepositoryExt}; #[tokio::test] async fn test_repository() { diff --git a/crates/p2p/src/states.rs b/crates/db/src/states.rs similarity index 100% rename from crates/p2p/src/states.rs rename to crates/db/src/states.rs diff --git a/crates/p2p/Cargo.toml b/crates/p2p/Cargo.toml index 8d888a6..2a1ab8f 100644 --- a/crates/p2p/Cargo.toml +++ b/crates/p2p/Cargo.toml @@ -4,47 +4,36 @@ version = "0.1.0" edition = "2021" [dependencies] -async-trait = "0.1.83" -bitcoin = { version = "0.32.5", features = ["serde"] } -futures = "0.3.31" libp2p = { version = "0.54.1", features = [ - "noise", - "gossipsub", - "tcp", - "request-response", - "tokio", - "secp256k1", - "macros", - "ping", - "yamux", - "identify", + "noise", + "gossipsub", + "tcp", + "request-response", + "tokio", + "secp256k1", + "macros", + "ping", + "yamux", + "identify", ] } -musig2 = { version = "0.1.0", features = ["serde"] } -prost = "0.13.4" -serde = { version = "1.0.217", features = ["derive"] } -snafu = { version = "0.8.5", default-features = false, features = [ - "rust_1_81", - "backtrace", - "std", -] } -tokio = { version = "1.42.0", default-features = false, features = [ - "macros", - "time", -] } -tokio-util = { version = "0.7.13", default-features = false } -tracing = { version = "0.1.41", default-features = false } -# make this implementation optional -sled = "0.34.7" -# temporaty solution for DB serialization. `ciborium` crate is not working, unfortunatly. -serde_json = "1.0.135" +async-trait.workspace = true +bitcoin = { workspace = true, features = ["serde"] } +futures.workspace = true +musig2 = { workspace = true, features = ["serde"] } +prost.workspace = true +serde = { workspace = true, features = ["derive"] } +snafu = { workspace = true, features = ["rust_1_81", "backtrace", "std"] } +strata-p2p-db.path = "../db" +strata-p2p-wire.path = "../wire" +tokio = { workspace = true, features = ["macros", "time"] } +tokio-util.workspace = true +tracing.workspace = true [dev-dependencies] +sled = "0.34.7" tokio = { version = "1.42.0", features = ["rt", "macros", "rt-multi-thread"] } rand = "0.8.5" secp256k1 = { version = "0.29.0", features = ["rand"] } tokio-util = { version = "0.7.13", default-features = false, features = ["rt"] } tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } - -[build-dependencies] -prost-build = "0.13.4" diff --git a/crates/p2p/build.rs b/crates/p2p/build.rs deleted file mode 100644 index ec431fb..0000000 --- a/crates/p2p/build.rs +++ /dev/null @@ -1,12 +0,0 @@ -use std::io::Result; - -fn main() -> Result<()> { - prost_build::compile_protos( - &[ - "proto/strata/bitvm2/p2p/v1/getmessage.proto", - "proto/strata/bitvm2/p2p/v1/gossipsub.proto", - ], - &["proto/"], - )?; - Ok(()) -} diff --git a/crates/p2p/proto/strata/bitvm2/p2p/v1/gossipsub.proto b/crates/p2p/proto/strata/bitvm2/p2p/v1/gossipsub.proto deleted file mode 100644 index b923572..0000000 --- a/crates/p2p/proto/strata/bitvm2/p2p/v1/gossipsub.proto +++ /dev/null @@ -1,48 +0,0 @@ -syntax = "proto3"; - -package strata.bitvm2.p2p.v1; - -message GenesisInfo { - // output number of pre stake tx - uint32 pre_stake_vout = 1; - // transaction hash of pre stake tx - bytes pre_stake_txid = 2; - // keys Y_{i, j} - repeated bytes checkpoint_pubkeys = 3; -} - -message DepositSetupExchange { - // 32-byte hash of some unique to deposit data - bytes scope = 1; - // Some arbitrary payload which depends on the implementation. - bytes payload = 2; -} - -message DepositNoncesExchange { - // 32-byte hash of some unique to deposit data - bytes scope = 1; - // Public nonces for each transaction - repeated bytes pub_nonces = 2; -} - -message DepositSignaturesExchange { - // 32-byte hash of some unique to deposit data - bytes scope = 1; - // Partial signatures for each transaction - repeated bytes partial_sigs = 2; -} - -message GossipsubMsg { - oneof body { - GenesisInfo genesis_info = 1; - DepositSetupExchange setup = 2; - DepositNoncesExchange nonce = 3; - DepositSignaturesExchange sigs = 4; - } - // Public key of the peer. The hash of this key must the same as - // source PeerId of thi message. - bytes key = 10; - // Signature of concatenated content of body (without protobuf - // serialization). - bytes signature = 11; -} diff --git a/crates/p2p/src/commands.rs b/crates/p2p/src/commands.rs index 57452d7..d3b5598 100644 --- a/crates/p2p/src/commands.rs +++ b/crates/p2p/src/commands.rs @@ -1,10 +1,9 @@ -//! Commands for P2P implementation from operator implemenation. +//! Commands for P2P implementation from operator implementation. use bitcoin::{hashes::sha256, OutPoint, XOnlyPublicKey}; use musig2::{PartialSignature, PubNonce}; use prost::Message; - -use crate::wire::p2p::v1::typed::{ +use strata_p2p_wire::p2p::v1::{ DepositNonces, DepositSetup, DepositSigs, GenesisInfo, GossipsubMsgDepositKind, GossipsubMsgKind, }; diff --git a/crates/p2p/src/events.rs b/crates/p2p/src/events.rs index 4bcca97..76216aa 100644 --- a/crates/p2p/src/events.rs +++ b/crates/p2p/src/events.rs @@ -1,8 +1,7 @@ use bitcoin::hashes::sha256; use libp2p::PeerId; use prost::Message; - -use crate::wire::p2p::v1::typed::{GossipsubMsg, GossipsubMsgKind}; +use strata_p2p_wire::p2p::v1::{GossipsubMsg, GossipsubMsgKind}; /// Events emitted from P2P to handle from operator side. /// diff --git a/crates/p2p/src/lib.rs b/crates/p2p/src/lib.rs index 2ecb0ff..006b422 100644 --- a/crates/p2p/src/lib.rs +++ b/crates/p2p/src/lib.rs @@ -1,7 +1,4 @@ pub mod commands; -pub(crate) mod db; pub mod events; -pub(crate) mod states; pub mod swarm; pub(crate) mod timeouts; -pub mod wire; diff --git a/crates/p2p/src/swarm/behavior.rs b/crates/p2p/src/swarm/behavior.rs index 223cb14..b5ccc83 100644 --- a/crates/p2p/src/swarm/behavior.rs +++ b/crates/p2p/src/swarm/behavior.rs @@ -15,9 +15,9 @@ use libp2p::{ swarm::NetworkBehaviour, PeerId, StreamProtocol, }; +use strata_p2p_wire::p2p::v1::proto::{GetMessageRequest, GetMessageResponse}; use super::{codec, hasher::Sha256Hasher, TOPIC}; -use crate::wire::p2p::v1::{GetMessageRequest, GetMessageResponse}; pub type RequestResponseProtoBehaviour = RequestResponse>; diff --git a/crates/p2p/src/swarm/mod.rs b/crates/p2p/src/swarm/mod.rs index 28d7d63..d75aa8f 100644 --- a/crates/p2p/src/swarm/mod.rs +++ b/crates/p2p/src/swarm/mod.rs @@ -18,6 +18,14 @@ use libp2p::{ }; use prost::Message as ProtoMsg; use snafu::prelude::*; +use strata_p2p_db::{ + states::PeerDepositState, DBResult, DepositSetupEntry, GenesisInfoEntry, NoncesEntry, + PartialSignaturesEntry, RepositoryError, RepositoryExt, +}; +use strata_p2p_wire::p2p::v1::{ + proto::{get_message_request, DepositRequestKey, GetMessageRequest, GetMessageResponse}, + GossipsubMsg, GossipsubMsgDepositKind, GossipsubMsgKind, +}; use tokio::{ select, sync::{broadcast, mpsc}, @@ -27,18 +35,8 @@ use tracing::{debug, error, info, instrument}; use crate::{ commands::Command, - db::{ - DBResult, DepositSetupEntry, GenesisInfoEntry, NoncesEntry, PartialSignaturesEntry, - RepositoryError, RepositoryExt, - }, events::{Event, EventKind}, - states::PeerDepositState, timeouts::{TimeoutEvent, TimeoutsManager}, - wire::p2p::v1::{ - get_message_request, - typed::{GossipsubMsg, GossipsubMsgDepositKind, GossipsubMsgKind}, - DepositRequestKey, GetMessageRequest, GetMessageResponse, - }, }; mod behavior; diff --git a/crates/p2p/src/timeouts.rs b/crates/p2p/src/timeouts.rs index 96425cb..151b95a 100644 --- a/crates/p2p/src/timeouts.rs +++ b/crates/p2p/src/timeouts.rs @@ -11,7 +11,7 @@ use futures::{FutureExt, Stream, StreamExt}; use libp2p::PeerId; use tokio::time::{sleep, Sleep}; -/// Kind of timeout which can be ommited by [`TimeoutsManager`]. +/// Kind of timeout which can be omitted by [`TimeoutsManager`]. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub(crate) enum TimeoutEvent { /// Timeout related to genesis stage for some peer. diff --git a/crates/p2p/src/wire/mod.rs b/crates/p2p/src/wire/mod.rs deleted file mode 100644 index 67a7caa..0000000 --- a/crates/p2p/src/wire/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod p2p; - -pub use p2p::v1::typed::*; diff --git a/crates/p2p/src/wire/p2p/mod.rs b/crates/p2p/src/wire/p2p/mod.rs deleted file mode 100644 index c0753a1..0000000 --- a/crates/p2p/src/wire/p2p/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub(crate) mod v1; diff --git a/crates/p2p/src/wire/p2p/v1.rs b/crates/p2p/src/wire/p2p/v1.rs deleted file mode 100644 index d0480f4..0000000 --- a/crates/p2p/src/wire/p2p/v1.rs +++ /dev/null @@ -1,355 +0,0 @@ -include!(concat!(env!("OUT_DIR"), "/strata.bitvm2.p2p.v1.rs")); - -pub mod typed { - use bitcoin::{ - consensus::Decodable, - hashes::{sha256, Hash}, - io::Cursor, - OutPoint, XOnlyPublicKey, - }; - use libp2p::{identity::secp256k1::PublicKey, PeerId}; - use musig2::{PartialSignature, PubNonce}; - use prost::{DecodeError, Message}; - - use super::{ - get_message_request::Body, gossipsub_msg::Body as ProtoGossipsubMsgBody, - DepositNoncesExchange as ProtoDepositNonces, DepositRequestKey, - DepositSetupExchange as ProtoDepositSetup, DepositSignaturesExchange as ProtoDepositSigs, - GenesisInfo as ProtoGenesisInfo, GenesisRequestKey, GossipsubMsg as ProtoGossipMsg, - }; - - pub enum GetMessageRequestExchangeKind { - Setup, - Nonces, - Signatures, - } - - /// Typed version of "get_message_request::GetMessageRequest". - #[allow(unused)] - pub enum GetMessageRequest { - Genesis { - operator_id: PeerId, - }, - ExchangeSession { - scope: sha256::Hash, - operator_id: PeerId, - kind: GetMessageRequestExchangeKind, - }, - } - - impl GetMessageRequest { - pub fn from_msg(msg: super::GetMessageRequest) -> Option { - let body = msg.body?; - - let (operator_id, deposit_txid, kind) = match body { - Body::DepositSetup(DepositRequestKey { scope, operator }) => { - (scope, operator, GetMessageRequestExchangeKind::Setup) - } - Body::DepositNonce(DepositRequestKey { scope, operator }) => { - (scope, operator, GetMessageRequestExchangeKind::Nonces) - } - Body::DepositSigs(DepositRequestKey { scope, operator }) => { - (scope, operator, GetMessageRequestExchangeKind::Signatures) - } - Body::GenesisInfo(GenesisRequestKey { operator }) => { - return Some(Self::Genesis { - operator_id: PeerId::from_bytes(&operator).ok()?, - }); - } - }; - - let operator_id = PeerId::from_bytes(&operator_id).ok()?; - let mut cur = Cursor::new(deposit_txid); - let scope = Decodable::consensus_decode(&mut cur).ok()?; - - Some(Self::ExchangeSession { - scope, - operator_id, - kind, - }) - } - } - - /// New deposit request apeared, and operators - /// exchanging setup data. - #[derive(Debug, Clone)] - pub struct DepositSetup { - /// Some arbitrary payload - pub payload: DepositSetupPayload, - } - - impl DepositSetup { - pub fn from_proto_msg(proto: &ProtoDepositSetup) -> Result { - let payload: DSP = Message::decode(proto.payload.as_ref())?; - - Ok(Self { payload }) - } - } - - /// Operators exchange nonces before signing. - #[derive(Debug, Clone)] - pub struct DepositNonces { - pub nonces: Vec, - } - - impl DepositNonces { - pub fn from_proto_msg(proto: &ProtoDepositNonces) -> Result { - let pub_nonces = proto - .pub_nonces - .iter() - .map(|bytes| PubNonce::from_bytes(bytes)) - .collect::, _>>() - .map_err(|err| DecodeError::new(err.to_string()))?; - - Ok(Self { nonces: pub_nonces }) - } - } - - /// Operators exchange signatures for transaction graph. - #[derive(Debug, Clone)] - pub struct DepositSigs { - pub partial_sigs: Vec, - } - - impl DepositSigs { - pub fn from_proto_msg(proto: &ProtoDepositSigs) -> Result { - let partial_sigs = proto - .partial_sigs - .iter() - .map(|bytes| PartialSignature::from_slice(bytes)) - .collect::, _>>() - .map_err(|err| DecodeError::new(err.to_string()))?; - - Ok(Self { partial_sigs }) - } - } - - /// Info provided during initial startup of nodes. - #[derive(Debug, Clone)] - pub struct GenesisInfo { - pub pre_stake_outpoint: OutPoint, - pub checkpoint_pubkeys: Vec, - } - - impl GenesisInfo { - pub fn from_proto_msg(proto: &ProtoGenesisInfo) -> Result { - let mut curr = Cursor::new(&proto.pre_stake_txid); - let txid = Decodable::consensus_decode(&mut curr) - .map_err(|err| DecodeError::new(err.to_string()))?; - - let outpoint = OutPoint::new(txid, proto.pre_stake_vout); - - let pubkeys = proto - .checkpoint_pubkeys - .iter() - .map(|bytes| XOnlyPublicKey::from_slice(bytes)) - .collect::, _>>() - .map_err(|err| DecodeError::new(err.to_string()))?; - - Ok(Self { - pre_stake_outpoint: outpoint, - checkpoint_pubkeys: pubkeys, - }) - } - } - - #[derive(Clone, Debug)] - pub enum GossipsubMsgDepositKind { - /// New deposit request apeared, and operators - /// exchanging setup data. - Setup(DepositSetup), - /// Operators exchange nonces before signing. - Nonces(DepositNonces), - /// Operators exchange signatures for transaction graph. - Sigs(DepositSigs), - } - - impl From - for GossipsubMsgDepositKind - { - fn from(v: DepositSigs) -> Self { - Self::Sigs(v) - } - } - - impl From - for GossipsubMsgDepositKind - { - fn from(v: DepositNonces) -> Self { - Self::Nonces(v) - } - } - - impl From> - for GossipsubMsgDepositKind - { - fn from(v: DepositSetup) -> Self { - Self::Setup(v) - } - } - - #[derive(Clone, Debug)] - pub enum GossipsubMsgKind { - /// Operators exchange - GenesisInfo(GenesisInfo), - Deposit { - scope: sha256::Hash, - kind: GossipsubMsgDepositKind, - }, - } - - impl GossipsubMsgKind { - pub fn from_msg_proto(proto: &ProtoGossipsubMsgBody) -> Result { - let (scope, kind) = match proto { - ProtoGossipsubMsgBody::GenesisInfo(proto) => { - return Ok(Self::GenesisInfo(GenesisInfo::from_proto_msg(proto)?)); - } - ProtoGossipsubMsgBody::Setup(proto) => { - (&proto.scope, DepositSetup::from_proto_msg(proto)?.into()) - } - ProtoGossipsubMsgBody::Nonce(proto) => { - (&proto.scope, DepositNonces::from_proto_msg(proto)?.into()) - } - ProtoGossipsubMsgBody::Sigs(proto) => { - (&proto.scope, DepositSigs::from_proto_msg(proto)?.into()) - } - }; - - let mut curr = Cursor::new(scope); - let scope = Decodable::consensus_decode(&mut curr) - .map_err(|err| DecodeError::new(err.to_string()))?; - - Ok(Self::Deposit { scope, kind }) - } - - /// Return content of the message for signing. - /// - /// Depending on the variant, concatenates serialized data of the variant and returns it as - /// a vec of bytes. - pub fn content(&self) -> Vec { - let mut content = Vec::new(); - - match &self { - GossipsubMsgKind::GenesisInfo(GenesisInfo { - pre_stake_outpoint, - checkpoint_pubkeys, - }) => { - content.extend(pre_stake_outpoint.vout.to_le_bytes()); - content.extend(pre_stake_outpoint.txid.to_byte_array()); - checkpoint_pubkeys.iter().for_each(|key| { - content.extend(key.serialize()); - }); - } - GossipsubMsgKind::Deposit { scope, kind } => { - content.extend(scope.to_byte_array()); - - match kind { - GossipsubMsgDepositKind::Setup(DepositSetup { payload }) => { - content.extend(payload.encode_to_vec()); - } - GossipsubMsgDepositKind::Nonces(DepositNonces { nonces }) => { - for nonce in nonces { - content.extend(nonce.serialize()); - } - } - GossipsubMsgDepositKind::Sigs(DepositSigs { partial_sigs }) => { - for sig in partial_sigs { - content.extend(sig.serialize()); - } - } - }; - } - }; - - content - } - - fn to_raw(&self) -> ProtoGossipsubMsgBody { - match self { - GossipsubMsgKind::GenesisInfo(info) => { - ProtoGossipsubMsgBody::GenesisInfo(ProtoGenesisInfo { - pre_stake_vout: info.pre_stake_outpoint.vout, - pre_stake_txid: info.pre_stake_outpoint.txid.to_byte_array().to_vec(), - checkpoint_pubkeys: info - .checkpoint_pubkeys - .iter() - .map(|k| k.serialize().to_vec()) - .collect(), - }) - } - GossipsubMsgKind::Deposit { scope, kind } => { - let scope = scope.to_byte_array().to_vec(); - match kind { - GossipsubMsgDepositKind::Setup(setup) => { - ProtoGossipsubMsgBody::Setup(ProtoDepositSetup { - scope, - payload: setup.payload.encode_to_vec(), - }) - } - GossipsubMsgDepositKind::Nonces(dep) => { - ProtoGossipsubMsgBody::Nonce(ProtoDepositNonces { - scope, - pub_nonces: dep - .nonces - .iter() - .map(|n| n.serialize().to_vec()) - .collect(), - }) - } - GossipsubMsgDepositKind::Sigs(dep) => { - ProtoGossipsubMsgBody::Sigs(ProtoDepositSigs { - scope, - partial_sigs: dep - .partial_sigs - .iter() - .map(|s| s.serialize().to_vec()) - .collect(), - }) - } - } - } - } - } - } - - #[derive(Clone, Debug)] - pub struct GossipsubMsg { - pub signature: Vec, - pub key: PublicKey, - pub kind: GossipsubMsgKind, - } - - impl GossipsubMsg - where - DepositSetupPayload: Message + Default + Clone, - { - pub fn from_bytes(bytes: &[u8]) -> Result { - let msg = super::GossipsubMsg::decode(bytes)?; - let Some(body) = msg.body else { - return Err(DecodeError::new("Message with empty body")); - }; - - let kind = GossipsubMsgKind::::from_msg_proto(&body)?; - let key = PublicKey::try_from_bytes(&msg.key) - .map_err(|err| DecodeError::new(err.to_string()))?; - - Ok(Self { - signature: msg.signature, - key, - kind, - }) - } - - pub fn into_raw(self) -> ProtoGossipMsg { - ProtoGossipMsg { - key: self.key.to_bytes().to_vec(), - signature: self.signature, - body: Some(self.kind.to_raw()), - } - } - - pub fn content(&self) -> Vec { - self.kind.content() - } - } -} diff --git a/crates/p2p/tests/gossipsub.rs b/crates/p2p/tests/gossipsub.rs index a5e64d7..a790068 100644 --- a/crates/p2p/tests/gossipsub.rs +++ b/crates/p2p/tests/gossipsub.rs @@ -11,11 +11,8 @@ use libp2p::{ PeerId, }; use snafu::whatever; -use strata_p2p::{ - events::EventKind, - swarm::handle::P2PHandle, - wire::{GossipsubMsg, GossipsubMsgDepositKind, GossipsubMsgKind}, -}; +use strata_p2p::{events::EventKind, swarm::handle::P2PHandle}; +use strata_p2p_wire::p2p::v1::{GossipsubMsg, GossipsubMsgDepositKind, GossipsubMsgKind}; use tokio::time::sleep; use tokio_util::{sync::CancellationToken, task::TaskTracker}; use tracing::info; diff --git a/crates/wire/Cargo.toml b/crates/wire/Cargo.toml new file mode 100644 index 0000000..991b23b --- /dev/null +++ b/crates/wire/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "strata-p2p-wire" +version = "0.1.0" +edition = "2024" + +[dependencies] +bitcoin.workspace = true +libp2p-identity.workspace = true +musig2.workspace = true +prost.workspace = true + +[build-dependencies] +prost-build = "0.13.4" diff --git a/crates/wire/build.rs b/crates/wire/build.rs new file mode 100644 index 0000000..5c501bd --- /dev/null +++ b/crates/wire/build.rs @@ -0,0 +1,12 @@ +use std::io::Result; + +fn main() -> Result<()> { + prost_build::compile_protos( + &[ + "../../proto/strata/bitvm2/p2p/v1/getmessage.proto", + "../../proto/strata/bitvm2/p2p/v1/gossipsub.proto", + ], + &["../../proto/"], + )?; + Ok(()) +} diff --git a/crates/wire/src/lib.rs b/crates/wire/src/lib.rs new file mode 100644 index 0000000..313eaae --- /dev/null +++ b/crates/wire/src/lib.rs @@ -0,0 +1 @@ +pub mod p2p; diff --git a/crates/wire/src/p2p/mod.rs b/crates/wire/src/p2p/mod.rs new file mode 100644 index 0000000..a3a6d96 --- /dev/null +++ b/crates/wire/src/p2p/mod.rs @@ -0,0 +1 @@ +pub mod v1; diff --git a/crates/wire/src/p2p/v1.rs b/crates/wire/src/p2p/v1.rs new file mode 100644 index 0000000..77e30f3 --- /dev/null +++ b/crates/wire/src/p2p/v1.rs @@ -0,0 +1,6 @@ +pub mod proto { + include!(concat!(env!("OUT_DIR"), "/strata.bitvm2.p2p.v1.rs")); +} + +pub(crate) mod typed; +pub use typed::*; diff --git a/crates/wire/src/p2p/v1/typed.rs b/crates/wire/src/p2p/v1/typed.rs new file mode 100644 index 0000000..4512a5c --- /dev/null +++ b/crates/wire/src/p2p/v1/typed.rs @@ -0,0 +1,349 @@ +use bitcoin::{ + OutPoint, XOnlyPublicKey, + consensus::Decodable, + hashes::{Hash, sha256}, + io::Cursor, +}; +use libp2p_identity::{PeerId, secp256k1::PublicKey}; +use musig2::{PartialSignature, PubNonce}; +use prost::{DecodeError, Message}; + +use super::proto::{ + DepositNoncesExchange as ProtoDepositNonces, DepositRequestKey, + DepositSetupExchange as ProtoDepositSetup, DepositSignaturesExchange as ProtoDepositSigs, + GenesisInfo as ProtoGenesisInfo, GenesisRequestKey, + GetMessageRequest as ProtoGetMessageRequest, GossipsubMsg as ProtoGossipMsg, + get_message_request::Body as ProtoGetMessageRequestBody, + gossipsub_msg::Body as ProtoGossipsubMsgBody, +}; + +pub enum GetMessageRequestExchangeKind { + Setup, + Nonces, + Signatures, +} + +/// Typed version of "get_message_request::GetMessageRequest". +#[allow(unused)] +pub enum GetMessageRequest { + Genesis { + operator_id: PeerId, + }, + ExchangeSession { + scope: sha256::Hash, + operator_id: PeerId, + kind: GetMessageRequestExchangeKind, + }, +} + +impl GetMessageRequest { + pub fn from_msg(msg: ProtoGetMessageRequest) -> Option { + let body = msg.body?; + + let (operator_id, deposit_txid, kind) = match body { + ProtoGetMessageRequestBody::DepositSetup(DepositRequestKey { scope, operator }) => { + (scope, operator, GetMessageRequestExchangeKind::Setup) + } + ProtoGetMessageRequestBody::DepositNonce(DepositRequestKey { scope, operator }) => { + (scope, operator, GetMessageRequestExchangeKind::Nonces) + } + ProtoGetMessageRequestBody::DepositSigs(DepositRequestKey { scope, operator }) => { + (scope, operator, GetMessageRequestExchangeKind::Signatures) + } + ProtoGetMessageRequestBody::GenesisInfo(GenesisRequestKey { operator }) => { + return Some(Self::Genesis { + operator_id: PeerId::from_bytes(&operator).ok()?, + }); + } + }; + + let operator_id = PeerId::from_bytes(&operator_id).ok()?; + let mut cur = Cursor::new(deposit_txid); + let scope = Decodable::consensus_decode(&mut cur).ok()?; + + Some(Self::ExchangeSession { + scope, + operator_id, + kind, + }) + } +} + +/// New deposit request appeared, and operators +/// exchanging setup data. +#[derive(Debug, Clone)] +pub struct DepositSetup { + /// Some arbitrary payload + pub payload: DepositSetupPayload, +} + +impl DepositSetup { + pub fn from_proto_msg(proto: &ProtoDepositSetup) -> Result { + let payload: DSP = Message::decode(proto.payload.as_ref())?; + + Ok(Self { payload }) + } +} + +/// Operators exchange nonces before signing. +#[derive(Debug, Clone)] +pub struct DepositNonces { + pub nonces: Vec, +} + +impl DepositNonces { + pub fn from_proto_msg(proto: &ProtoDepositNonces) -> Result { + let pub_nonces = proto + .pub_nonces + .iter() + .map(|bytes| PubNonce::from_bytes(bytes)) + .collect::, _>>() + .map_err(|err| DecodeError::new(err.to_string()))?; + + Ok(Self { nonces: pub_nonces }) + } +} + +/// Operators exchange signatures for transaction graph. +#[derive(Debug, Clone)] +pub struct DepositSigs { + pub partial_sigs: Vec, +} + +impl DepositSigs { + pub fn from_proto_msg(proto: &ProtoDepositSigs) -> Result { + let partial_sigs = proto + .partial_sigs + .iter() + .map(|bytes| PartialSignature::from_slice(bytes)) + .collect::, _>>() + .map_err(|err| DecodeError::new(err.to_string()))?; + + Ok(Self { partial_sigs }) + } +} + +/// Info provided during initial startup of nodes. +#[derive(Debug, Clone)] +pub struct GenesisInfo { + pub pre_stake_outpoint: OutPoint, + pub checkpoint_pubkeys: Vec, +} + +impl GenesisInfo { + pub fn from_proto_msg(proto: &ProtoGenesisInfo) -> Result { + let mut curr = Cursor::new(&proto.pre_stake_txid); + let txid = Decodable::consensus_decode(&mut curr) + .map_err(|err| DecodeError::new(err.to_string()))?; + + let outpoint = OutPoint::new(txid, proto.pre_stake_vout); + + let pubkeys = proto + .checkpoint_pubkeys + .iter() + .map(|bytes| XOnlyPublicKey::from_slice(bytes)) + .collect::, _>>() + .map_err(|err| DecodeError::new(err.to_string()))?; + + Ok(Self { + pre_stake_outpoint: outpoint, + checkpoint_pubkeys: pubkeys, + }) + } +} + +#[derive(Clone, Debug)] +pub enum GossipsubMsgDepositKind { + /// New deposit request appeared, and operators + /// exchanging setup data. + Setup(DepositSetup), + /// Operators exchange nonces before signing. + Nonces(DepositNonces), + /// Operators exchange signatures for transaction graph. + Sigs(DepositSigs), +} + +impl From + for GossipsubMsgDepositKind +{ + fn from(v: DepositSigs) -> Self { + Self::Sigs(v) + } +} + +impl From + for GossipsubMsgDepositKind +{ + fn from(v: DepositNonces) -> Self { + Self::Nonces(v) + } +} + +impl From> + for GossipsubMsgDepositKind +{ + fn from(v: DepositSetup) -> Self { + Self::Setup(v) + } +} + +#[derive(Clone, Debug)] +pub enum GossipsubMsgKind { + /// Operators exchange + GenesisInfo(GenesisInfo), + Deposit { + scope: sha256::Hash, + kind: GossipsubMsgDepositKind, + }, +} + +impl GossipsubMsgKind { + pub fn from_msg_proto(proto: &ProtoGossipsubMsgBody) -> Result { + let (scope, kind) = match proto { + ProtoGossipsubMsgBody::GenesisInfo(proto) => { + return Ok(Self::GenesisInfo(GenesisInfo::from_proto_msg(proto)?)); + } + ProtoGossipsubMsgBody::Setup(proto) => { + (&proto.scope, DepositSetup::from_proto_msg(proto)?.into()) + } + ProtoGossipsubMsgBody::Nonce(proto) => { + (&proto.scope, DepositNonces::from_proto_msg(proto)?.into()) + } + ProtoGossipsubMsgBody::Sigs(proto) => { + (&proto.scope, DepositSigs::from_proto_msg(proto)?.into()) + } + }; + + let mut curr = Cursor::new(scope); + let scope = Decodable::consensus_decode(&mut curr) + .map_err(|err| DecodeError::new(err.to_string()))?; + + Ok(Self::Deposit { scope, kind }) + } + + /// Return content of the message for signing. + /// + /// Depending on the variant, concatenates serialized data of the variant and returns it as + /// a vec of bytes. + pub fn content(&self) -> Vec { + let mut content = Vec::new(); + + match &self { + GossipsubMsgKind::GenesisInfo(GenesisInfo { + pre_stake_outpoint, + checkpoint_pubkeys, + }) => { + content.extend(pre_stake_outpoint.vout.to_le_bytes()); + content.extend(pre_stake_outpoint.txid.to_byte_array()); + checkpoint_pubkeys.iter().for_each(|key| { + content.extend(key.serialize()); + }); + } + GossipsubMsgKind::Deposit { scope, kind } => { + content.extend(scope.to_byte_array()); + + match kind { + GossipsubMsgDepositKind::Setup(DepositSetup { payload }) => { + content.extend(payload.encode_to_vec()); + } + GossipsubMsgDepositKind::Nonces(DepositNonces { nonces }) => { + for nonce in nonces { + content.extend(nonce.serialize()); + } + } + GossipsubMsgDepositKind::Sigs(DepositSigs { partial_sigs }) => { + for sig in partial_sigs { + content.extend(sig.serialize()); + } + } + }; + } + }; + + content + } + + fn to_raw(&self) -> ProtoGossipsubMsgBody { + match self { + GossipsubMsgKind::GenesisInfo(info) => { + ProtoGossipsubMsgBody::GenesisInfo(ProtoGenesisInfo { + pre_stake_vout: info.pre_stake_outpoint.vout, + pre_stake_txid: info.pre_stake_outpoint.txid.to_byte_array().to_vec(), + checkpoint_pubkeys: info + .checkpoint_pubkeys + .iter() + .map(|k| k.serialize().to_vec()) + .collect(), + }) + } + GossipsubMsgKind::Deposit { scope, kind } => { + let scope = scope.to_byte_array().to_vec(); + match kind { + GossipsubMsgDepositKind::Setup(setup) => { + ProtoGossipsubMsgBody::Setup(ProtoDepositSetup { + scope, + payload: setup.payload.encode_to_vec(), + }) + } + GossipsubMsgDepositKind::Nonces(dep) => { + ProtoGossipsubMsgBody::Nonce(ProtoDepositNonces { + scope, + pub_nonces: dep.nonces.iter().map(|n| n.serialize().to_vec()).collect(), + }) + } + GossipsubMsgDepositKind::Sigs(dep) => { + ProtoGossipsubMsgBody::Sigs(ProtoDepositSigs { + scope, + partial_sigs: dep + .partial_sigs + .iter() + .map(|s| s.serialize().to_vec()) + .collect(), + }) + } + } + } + } + } +} + +#[derive(Clone, Debug)] +pub struct GossipsubMsg { + pub signature: Vec, + pub key: PublicKey, + pub kind: GossipsubMsgKind, +} + +impl GossipsubMsg +where + DepositSetupPayload: Message + Default + Clone, +{ + pub fn from_bytes(bytes: &[u8]) -> Result { + let msg = ProtoGossipMsg::decode(bytes)?; + let Some(body) = msg.body else { + return Err(DecodeError::new("Message with empty body")); + }; + + let kind = GossipsubMsgKind::::from_msg_proto(&body)?; + let key = + PublicKey::try_from_bytes(&msg.key).map_err(|err| DecodeError::new(err.to_string()))?; + + Ok(Self { + signature: msg.signature, + key, + kind, + }) + } + + pub fn into_raw(self) -> ProtoGossipMsg { + ProtoGossipMsg { + key: self.key.to_bytes().to_vec(), + signature: self.signature, + body: Some(self.kind.to_raw()), + } + } + + pub fn content(&self) -> Vec { + self.kind.content() + } +} diff --git a/crates/p2p/proto/strata/bitvm2/p2p/v1/getmessage.proto b/proto/strata/bitvm2/p2p/v1/getmessage.proto similarity index 93% rename from crates/p2p/proto/strata/bitvm2/p2p/v1/getmessage.proto rename to proto/strata/bitvm2/p2p/v1/getmessage.proto index 5977a2c..84cbdaa 100644 --- a/crates/p2p/proto/strata/bitvm2/p2p/v1/getmessage.proto +++ b/proto/strata/bitvm2/p2p/v1/getmessage.proto @@ -27,5 +27,5 @@ message GetMessageRequestBatch { } message GetMessageResponse { - repeated GossipsubMsg msg = 1; + repeated GossipsubMsg msg = 1; } diff --git a/proto/strata/bitvm2/p2p/v1/gossipsub.proto b/proto/strata/bitvm2/p2p/v1/gossipsub.proto new file mode 100644 index 0000000..7507771 --- /dev/null +++ b/proto/strata/bitvm2/p2p/v1/gossipsub.proto @@ -0,0 +1,48 @@ +syntax = "proto3"; + +package strata.bitvm2.p2p.v1; + +message GenesisInfo { + // output number of pre stake tx + uint32 pre_stake_vout = 1; + // transaction hash of pre stake tx + bytes pre_stake_txid = 2; + // keys Y_{i, j} + repeated bytes checkpoint_pubkeys = 3; +} + +message DepositSetupExchange { + // 32-byte hash of some unique to deposit data + bytes scope = 1; + // Some arbitrary payload which depends on the implementation. + bytes payload = 2; +} + +message DepositNoncesExchange { + // 32-byte hash of some unique to deposit data + bytes scope = 1; + // Public nonces for each transaction + repeated bytes pub_nonces = 2; +} + +message DepositSignaturesExchange { + // 32-byte hash of some unique to deposit data + bytes scope = 1; + // Partial signatures for each transaction + repeated bytes partial_sigs = 2; +} + +message GossipsubMsg { + oneof body { + GenesisInfo genesis_info = 1; + DepositSetupExchange setup = 2; + DepositNoncesExchange nonce = 3; + DepositSignaturesExchange sigs = 4; + } + // Public key of the peer. The hash of this key must be the same as + // source PeerId of this message. + bytes key = 10; + // Signature of concatenated content of body (without protobuf + // serialization). + bytes signature = 11; +}