Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit c036205

Browse files
authoredMar 5, 2025··
STR-1108: More Messages (#37)
* feat: command to add a peer * refactor: tests * chore: rust lints * fix!: critical bug... * test: fixed the bug * tests: refactor * test: connect peers (does not work) * feat: update messages for the new stake chain API * chore: add rust_2018_idioms lints * chore(clippy): expect(dead_code) instead of allow * doc: document DepositInfo fields and few more nitbits * feat!: wots_pks into deposit and withdraw data * feat!: wots_pks into deposit data * feat!: add x-only pk into deposit data * doc: reimburse instead of refund
1 parent 0f457ed commit c036205

26 files changed

+1679
-865
lines changed
 

‎crates/db/Cargo.toml

+7
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@ name = "strata-p2p-db"
33
version = "0.1.0"
44
edition = "2021"
55

6+
[lints]
7+
rust.missing_debug_implementations = "warn"
8+
rust.unreachable_pub = "warn"
9+
rust.unused_crate_dependencies = "deny"
10+
rust.unused_must_use = "deny"
11+
rust.rust_2018_idioms = { level = "deny", priority = -1 }
12+
613
[dependencies]
714
async-trait.workspace = true
815
bitcoin.workspace = true

‎crates/db/src/lib.rs

+32-10
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
#![feature(generic_const_exprs)] // but necessary for using const generic bounds in
44

55
use async_trait::async_trait;
6-
use bitcoin::{OutPoint, XOnlyPublicKey};
6+
use bitcoin::{hashes::sha256, Txid, XOnlyPublicKey};
77
use libp2p_identity::PeerId;
88
use musig2::{PartialSignature, PubNonce};
9-
use serde::{de::DeserializeOwned, Serialize};
10-
use strata_p2p_types::{OperatorPubKey, Scope, SessionId, StakeChainId, StakeData, WotsPublicKeys};
9+
use serde::{de::DeserializeOwned, Deserialize, Serialize};
10+
use strata_p2p_types::{OperatorPubKey, Scope, SessionId, StakeChainId, WotsPublicKeys};
1111
use thiserror::Error;
1212

1313
mod prost_serde;
@@ -29,7 +29,7 @@ impl From<serde_json::Error> for RepositoryError {
2929
}
3030
}
3131

32-
#[derive(serde::Serialize, serde::Deserialize, Debug)]
32+
#[derive(Debug, Serialize, Deserialize)]
3333
pub struct AuthenticatedEntry<T> {
3434
pub entry: T,
3535
pub signature: Vec<u8>,
@@ -42,12 +42,11 @@ pub type PartialSignaturesEntry = AuthenticatedEntry<Vec<PartialSignature>>;
4242
/// A [`Vec`] of [`PubNonce`]s.
4343
pub type NoncesEntry = AuthenticatedEntry<Vec<PubNonce>>;
4444

45-
/// A big tuple of:
45+
/// A tuple of:
4646
///
47-
/// 1. [`OutPoint`] of the pre-stake transaction.
48-
/// 2. [`Vec`] of Schnorr verification keys `Y_{i,j}` for blocks `j = 0..M`.
49-
/// 3. [`Vec`] of [`StakeData`]s.
50-
pub type StakeChainEntry = AuthenticatedEntry<(OutPoint, Vec<XOnlyPublicKey>, Vec<StakeData>)>;
47+
/// 1. [`Txid`] of the pre-stake transaction.
48+
/// 2. vout index of the pre-stake transaction.
49+
pub type StakeChainEntry = AuthenticatedEntry<(Txid, u32)>;
5150

5251
/// Basic functionality to get, set, and delete values from a Database.
5352
#[async_trait]
@@ -266,9 +265,32 @@ pub trait RepositoryExt: Repository {
266265

267266
impl<T> RepositoryExt for T where T: Repository {}
268267

269-
#[derive(serde::Serialize, serde::Deserialize, Debug)]
268+
/// Information that is gossiped or requested by other nodes when a deposit occurs.
269+
#[derive(Debug, Serialize, Deserialize)]
270270
pub struct DepositSetupEntry {
271+
/// [`sha256::Hash`] hash of the deposit data.
272+
pub hash: sha256::Hash,
273+
274+
/// Funding transaction ID.
275+
///
276+
/// Used to cover the dust outputs in the transaction graph connectors.
277+
pub funding_txid: Txid,
278+
279+
/// Funding transaction output index.
280+
///
281+
/// Used to cover the dust outputs in the transaction graph connectors.
282+
pub funding_vout: u32,
283+
284+
/// Operator's X-only public key to construct a P2TR address to reimburse the
285+
/// operator for a valid withdraw fulfillment.
286+
pub operator_pk: XOnlyPublicKey,
287+
288+
/// Winternitz One-Time Signature (WOTS) public keys shared in a deposit.
271289
pub wots_pks: WotsPublicKeys,
290+
291+
/// Signature of the Operator's message using his [`OperatorPubKey`].
272292
pub signature: Vec<u8>,
293+
294+
/// The Operator's public key that the message came from.
273295
pub key: OperatorPubKey,
274296
}

‎crates/db/src/prost_serde.rs

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
//! Custom [`serde`] serializers and deserializers for [`prost`] messages.
2-
#![allow(dead_code)]
32
43
use serde::{de::Error, Deserialize, Deserializer, Serializer};
54

65
/// Serializes a [`prost`] message into a byte array.
7-
pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
6+
#[allow(unfulfilled_lint_expectations)]
7+
#[expect(dead_code)]
8+
pub(crate) fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
89
where
910
T: prost::Message,
1011
S: Serializer,
@@ -13,7 +14,9 @@ where
1314
}
1415

1516
/// Deserializes a [`prost`] message from a byte array.
16-
pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
17+
#[allow(unfulfilled_lint_expectations)]
18+
#[expect(dead_code)]
19+
pub(crate) fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
1720
where
1821
D: Deserializer<'de>,
1922
T: prost::Message + Default,

‎crates/db/src/sled.rs

+16-11
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use tracing::warn;
1111
use super::{DBResult, Repository, RepositoryError};
1212

1313
/// Thread-safe wrapper for [`Db`] with a [`ThreadPool`] for async operations.
14-
#[derive(Clone)]
14+
#[derive(Debug, Clone)]
1515
pub struct AsyncDB {
1616
/// Thread pool used for async operations.
1717
pool: ThreadPool,
@@ -134,12 +134,11 @@ mod tests {
134134
use rand::{thread_rng, Rng, RngCore};
135135
use secp256k1::{All, Keypair, Secp256k1};
136136
use strata_p2p_types::{
137-
OperatorPubKey, SessionId, StakeChainId, Wots160PublicKey, Wots256PublicKey,
137+
OperatorPubKey, SessionId, StakeChainId, StakeData, Wots160PublicKey, Wots256PublicKey,
138138
};
139139

140140
use crate::{
141141
sled::AsyncDB, NoncesEntry, PartialSignaturesEntry, RepositoryExt, StakeChainEntry,
142-
StakeData,
143142
};
144143

145144
#[tokio::test]
@@ -201,10 +200,8 @@ mod tests {
201200

202201
let stake_chain_id = StakeChainId::hash(b"stake_chain_id");
203202
let outpoint = OutPoint::null();
204-
let checkpoint_pubkeys = vec![generate_random_xonly(&secp); 10_000];
205-
let stake_data = vec![generate_random_stake_data(); 10_000];
206203
let entry = StakeChainEntry {
207-
entry: (outpoint, checkpoint_pubkeys.clone(), stake_data.clone()),
204+
entry: (outpoint.txid, outpoint.vout),
208205
signature: vec![],
209206
key: operator_pk.clone(),
210207
};
@@ -214,16 +211,15 @@ mod tests {
214211
.unwrap();
215212

216213
let StakeChainEntry {
217-
entry: (got_op, got_keys, got_stake_data),
214+
entry: (got_txid, got_vout),
218215
..
219216
} = db
220217
.get_stake_chain_info(&operator_pk, &stake_chain_id)
221218
.await
222219
.unwrap()
223220
.unwrap();
224-
assert_eq!(got_op, outpoint);
225-
assert_eq!(got_keys, checkpoint_pubkeys);
226-
assert_eq!(got_stake_data, stake_data);
221+
assert_eq!(got_txid, outpoint.txid);
222+
assert_eq!(got_vout, outpoint.vout);
227223

228224
let retrieved_pub_nonces = db
229225
.get_pub_nonces(&operator_pk, session_id)
@@ -236,13 +232,14 @@ mod tests {
236232
inner(&db).await
237233
}
238234

235+
#[expect(dead_code)]
239236
fn generate_random_xonly(ctx: &Secp256k1<All>) -> XOnlyPublicKey {
240237
let (_seckey, pubkey) = ctx.generate_keypair(&mut thread_rng());
241238
let (xonly, _parity) = pubkey.x_only_public_key();
242239
xonly
243240
}
244241

245-
#[allow(dead_code)]
242+
#[expect(dead_code)]
246243
fn generate_random_wots160() -> Wots160PublicKey {
247244
let mut rng = thread_rng();
248245
let mut wots = [[0; 20]; Wots160PublicKey::SIZE];
@@ -252,6 +249,8 @@ mod tests {
252249
Wots160PublicKey::new(wots)
253250
}
254251

252+
#[allow(unfulfilled_lint_expectations)]
253+
#[expect(dead_code)]
255254
fn generate_random_wots256() -> Wots256PublicKey {
256255
let mut rng = thread_rng();
257256
let mut wots = [[0; 20]; Wots256PublicKey::SIZE];
@@ -261,20 +260,26 @@ mod tests {
261260
Wots256PublicKey::new(wots)
262261
}
263262

263+
#[allow(unfulfilled_lint_expectations)]
264+
#[expect(dead_code)]
264265
fn generate_random_hash() -> sha256::Hash {
265266
let mut rng = thread_rng();
266267
let mut hash = [0; 32];
267268
rng.fill_bytes(&mut hash);
268269
sha256::Hash::from_byte_array(hash)
269270
}
270271

272+
#[allow(unfulfilled_lint_expectations)]
273+
#[expect(dead_code)]
271274
fn generate_random_outpoint() -> OutPoint {
272275
let mut rng = thread_rng();
273276
let txid = Txid::from_slice(&generate_random_hash().to_byte_array()).unwrap();
274277
let vout = rng.gen_range(0..u32::MAX);
275278
OutPoint::new(txid, vout)
276279
}
277280

281+
#[allow(unfulfilled_lint_expectations)]
282+
#[expect(dead_code)]
278283
fn generate_random_stake_data() -> StakeData {
279284
StakeData::new(
280285
generate_random_wots256(),

‎crates/p2p/Cargo.toml

+9-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@ name = "strata-p2p"
33
version = "0.1.0"
44
edition = "2021"
55

6+
[lints]
7+
rust.missing_debug_implementations = "warn"
8+
rust.unreachable_pub = "warn"
9+
rust.unused_crate_dependencies = "deny"
10+
rust.unused_must_use = "deny"
11+
rust.rust_2018_idioms = { level = "deny", priority = -1 }
12+
613
[dependencies]
714
libp2p = { version = "0.54.1", features = [
815
"noise",
@@ -22,7 +29,6 @@ futures.workspace = true
2229
musig2 = { workspace = true, features = ["serde"] }
2330
prost.workspace = true
2431
thiserror.workspace = true
25-
threadpool.workspace = true
2632
tokio = { workspace = true, features = ["macros", "time"] }
2733
tokio-util.workspace = true
2834
tracing.workspace = true
@@ -33,8 +39,10 @@ strata-p2p-types.path = "../types"
3339
strata-p2p-wire.path = "../wire"
3440

3541
[dev-dependencies]
42+
bitcoin = { workspace = true, features = ["serde", "rand"] }
3643
sled = "0.34.7"
3744
tokio = { workspace = true, features = ["rt", "rt-multi-thread"] }
3845
tokio-util.workspace = true
46+
threadpool.workspace = true
3947
tracing-subscriber.workspace = true
4048
anyhow = "1.0.95"

‎crates/p2p/src/commands.rs

+60-26
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
//! Commands for P2P implementation from operator implementation.
22
3-
use bitcoin::{OutPoint, XOnlyPublicKey};
4-
use libp2p::identity::secp256k1;
3+
use bitcoin::{hashes::sha256, Txid, XOnlyPublicKey};
4+
use libp2p::{identity::secp256k1, Multiaddr, PeerId};
55
use musig2::{PartialSignature, PubNonce};
6-
use strata_p2p_types::{OperatorPubKey, Scope, SessionId, StakeChainId, StakeData, WotsPublicKeys};
7-
use strata_p2p_wire::p2p::v1::{
8-
GetMessageRequest, GossipsubMsg, StakeChainExchange, UnsignedGossipsubMsg,
9-
};
6+
use strata_p2p_types::{OperatorPubKey, Scope, SessionId, StakeChainId, WotsPublicKeys};
7+
use strata_p2p_wire::p2p::v1::{GetMessageRequest, GossipsubMsg, UnsignedGossipsubMsg};
108

119
/// Ask P2P implementation to distribute some data across network.
10+
#[expect(clippy::large_enum_variant)]
1211
#[derive(Debug, Clone)]
1312
pub enum Command {
1413
/// Publishes message through gossip sub network of peers.
@@ -19,6 +18,9 @@ pub enum Command {
1918

2019
/// Cleans session, scopes from internal DB.
2120
CleanStorage(CleanStorageCommand),
21+
22+
/// Connects to a peer, whitelists peer, and adds peer to the swarm.
23+
ConnectToPeer(ConnectToPeerCommand),
2224
}
2325

2426
#[derive(Debug, Clone)]
@@ -35,21 +37,18 @@ pub struct PublishMessage {
3537

3638
/// Types of unsigned messages.
3739
#[derive(Debug, Clone)]
40+
#[expect(clippy::large_enum_variant)]
3841
pub enum UnsignedPublishMessage {
3942
/// Stake Chain information.
4043
StakeChainExchange {
4144
/// 32-byte hash of some unique to stake chain data.
4245
stake_chain_id: StakeChainId,
4346

44-
/// [`OutPoint`] of the pre-stake transaction.
45-
pre_stake_outpoint: OutPoint,
46-
47-
/// Each operator `i = 0..N` sends a message with his Schnorr verification keys `Y_{i,j}`
48-
/// for blocks `j = 0..M`.
49-
checkpoint_pubkeys: Vec<XOnlyPublicKey>,
47+
/// [`Txid`] of the pre-stake transaction.
48+
pre_stake_txid: Txid,
5049

51-
/// Stake data for a whole Stake Chain.
52-
stake_data: Vec<StakeData>,
50+
/// vout index of the pre-stake transaction.
51+
pre_stake_vout: u32,
5352
},
5453

5554
/// Deposit setup.
@@ -59,7 +58,24 @@ pub enum UnsignedPublishMessage {
5958
/// The deposit [`Scope`].
6059
scope: Scope,
6160

62-
/// Payload, WOTS PKs.
61+
/// [`sha256::Hash`] hash of the deposit data.
62+
hash: sha256::Hash,
63+
64+
/// Funding transaction ID.
65+
///
66+
/// Used to cover the dust outputs in the transaction graph connectors.
67+
funding_txid: Txid,
68+
69+
/// Funding transaction output index.
70+
///
71+
/// Used to cover the dust outputs in the transaction graph connectors.
72+
funding_vout: u32,
73+
74+
/// Operator's X-only public key to construct a P2TR address to reimburse the
75+
/// operator for a valid withdraw fulfillment.
76+
operator_pk: XOnlyPublicKey,
77+
78+
/// Winternitz One-Time Signature (WOTS) public keys shared in a deposit.
6379
wots_pks: WotsPublicKeys,
6480
},
6581

@@ -115,21 +131,29 @@ impl From<UnsignedPublishMessage> for UnsignedGossipsubMsg {
115131
match value {
116132
UnsignedPublishMessage::StakeChainExchange {
117133
stake_chain_id,
118-
pre_stake_outpoint,
119-
checkpoint_pubkeys,
120-
stake_data,
134+
pre_stake_txid,
135+
pre_stake_vout,
121136
} => UnsignedGossipsubMsg::StakeChainExchange {
122137
stake_chain_id,
123-
info: StakeChainExchange {
124-
checkpoint_pubkeys,
125-
pre_stake_outpoint,
126-
stake_data,
127-
},
138+
pre_stake_txid,
139+
pre_stake_vout,
128140
},
129141

130-
UnsignedPublishMessage::DepositSetup { scope, wots_pks } => {
131-
UnsignedGossipsubMsg::DepositSetup { scope, wots_pks }
132-
}
142+
UnsignedPublishMessage::DepositSetup {
143+
scope,
144+
hash,
145+
funding_txid,
146+
funding_vout,
147+
operator_pk,
148+
wots_pks,
149+
} => UnsignedGossipsubMsg::DepositSetup {
150+
scope,
151+
hash,
152+
funding_txid,
153+
funding_vout,
154+
operator_pk,
155+
wots_pks,
156+
},
133157

134158
UnsignedPublishMessage::Musig2NoncesExchange {
135159
session_id,
@@ -156,6 +180,16 @@ impl From<PublishMessage> for Command {
156180
}
157181
}
158182

183+
/// Connects to a peer.
184+
#[derive(Debug, Clone)]
185+
pub struct ConnectToPeerCommand {
186+
/// Peer ID.
187+
pub peer_id: PeerId,
188+
189+
/// Peer address.
190+
pub peer_addr: Multiaddr,
191+
}
192+
159193
/// Commands P2P to clean entries from internal key-value storage by
160194
/// session IDs, scopes and operator pubkeys.
161195
#[derive(Debug, Clone)]

‎crates/p2p/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@
33
pub mod commands;
44
pub mod events;
55
pub mod swarm;
6+
7+
#[cfg(test)]
8+
mod tests;

‎crates/p2p/src/swarm/behavior.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@ use super::{codec, TOPIC};
2222

2323
/// Alias for request-response behaviour with messages serialized by using
2424
/// homebrewed codec implementation.
25-
pub type RequestResponseProtoBehaviour<Req, Resp> = RequestResponse<codec::Codec<Req, Resp>>;
25+
pub(crate) type RequestResponseProtoBehaviour<Req, Resp> = RequestResponse<codec::Codec<Req, Resp>>;
2626

2727
/// Composite behaviour which consists of other ones used by swarm in P2P
2828
/// implementation.
29+
#[expect(missing_debug_implementations)]
2930
#[derive(NetworkBehaviour)]
3031
pub struct Behaviour {
3132
/// Gossipsub - pub/sub model for messages distribution.

‎crates/p2p/src/swarm/codec.rs

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const RESPONSE_SIZE_MAXIMUM: u64 = 10 * 1024 * 1024;
2121
/// A [`Codec`] defines the request and response types
2222
/// for a request-response [`Behaviour`](super::Behaviour) protocol or
2323
/// protocol family and how they are encoded/decoded on an I/O stream.
24+
#[derive(Debug)]
2425
pub struct Codec<Req, Resp> {
2526
/// Phatom data for the tuple request-response.
2627
phantom: PhantomData<(Req, Resp)>,

‎crates/p2p/src/swarm/mod.rs

+70-46
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ static TOPIC: LazyLock<Sha256Topic> = LazyLock::new(|| Sha256Topic::new("bitvm2"
5454
const PROTOCOL_NAME: &str = "/strata-bitvm2";
5555

5656
/// Configuration options for [`P2P`].
57+
#[derive(Debug, Clone)]
5758
pub struct P2PConfig {
5859
/// [`Keypair`] used as [`PeerId`].
5960
pub keypair: Keypair,
@@ -75,6 +76,7 @@ pub struct P2PConfig {
7576
}
7677

7778
/// Implementation of p2p protocol for BitVM2 data exchange.
79+
#[expect(missing_debug_implementations)]
7880
pub struct P2P<Repository> {
7981
/// The swarm that handles the networking.
8082
swarm: Swarm<Behaviour>,
@@ -186,7 +188,7 @@ impl<DB: RepositoryExt> P2P<DB> {
186188
let ConnectedPoint::Dialer { address, .. } = endpoint else {
187189
continue;
188190
};
189-
debug!(%address, "Establshed connection with peer");
191+
debug!(%address, "Established connection with peer");
190192
is_not_connected.remove(&address);
191193
}
192194
_ => {}
@@ -278,7 +280,7 @@ impl<DB: RepositoryExt> P2P<DB> {
278280
let msg = match GossipsubMsg::from_bytes(&message.data) {
279281
Ok(msg) => msg,
280282
Err(err) => {
281-
debug!(%err, "Got invalid message from peer, rejecting it.");
283+
error!(%err, "Got invalid message from peer, rejecting it.");
282284
// no error should appear in case of message rejection
283285
let _ = self
284286
.swarm
@@ -298,7 +300,7 @@ impl<DB: RepositoryExt> P2P<DB> {
298300
.source
299301
.expect("Message must have author as ValidationMode set to Permissive");
300302
if let Err(err) = self.validate_gossipsub_msg(&msg) {
301-
debug!(reason=%err, "Message failed validation.");
303+
error!(reason=%err, "Message failed validation.");
302304
// no error should appear in case of message rejection
303305
let _ = self
304306
.swarm
@@ -331,7 +333,7 @@ impl<DB: RepositoryExt> P2P<DB> {
331333
MessageAcceptance::Accept,
332334
)
333335
.inspect_err(
334-
|err| debug!(%err, ?event, "failed to propagate accepted message further"),
336+
|err| error!(%err, ?event, "failed to propagate accepted message further"),
335337
);
336338

337339
// Do not broadcast new event to "handles" if it's not new.
@@ -353,28 +355,36 @@ impl<DB: RepositoryExt> P2P<DB> {
353355
match &msg.unsigned {
354356
v1::UnsignedGossipsubMsg::StakeChainExchange {
355357
stake_chain_id,
356-
info,
358+
pre_stake_txid,
359+
pre_stake_vout,
357360
} => {
358361
self.db
359362
.set_stake_chain_info_if_not_exists(
360363
*stake_chain_id,
361364
StakeChainEntry {
362-
entry: (
363-
info.pre_stake_outpoint,
364-
info.checkpoint_pubkeys.clone(),
365-
info.stake_data.clone(),
366-
),
365+
entry: (*pre_stake_txid, *pre_stake_vout),
367366
signature: msg.signature.clone(),
368367
key: msg.key.clone(),
369368
},
370369
)
371370
.await
372371
}
373-
v1::UnsignedGossipsubMsg::DepositSetup { scope, wots_pks } => {
372+
v1::UnsignedGossipsubMsg::DepositSetup {
373+
scope,
374+
hash,
375+
funding_txid,
376+
funding_vout,
377+
operator_pk,
378+
wots_pks,
379+
} => {
374380
self.db
375381
.set_deposit_setup_if_not_exists(
376382
*scope,
377383
DepositSetupEntry {
384+
hash: *hash,
385+
funding_txid: *funding_txid,
386+
funding_vout: *funding_vout,
387+
operator_pk: *operator_pk,
378388
wots_pks: wots_pks.clone(),
379389
signature: msg.signature.clone(),
380390
key: msg.key.clone(),
@@ -426,7 +436,7 @@ impl<DB: RepositoryExt> P2P<DB> {
426436
.behaviour_mut()
427437
.gossipsub
428438
.publish(TOPIC.hash(), msg.into_raw().encode_to_vec())
429-
.inspect_err(|err| debug!(%err, "Failed to publish msg through gossipsub"));
439+
.inspect_err(|err| error!(%err, "Failed to publish msg through gossipsub"));
430440

431441
Ok(())
432442
}
@@ -482,6 +492,27 @@ impl<DB: RepositoryExt> P2P<DB> {
482492
self.db.delete_deposit_setups(&operator_scope_pairs).await?;
483493
}
484494

495+
Ok(())
496+
}
497+
Command::ConnectToPeer(connect_to_peer_command) => {
498+
// Whitelist peer
499+
self.config.allowlist.push(connect_to_peer_command.peer_id);
500+
self.config
501+
.connect_to
502+
.push(connect_to_peer_command.peer_addr.clone());
503+
504+
// Add peer to swarm
505+
self.swarm.add_peer_address(
506+
connect_to_peer_command.peer_id,
507+
connect_to_peer_command.peer_addr.clone(),
508+
);
509+
510+
// Connect to peer
511+
let _ = self
512+
.swarm
513+
.dial(connect_to_peer_command.peer_addr)
514+
.inspect_err(|err| error!(%err, "Failed to connect to peer"));
515+
485516
Ok(())
486517
}
487518
}
@@ -495,21 +526,22 @@ impl<DB: RepositoryExt> P2P<DB> {
495526
) -> P2PResult<()> {
496527
match event {
497528
RequestResponseEvent::Message { peer, message } => {
529+
debug!(%peer, ?message, "Received message");
498530
self.handle_message_event(peer, message).await?
499531
}
500532
RequestResponseEvent::OutboundFailure {
501533
peer,
502534
request_id,
503535
error,
504536
} => {
505-
debug!(%peer, %error, %request_id, "Outbound failure")
537+
error!(%peer, %error, %request_id, "Outbound failure")
506538
}
507539
RequestResponseEvent::InboundFailure {
508540
peer,
509541
request_id,
510542
error,
511543
} => {
512-
debug!(%peer, %error, %request_id, "Inbound failure")
544+
error!(%peer, %error, %request_id, "Inbound failure")
513545
}
514546
RequestResponseEvent::ResponseSent { peer, request_id } => {
515547
debug!(%peer, %request_id, "Response sent")
@@ -534,14 +566,14 @@ impl<DB: RepositoryExt> P2P<DB> {
534566
let empty_response = GetMessageResponse { msg: vec![] };
535567

536568
let Ok(req) = v1::GetMessageRequest::from_msg(request) else {
537-
debug!(%peer_id, "Peer sent invalid get message request, disconnecting it");
569+
error!(%peer_id, "Peer sent invalid get message request, disconnecting it");
538570
let _ = self.swarm.disconnect_peer_id(peer_id);
539571
let _ = self
540572
.swarm
541573
.behaviour_mut()
542574
.request_response
543575
.send_response(channel, empty_response)
544-
.inspect_err(|_| debug!("Failed to send response"));
576+
.inspect_err(|_| error!("Failed to send response"));
545577

546578
return Ok(());
547579
};
@@ -553,7 +585,7 @@ impl<DB: RepositoryExt> P2P<DB> {
553585
.behaviour_mut()
554586
.request_response
555587
.send_response(channel, empty_response)
556-
.inspect_err(|_| debug!("Failed to send response"));
588+
.inspect_err(|_| error!("Failed to send response"));
557589

558590
return Ok(());
559591
};
@@ -565,15 +597,15 @@ impl<DB: RepositoryExt> P2P<DB> {
565597
.behaviour_mut()
566598
.request_response
567599
.send_response(channel, response)
568-
.inspect_err(|_| debug!("Failed to send response"));
600+
.inspect_err(|_| error!("Failed to send response"));
569601
}
570602

571603
request_response::Message::Response {
572604
request_id,
573605
response,
574606
} => {
575607
if response.msg.is_empty() {
576-
debug!(%request_id, "Received empty response");
608+
error!(%request_id, ?response, "Received empty response");
577609
return Ok(());
578610
}
579611

@@ -586,12 +618,12 @@ impl<DB: RepositoryExt> P2P<DB> {
586618
let msg = match GossipsubMsg::from_proto(msg.clone()) {
587619
Ok(msg) => msg,
588620
Err(err) => {
589-
debug!(%peer_id, reason=%err, "Peer sent invalid message");
621+
error!(%peer_id, reason=%err, "Peer sent invalid message");
590622
continue;
591623
}
592624
};
593625
if let Err(err) = self.validate_gossipsub_msg(&msg) {
594-
debug!(%peer_id, reason=%err, "Message failed validation");
626+
error!(%peer_id, reason=%err, "Message failed validation");
595627
continue;
596628
}
597629

@@ -621,20 +653,8 @@ impl<DB: RepositoryExt> P2P<DB> {
621653
info.map(|v| proto::GossipsubMsg {
622654
body: Some(Body::StakeChain(proto::StakeChainExchange {
623655
stake_chain_id: stake_chain_id.to_vec(),
624-
pre_stake_vout: v.entry.0.vout,
625-
pre_stake_txid: v.entry.0.txid.to_byte_array().to_vec(),
626-
checkpoint_pubkeys: v
627-
.entry
628-
.1
629-
.iter()
630-
.map(|k| k.serialize().to_vec())
631-
.collect(),
632-
stake_data: v
633-
.entry
634-
.2
635-
.iter()
636-
.map(|w| w.to_flattened_bytes().to_vec())
637-
.collect(),
656+
pre_stake_txid: v.entry.0.to_byte_array().to_vec(),
657+
pre_stake_vout: v.entry.1,
638658
})),
639659
signature: v.signature,
640660
key: v.key.into(),
@@ -646,6 +666,10 @@ impl<DB: RepositoryExt> P2P<DB> {
646666
setup.map(|v| proto::GossipsubMsg {
647667
body: Some(Body::Setup(proto::DepositSetupExchange {
648668
scope: scope.to_vec(),
669+
hash: v.hash.to_byte_array().to_vec(),
670+
funding_txid: v.funding_txid.to_byte_array().to_vec(),
671+
funding_vout: v.funding_vout,
672+
operator_pk: v.operator_pk.serialize().to_vec(),
649673
wots_pks: v.wots_pks.to_flattened_bytes().to_vec(),
650674
})),
651675
signature: v.signature,
@@ -656,12 +680,15 @@ impl<DB: RepositoryExt> P2P<DB> {
656680
session_id,
657681
operator_pk,
658682
} => {
659-
let nonces = self.db.get_pub_nonces(&operator_pk, session_id).await?;
683+
let sigs = self
684+
.db
685+
.get_partial_signatures(&operator_pk, session_id)
686+
.await?;
660687

661-
nonces.map(|v| proto::GossipsubMsg {
662-
body: Some(Body::Nonce(proto::Musig2NoncesExchange {
688+
sigs.map(|v| proto::GossipsubMsg {
689+
body: Some(Body::Sigs(proto::Musig2SignaturesExchange {
663690
session_id: session_id.to_vec(),
664-
pub_nonces: v.entry.iter().map(|n| n.serialize().to_vec()).collect(),
691+
partial_sigs: v.entry.iter().map(|n| n.serialize().to_vec()).collect(),
665692
})),
666693
signature: v.signature,
667694
key: v.key.into(),
@@ -671,15 +698,12 @@ impl<DB: RepositoryExt> P2P<DB> {
671698
session_id,
672699
operator_pk,
673700
} => {
674-
let sigs = self
675-
.db
676-
.get_partial_signatures(&operator_pk, session_id)
677-
.await?;
701+
let nonces = self.db.get_pub_nonces(&operator_pk, session_id).await?;
678702

679-
sigs.map(|v| proto::GossipsubMsg {
680-
body: Some(Body::Sigs(proto::Musig2SignaturesExchange {
703+
nonces.map(|v| proto::GossipsubMsg {
704+
body: Some(Body::Nonce(proto::Musig2NoncesExchange {
681705
session_id: session_id.to_vec(),
682-
partial_sigs: v.entry.iter().map(|n| n.serialize().to_vec()).collect(),
706+
pub_nonces: v.entry.iter().map(|n| n.serialize().to_vec()).collect(),
683707
})),
684708
signature: v.signature,
685709
key: v.key.into(),

‎crates/p2p/src/tests/common.rs

+411
Large diffs are not rendered by default.

‎crates/p2p/src/tests/connect_peer.rs

+156
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
//! Connect to Peer Command tests.
2+
3+
use std::time::Duration;
4+
5+
use anyhow::bail;
6+
use libp2p::{build_multiaddr, identity::secp256k1::Keypair as SecpKeypair, PeerId};
7+
use strata_p2p_types::{OperatorPubKey, StakeChainId};
8+
use strata_p2p_wire::p2p::v1::UnsignedGossipsubMsg;
9+
use tokio::time::sleep;
10+
use tracing::info;
11+
use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
12+
13+
use super::common::{mock_stake_chain_info, Operator, Setup};
14+
use crate::{
15+
commands::{Command, ConnectToPeerCommand},
16+
events::Event,
17+
};
18+
19+
/// Tests sending a gossipsub message from a new operator to all existing operators.
20+
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
21+
#[ignore = "don't know why this does not work"]
22+
async fn gossip_new_operator() -> anyhow::Result<()> {
23+
const OPERATORS_NUM: usize = 2;
24+
25+
tracing_subscriber::registry()
26+
.with(fmt::layer())
27+
.with(EnvFilter::from_default_env())
28+
.init();
29+
30+
// Generate a keypair for the new operator
31+
let new_operator_keypair = SecpKeypair::generate();
32+
let new_operator_pubkey: OperatorPubKey = new_operator_keypair.public().clone().into();
33+
34+
// Create allowlist for all operators (including the new one)
35+
let mut signers_allowlist = vec![new_operator_pubkey.clone()];
36+
37+
// Create the original operators with allowlist containing the new operator
38+
let Setup {
39+
mut operators,
40+
cancel,
41+
tasks,
42+
} = Setup::with_extra_signers(OPERATORS_NUM, signers_allowlist.clone()).await?;
43+
44+
// Add existing operators to the signers allowlist
45+
for op in &operators {
46+
signers_allowlist.push(op.kp.public().clone().into());
47+
}
48+
49+
// Create a separate task for the new operator
50+
let local_addr = build_multiaddr!(Memory((OPERATORS_NUM + 1) as u64));
51+
52+
// Create peer IDs of existing operators
53+
let peer_ids: Vec<PeerId> = operators.iter().map(|op| op.peer_id).collect();
54+
55+
// Create connection addresses for the new operator to connect to existing ones
56+
let connect_addrs = (1..=OPERATORS_NUM)
57+
.map(|id| build_multiaddr!(Memory(id as u64)))
58+
.collect::<Vec<_>>();
59+
60+
// Create new operator with all necessary information
61+
info!("Creating new operator to listen at {}", local_addr);
62+
let mut new_operator = Operator::new(
63+
new_operator_keypair.clone(),
64+
peer_ids.clone(),
65+
connect_addrs, // Connect directly to existing operators
66+
local_addr.clone(),
67+
cancel.child_token(),
68+
operators
69+
.iter()
70+
.map(|op| op.kp.public().clone().into())
71+
.collect(),
72+
)
73+
.unwrap();
74+
75+
let new_operator_peer_id = new_operator.p2p.local_peer_id();
76+
let new_operator_handle = new_operator.handle.clone();
77+
78+
// Wait for existing operators to fully initialize
79+
sleep(Duration::from_millis(500)).await;
80+
81+
// Run the new operator in a separate task - this call will handle connections
82+
tasks.spawn(async move {
83+
// This will attempt to establish the connections to other operators
84+
info!("New operator is establishing connections");
85+
new_operator.p2p.establish_connections().await;
86+
info!("New operator connections established");
87+
88+
// This will start listening for messages
89+
new_operator.p2p.listen().await;
90+
});
91+
92+
// Connect the old operators to the new one
93+
for operator in &operators {
94+
info!(id = %operator.peer_id, "Connecting operator to new operator");
95+
new_operator
96+
.handle
97+
.send_command(Command::ConnectToPeer(ConnectToPeerCommand {
98+
peer_id: new_operator_peer_id,
99+
peer_addr: local_addr.clone(),
100+
}))
101+
.await;
102+
}
103+
104+
// Give time for the new operator to establish connections
105+
sleep(Duration::from_secs(5)).await;
106+
107+
// Verify the connections by having a regular operator send a message first
108+
let test_id1 = StakeChainId::hash(b"test_from_regular_operator");
109+
info!("Regular operator sending test message");
110+
operators[0]
111+
.handle
112+
.send_command(mock_stake_chain_info(&operators[0].kp, test_id1))
113+
.await;
114+
115+
// Wait for message propagation and verify
116+
sleep(Duration::from_secs(2)).await;
117+
118+
// Now try to have the new operator send a message
119+
let test_id2 = StakeChainId::hash(b"test_from_new_operator");
120+
info!("New operator sending test message");
121+
new_operator_handle
122+
.send_command(mock_stake_chain_info(&new_operator_keypair, test_id2))
123+
.await;
124+
125+
// Wait for message propagation
126+
sleep(Duration::from_secs(2)).await;
127+
128+
// Check that existing operators received the message
129+
for operator in &mut operators {
130+
info!(peer_id=%operator.peer_id, "Checking if operator received message");
131+
132+
while !operator.handle.events_is_empty() {
133+
let event = operator.handle.next_event().await?;
134+
info!(?event, "Received event");
135+
136+
match event {
137+
Event::ReceivedMessage(msg) => match &msg.unsigned {
138+
UnsignedGossipsubMsg::StakeChainExchange { stake_chain_id, .. } => {
139+
if *stake_chain_id == test_id1 {
140+
info!("Operator received message from regular operator");
141+
} else if *stake_chain_id == test_id2 {
142+
info!("Operator received message from new operator");
143+
}
144+
}
145+
_ => bail!("Unexpected message type"),
146+
},
147+
}
148+
}
149+
}
150+
151+
// Clean up
152+
cancel.cancel();
153+
tasks.wait().await;
154+
155+
Ok(())
156+
}

‎crates/p2p/src/tests/db.rs

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
//! DB tests.
2+
3+
use strata_p2p_db::{sled::AsyncDB, RepositoryExt};
4+
use strata_p2p_types::{OperatorPubKey, Scope, SessionId, StakeChainId};
5+
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
6+
7+
use super::common::{
8+
exchange_deposit_nonces, exchange_deposit_setup, exchange_deposit_sigs,
9+
exchange_stake_chain_info, Setup,
10+
};
11+
use crate::commands::CleanStorageCommand;
12+
13+
#[tokio::test]
14+
async fn operator_cleans_entries_correctly_at_command() -> anyhow::Result<()> {
15+
const OPERATORS_NUM: usize = 2;
16+
17+
tracing_subscriber::registry()
18+
.with(fmt::layer())
19+
.with(EnvFilter::from_default_env())
20+
.init();
21+
22+
let Setup {
23+
mut operators,
24+
cancel,
25+
tasks,
26+
} = Setup::all_to_all(OPERATORS_NUM).await?;
27+
28+
let stake_chain_id = StakeChainId::hash(b"stake_chain_id");
29+
let scope = Scope::hash(b"scope");
30+
let session_id = SessionId::hash(b"session_id");
31+
32+
exchange_stake_chain_info(&mut operators, OPERATORS_NUM, stake_chain_id).await?;
33+
exchange_deposit_setup(&mut operators, OPERATORS_NUM, scope).await?;
34+
exchange_deposit_nonces(&mut operators, OPERATORS_NUM, session_id).await?;
35+
exchange_deposit_sigs(&mut operators, OPERATORS_NUM, session_id).await?;
36+
37+
let other_operator_pubkey = OperatorPubKey::from(operators[0].kp.public().to_bytes().to_vec());
38+
let last_operator = &mut operators[1];
39+
last_operator
40+
.handle
41+
.send_command(CleanStorageCommand::new(
42+
vec![scope],
43+
vec![session_id],
44+
vec![other_operator_pubkey.clone()],
45+
))
46+
.await;
47+
48+
cancel.cancel();
49+
tasks.wait().await;
50+
51+
// Check that storage is empty after that.
52+
let setup_entry = <AsyncDB as RepositoryExt>::get_deposit_setup(
53+
&last_operator.db,
54+
&other_operator_pubkey,
55+
scope,
56+
)
57+
.await?;
58+
assert!(setup_entry.is_none());
59+
60+
let nonces_entry = <AsyncDB as RepositoryExt>::get_pub_nonces(
61+
&last_operator.db,
62+
&other_operator_pubkey,
63+
session_id,
64+
)
65+
.await?;
66+
assert!(nonces_entry.is_none());
67+
68+
let sigs_entry = <AsyncDB as RepositoryExt>::get_partial_signatures(
69+
&last_operator.db,
70+
&other_operator_pubkey,
71+
session_id,
72+
)
73+
.await?;
74+
assert!(sigs_entry.is_none());
75+
76+
Ok(())
77+
}

‎crates/p2p/src/tests/gossipsub.rs

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
//! Gossipsub tests.
2+
3+
use strata_p2p_types::{Scope, SessionId, StakeChainId};
4+
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
5+
6+
use super::common::{
7+
exchange_deposit_nonces, exchange_deposit_setup, exchange_deposit_sigs,
8+
exchange_stake_chain_info, Setup,
9+
};
10+
11+
/// Tests the gossip protocol in an all to all connected network with a single ID.
12+
#[tokio::test(flavor = "multi_thread", worker_threads = 3)]
13+
async fn all_to_all_one_id() -> anyhow::Result<()> {
14+
const OPERATORS_NUM: usize = 2;
15+
16+
tracing_subscriber::registry()
17+
.with(fmt::layer())
18+
.with(EnvFilter::from_default_env())
19+
.init();
20+
21+
let Setup {
22+
mut operators,
23+
cancel,
24+
tasks,
25+
} = Setup::all_to_all(OPERATORS_NUM).await?;
26+
27+
let stake_chain_id = StakeChainId::hash(b"stake_chain_id");
28+
let scope = Scope::hash(b"scope");
29+
let session_id = SessionId::hash(b"session_id");
30+
31+
exchange_stake_chain_info(&mut operators, OPERATORS_NUM, stake_chain_id).await?;
32+
exchange_deposit_setup(&mut operators, OPERATORS_NUM, scope).await?;
33+
exchange_deposit_nonces(&mut operators, OPERATORS_NUM, session_id).await?;
34+
exchange_deposit_sigs(&mut operators, OPERATORS_NUM, session_id).await?;
35+
36+
cancel.cancel();
37+
38+
tasks.wait().await;
39+
40+
Ok(())
41+
}
42+
43+
/// Tests the gossip protocol in an all to all connected network with multiple IDs.
44+
#[tokio::test(flavor = "multi_thread", worker_threads = 3)]
45+
async fn all_to_all_multiple_ids() -> anyhow::Result<()> {
46+
const OPERATORS_NUM: usize = 2;
47+
48+
tracing_subscriber::registry()
49+
.with(fmt::layer())
50+
.with(EnvFilter::from_default_env())
51+
.init();
52+
53+
let Setup {
54+
mut operators,
55+
cancel,
56+
tasks,
57+
} = Setup::all_to_all(OPERATORS_NUM).await?;
58+
59+
let stake_chain_ids = (0..OPERATORS_NUM)
60+
.map(|i| StakeChainId::hash(format!("stake_chain_id_{}", i).as_bytes()))
61+
.collect::<Vec<_>>();
62+
let scopes = (0..OPERATORS_NUM)
63+
.map(|i| Scope::hash(format!("scope_{}", i).as_bytes()))
64+
.collect::<Vec<_>>();
65+
66+
let session_ids = (0..OPERATORS_NUM)
67+
.map(|i| SessionId::hash(format!("session_{}", i).as_bytes()))
68+
.collect::<Vec<_>>();
69+
70+
for stake_chain_id in &stake_chain_ids {
71+
exchange_stake_chain_info(&mut operators, OPERATORS_NUM, *stake_chain_id).await?;
72+
}
73+
74+
for scope in &scopes {
75+
exchange_deposit_setup(&mut operators, OPERATORS_NUM, *scope).await?;
76+
}
77+
for session_id in &session_ids {
78+
exchange_deposit_nonces(&mut operators, OPERATORS_NUM, *session_id).await?;
79+
}
80+
for session_id in &session_ids {
81+
exchange_deposit_sigs(&mut operators, OPERATORS_NUM, *session_id).await?;
82+
}
83+
84+
cancel.cancel();
85+
86+
tasks.wait().await;
87+
88+
Ok(())
89+
}

‎crates/p2p/src/tests/mod.rs

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pub(crate) mod common;
2+
pub(crate) mod connect_peer;
3+
pub(crate) mod db;
4+
pub(crate) mod gossipsub;
5+
pub(crate) mod request;

‎crates/p2p/src/tests/request.rs

+256
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
//! GetMessage tests.
2+
3+
use anyhow::bail;
4+
use strata_p2p_db::{
5+
sled::AsyncDB, DepositSetupEntry, NoncesEntry, PartialSignaturesEntry, RepositoryExt,
6+
StakeChainEntry,
7+
};
8+
use strata_p2p_types::{OperatorPubKey, Scope, SessionId, StakeChainId};
9+
use strata_p2p_wire::p2p::v1::{GetMessageRequest, UnsignedGossipsubMsg};
10+
use tracing::info;
11+
use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
12+
13+
use super::common::{
14+
exchange_deposit_nonces, exchange_deposit_setup, exchange_deposit_sigs,
15+
exchange_stake_chain_info, mock_deposit_nonces, mock_deposit_setup, mock_deposit_sigs,
16+
mock_stake_chain_info, Setup,
17+
};
18+
use crate::{
19+
commands::{Command, UnsignedPublishMessage},
20+
events::Event,
21+
};
22+
23+
/// Tests the get message request-response flow.
24+
#[tokio::test(flavor = "multi_thread", worker_threads = 3)]
25+
async fn request_response() -> anyhow::Result<()> {
26+
const OPERATORS_NUM: usize = 2;
27+
28+
tracing_subscriber::registry()
29+
.with(fmt::layer())
30+
.with(EnvFilter::from_default_env())
31+
.init();
32+
33+
let Setup {
34+
mut operators,
35+
cancel,
36+
tasks,
37+
} = Setup::all_to_all(OPERATORS_NUM).await?;
38+
39+
let stake_chain_id = StakeChainId::hash(b"stake_chain_id");
40+
let scope = Scope::hash(b"scope");
41+
let session_id = SessionId::hash(b"session_id");
42+
43+
// last operator won't send his info to others
44+
exchange_stake_chain_info(
45+
&mut operators[..OPERATORS_NUM - 1],
46+
OPERATORS_NUM - 1,
47+
stake_chain_id,
48+
)
49+
.await?;
50+
exchange_deposit_setup(
51+
&mut operators[..OPERATORS_NUM - 1],
52+
OPERATORS_NUM - 1,
53+
scope,
54+
)
55+
.await?;
56+
exchange_deposit_nonces(
57+
&mut operators[..OPERATORS_NUM - 1],
58+
OPERATORS_NUM - 1,
59+
session_id,
60+
)
61+
.await?;
62+
exchange_deposit_sigs(
63+
&mut operators[..OPERATORS_NUM - 1],
64+
OPERATORS_NUM - 1,
65+
session_id,
66+
)
67+
.await?;
68+
69+
// create command to request info from the last operator
70+
let operator_pk: OperatorPubKey = operators[OPERATORS_NUM - 1].kp.public().clone().into();
71+
let command_stake_chain = Command::RequestMessage(GetMessageRequest::StakeChainExchange {
72+
stake_chain_id,
73+
operator_pk: operator_pk.clone(),
74+
});
75+
let command_deposit_setup = Command::RequestMessage(GetMessageRequest::DepositSetup {
76+
scope,
77+
operator_pk: operator_pk.clone(),
78+
});
79+
let command_deposit_nonces = Command::RequestMessage(GetMessageRequest::Musig2NoncesExchange {
80+
session_id,
81+
operator_pk: operator_pk.clone(),
82+
});
83+
let command_deposit_sigs =
84+
Command::RequestMessage(GetMessageRequest::Musig2SignaturesExchange {
85+
session_id,
86+
operator_pk: operator_pk.clone(),
87+
});
88+
89+
// put data in the last operator, so that he can respond it
90+
match mock_stake_chain_info(&operators[OPERATORS_NUM - 1].kp.clone(), stake_chain_id) {
91+
Command::PublishMessage(msg) => match msg.msg {
92+
UnsignedPublishMessage::StakeChainExchange {
93+
stake_chain_id,
94+
pre_stake_txid,
95+
pre_stake_vout,
96+
} => {
97+
let entry = StakeChainEntry {
98+
entry: (pre_stake_txid, pre_stake_vout),
99+
signature: msg.signature,
100+
key: msg.key,
101+
};
102+
<AsyncDB as RepositoryExt>::set_stake_chain_info_if_not_exists::<'_, '_>(
103+
&operators[OPERATORS_NUM - 1].db,
104+
stake_chain_id,
105+
entry,
106+
)
107+
.await?;
108+
}
109+
_ => unreachable!(),
110+
},
111+
_ => unreachable!(),
112+
}
113+
operators[0].handle.send_command(command_stake_chain).await;
114+
let event = operators[0].handle.next_event().await?;
115+
match event {
116+
Event::ReceivedMessage(msg) => match &msg.unsigned {
117+
UnsignedGossipsubMsg::StakeChainExchange {
118+
stake_chain_id: received_id,
119+
..
120+
} if msg.key == operator_pk && *received_id == stake_chain_id => {
121+
info!("Got stake chain info from the last operator")
122+
}
123+
_ => bail!("Got event other than expected 'stake_chain_info'",),
124+
},
125+
}
126+
127+
// put data in the last operator, so that he can respond it
128+
match mock_deposit_setup(&operators[OPERATORS_NUM - 1].kp.clone(), scope) {
129+
Command::PublishMessage(msg) => match msg.msg {
130+
UnsignedPublishMessage::DepositSetup {
131+
scope,
132+
hash,
133+
funding_txid,
134+
funding_vout,
135+
operator_pk,
136+
wots_pks,
137+
} => {
138+
let entry = DepositSetupEntry {
139+
signature: msg.signature,
140+
key: msg.key,
141+
hash,
142+
funding_txid,
143+
funding_vout,
144+
operator_pk,
145+
wots_pks,
146+
};
147+
<AsyncDB as RepositoryExt>::set_deposit_setup_if_not_exists::<'_, '_>(
148+
&operators[OPERATORS_NUM - 1].db,
149+
scope,
150+
entry,
151+
)
152+
.await?;
153+
}
154+
_ => unreachable!(),
155+
},
156+
_ => unreachable!(),
157+
}
158+
operators[0]
159+
.handle
160+
.send_command(command_deposit_setup)
161+
.await;
162+
let event = operators[0].handle.next_event().await?;
163+
match event {
164+
Event::ReceivedMessage(msg) => match &msg.unsigned {
165+
UnsignedGossipsubMsg::DepositSetup {
166+
scope: received_scope,
167+
..
168+
} if msg.key == operator_pk && *received_scope == scope => {
169+
info!("Got deposit setup info from the last operator")
170+
}
171+
_ => bail!("Got event other than expected 'deposit_setup'",),
172+
},
173+
}
174+
175+
// put data in the last operator, so that he can respond it
176+
match mock_deposit_nonces(&operators[OPERATORS_NUM - 1].kp.clone(), session_id) {
177+
Command::PublishMessage(msg) => match msg.msg {
178+
UnsignedPublishMessage::Musig2NoncesExchange {
179+
session_id,
180+
pub_nonces,
181+
} => {
182+
let entry = NoncesEntry {
183+
signature: msg.signature,
184+
key: msg.key,
185+
entry: pub_nonces,
186+
};
187+
<AsyncDB as RepositoryExt>::set_pub_nonces_if_not_exist::<'_, '_>(
188+
&operators[OPERATORS_NUM - 1].db,
189+
session_id,
190+
entry,
191+
)
192+
.await?;
193+
}
194+
_ => unreachable!(),
195+
},
196+
_ => unreachable!(),
197+
}
198+
operators[0]
199+
.handle
200+
.send_command(command_deposit_nonces)
201+
.await;
202+
let event = operators[0].handle.next_event().await?;
203+
match event {
204+
Event::ReceivedMessage(msg) => match &msg.unsigned {
205+
UnsignedGossipsubMsg::Musig2NoncesExchange {
206+
session_id: received_session_id,
207+
..
208+
} if msg.key == operator_pk && *received_session_id == session_id => {
209+
info!("Got deposit pubnonces from the last operator")
210+
}
211+
_ => bail!("Got event other than expected 'deposit_pubnonces'",),
212+
},
213+
}
214+
215+
// put data in the last operator, so that he can respond it
216+
match mock_deposit_sigs(&operators[OPERATORS_NUM - 1].kp.clone(), session_id) {
217+
Command::PublishMessage(msg) => match msg.msg {
218+
UnsignedPublishMessage::Musig2SignaturesExchange {
219+
session_id,
220+
partial_sigs,
221+
} => {
222+
let entry = PartialSignaturesEntry {
223+
signature: msg.signature,
224+
key: msg.key,
225+
entry: partial_sigs,
226+
};
227+
<AsyncDB as RepositoryExt>::set_partial_signatures_if_not_exists::<'_, '_>(
228+
&operators[OPERATORS_NUM - 1].db,
229+
session_id,
230+
entry,
231+
)
232+
.await?;
233+
}
234+
_ => unreachable!(),
235+
},
236+
_ => unreachable!(),
237+
}
238+
operators[0].handle.send_command(command_deposit_sigs).await;
239+
let event = operators[0].handle.next_event().await?;
240+
match event {
241+
Event::ReceivedMessage(msg) => match &msg.unsigned {
242+
UnsignedGossipsubMsg::Musig2SignaturesExchange {
243+
session_id: received_session_id,
244+
..
245+
} if msg.key == operator_pk && *received_session_id == session_id => {
246+
info!("Got deposit partial signatures from the last operator")
247+
}
248+
_ => bail!("Got event other than expected 'deposit_partial_sigs'",),
249+
},
250+
}
251+
cancel.cancel();
252+
253+
tasks.wait().await;
254+
255+
Ok(())
256+
}

‎crates/p2p/tests/common.rs

-50
This file was deleted.

‎crates/p2p/tests/gossipsub.rs

-503
This file was deleted.

‎crates/types/Cargo.toml

+7
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ edition = "2021"
77
default = []
88
proptest = ["dep:proptest", "dep:proptest-derive"]
99

10+
[lints]
11+
rust.missing_debug_implementations = "warn"
12+
rust.unreachable_pub = "warn"
13+
rust.unused_crate_dependencies = "deny"
14+
rust.unused_must_use = "deny"
15+
rust.rust_2018_idioms = { level = "deny", priority = -1 }
16+
1017
[dependencies]
1118
bitcoin.workspace = true
1219
hex.workspace = true

‎crates/types/src/deposit_data.rs

+235-10
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,114 @@ use serde::{Deserialize, Serialize};
88

99
use crate::{Wots160PublicKey, Wots256PublicKey, WOTS_SINGLE};
1010

11-
/// Winternitz One-Time Signature (WOTS) public key.
11+
/// Winternitz One-Time Signature (WOTS) public keys shared in a deposit.
1212
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Serialize, Deserialize)]
1313
#[cfg_attr(feature = "proptest", derive(Arbitrary))]
1414
pub struct WotsPublicKeys {
15+
/// WOTS public key used for the Withdrawal Fulfillment transaction.
16+
pub withdrawal_fulfillment: Wots256PublicKey,
17+
18+
/// WOTS public keys used for the Assert transaction in the Groth16 proof.
19+
pub groth16: Groth16PublicKeys,
20+
}
21+
22+
impl WotsPublicKeys {
23+
/// Creates a new [`WotsPublicKeys`] instance.
24+
///
25+
/// # Examples
26+
///
27+
/// ```
28+
/// # use strata_p2p_types::{WotsPublicKeys, Wots256PublicKey, Wots160PublicKey, Groth16PublicKeys};
29+
/// let withdrawal_key = Wots256PublicKey::new([[1u8; 20]; 68]);
30+
///
31+
/// // Create a WotsPublicKeys with empty Groth16 parts
32+
/// let empty_groth16_keys = WotsPublicKeys::new(
33+
/// withdrawal_key.clone(),
34+
/// vec![],
35+
/// vec![],
36+
/// vec![]
37+
/// );
38+
///
39+
/// // Create a WotsPublicKeys with some Groth16 parts
40+
/// let public_inputs = Wots256PublicKey::new([[2u8; 20]; 68]);
41+
/// let field_elements = Wots256PublicKey::new([[3u8; 20]; 68]);
42+
/// let hashes = Wots160PublicKey::new([[4u8; 20]; 44]);
43+
///
44+
/// let wots_keys = WotsPublicKeys::new(
45+
/// withdrawal_key,
46+
/// vec![public_inputs],
47+
/// vec![field_elements],
48+
/// vec![hashes],
49+
/// );
50+
/// ```
51+
pub fn new(
52+
withdrawal_fulfillment: Wots256PublicKey,
53+
public_inputs: Vec<Wots256PublicKey>,
54+
fqs: Vec<Wots256PublicKey>,
55+
hashes: Vec<Wots160PublicKey>,
56+
) -> Self {
57+
Self {
58+
withdrawal_fulfillment,
59+
groth16: Groth16PublicKeys::new(public_inputs, fqs, hashes),
60+
}
61+
}
62+
63+
/// Creates a [`WotsPublicKeys`] from a flattened byte array.
64+
///
65+
/// # Format
66+
///
67+
/// The flattened byte array is structured as follows:
68+
///
69+
/// - The first 3 bytes of the Groth16PublicKeys (containing counts)
70+
/// - The next `WOTS_SINGLE * Wots256PublicKey::SIZE` bytes represent the withdrawal_fulfillment
71+
/// key
72+
/// - The remaining bytes represent the flattened Groth16PublicKeys as described in
73+
/// [`Groth16PublicKeys::to_flattened_bytes`]
74+
pub fn from_flattened_bytes(bytes: &[u8]) -> Self {
75+
// The withdrawal fulfillment key size in flattened form
76+
let withdrawal_key_size = WOTS_SINGLE * Wots256PublicKey::SIZE;
77+
78+
// Parse the withdrawal fulfillment key
79+
let withdrawal_fulfillment =
80+
Wots256PublicKey::from_flattened_bytes(&bytes[0..withdrawal_key_size]);
81+
82+
// Parse the Groth16 public keys from the remaining bytes
83+
let groth16 = Groth16PublicKeys::from_flattened_bytes(&bytes[withdrawal_key_size..]);
84+
85+
Self {
86+
withdrawal_fulfillment,
87+
groth16,
88+
}
89+
}
90+
91+
/// Converts [`WotsPublicKeys`] to a flattened byte array.
92+
///
93+
/// # Format
94+
///
95+
/// The flattened byte array is structured as follows:
96+
///
97+
/// - The first `WOTS_SINGLE * Wots256PublicKey::SIZE` bytes represent the
98+
/// withdrawal_fulfillment key
99+
/// - The remaining bytes represent the flattened Groth16PublicKeys as described in
100+
/// [`Groth16PublicKeys::to_flattened_bytes`]
101+
pub fn to_flattened_bytes(&self) -> Vec<u8> {
102+
let mut bytes = Vec::new();
103+
104+
// Add withdrawal fulfillment key bytes
105+
bytes.extend(self.withdrawal_fulfillment.to_flattened_bytes());
106+
107+
// Add Groth16 public keys bytes
108+
bytes.extend(self.groth16.to_flattened_bytes());
109+
110+
bytes
111+
}
112+
}
113+
114+
/// Winternitz One-Time Signature (WOTS) public keys used for the Assert transaction
115+
/// in the Groth16 proof.
116+
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Serialize, Deserialize)]
117+
#[cfg_attr(feature = "proptest", derive(Arbitrary))]
118+
pub struct Groth16PublicKeys {
15119
/// Number of public inputs.
16120
pub n_public_inputs: u8,
17121

@@ -31,8 +135,26 @@ pub struct WotsPublicKeys {
31135
pub hashes: Vec<Wots160PublicKey>,
32136
}
33137

34-
impl WotsPublicKeys {
35-
/// Creates a new [`WotsPublicKeys`] instance.
138+
impl Groth16PublicKeys {
139+
/// Creates a new [`Groth16PublicKeys`] instance.
140+
///
141+
/// Note that you can create [`Groth16PublicKeys`] that contains no public inputs, field
142+
/// elements, or hashes. For example:
143+
///
144+
/// ```
145+
/// # use strata_p2p_types::{Groth16PublicKeys, Wots256PublicKey, Wots160PublicKey};
146+
/// let empty_wots = Groth16PublicKeys::new(vec![], vec![], vec![]);
147+
/// # assert!(empty_wots.is_empty());
148+
///
149+
/// let public_inputs = Wots256PublicKey::new([[1u8; 20]; 68]);
150+
/// let just_public_inputs = Groth16PublicKeys::new(vec![public_inputs], vec![], vec![]);
151+
///
152+
/// let field_elements = Wots256PublicKey::new([[2u8; 20]; 68]);
153+
/// let just_field_elements = Groth16PublicKeys::new(vec![], vec![field_elements], vec![]);
154+
///
155+
/// let hashes = Wots160PublicKey::new([[3u8; 20]; 44]);
156+
/// let just_hashes = Groth16PublicKeys::new(vec![], vec![], vec![hashes]);
157+
/// ```
36158
pub fn new(
37159
public_inputs: Vec<Wots256PublicKey>,
38160
fqs: Vec<Wots256PublicKey>,
@@ -58,7 +180,7 @@ impl WotsPublicKeys {
58180
self.n_public_inputs == 0 && self.n_field_elements == 0 && self.n_hashes == 0
59181
}
60182

61-
/// Converts [`WotsPublicKeys`] to a flattened byte array.
183+
/// Converts [`Groth16PublicKeys`] to a flattened byte array.
62184
///
63185
/// # Format
64186
///
@@ -175,9 +297,9 @@ mod tests {
175297
use crate::wots::wots_total_digits;
176298

177299
#[test]
178-
fn test_flattened_bytes_roundtrip() {
300+
fn groth16_wots_flattened_bytes_roundtrip() {
179301
// Create test data with known values
180-
let test_data = WotsPublicKeys::new(
302+
let test_data = Groth16PublicKeys::new(
181303
vec![Wots256PublicKey::new([[1u8; WOTS_SINGLE]; wots_total_digits(32)]); 2], /* 2 * 32 + 4 = 68 */
182304
vec![Wots256PublicKey::new([[2u8; WOTS_SINGLE]; wots_total_digits(32)]); 3], /* 2 * 32 + 4 = 68 */
183305
vec![Wots160PublicKey::new([[3u8; WOTS_SINGLE]; wots_total_digits(20)]); 4], /* 2 * 20 + 4 = 44 */
@@ -199,7 +321,7 @@ mod tests {
199321
assert_eq!(flattened[2], 4); // n_hashes
200322

201323
// Convert back from flattened bytes
202-
let reconstructed = WotsPublicKeys::from_flattened_bytes(&flattened);
324+
let reconstructed = Groth16PublicKeys::from_flattened_bytes(&flattened);
203325

204326
// Verify all fields match
205327
assert_eq!(test_data.n_public_inputs, reconstructed.n_public_inputs);
@@ -210,23 +332,87 @@ mod tests {
210332
assert_eq!(test_data.hashes, reconstructed.hashes);
211333
}
212334

335+
#[test]
336+
fn wots_public_keys_flattened_bytes_roundtrip() {
337+
// Create withdrawal fulfillment key
338+
let withdrawal_fulfillment =
339+
Wots256PublicKey::new([[4u8; WOTS_SINGLE]; wots_total_digits(32)]);
340+
341+
// Create Groth16 public keys
342+
let groth16 = Groth16PublicKeys::new(
343+
vec![Wots256PublicKey::new([[1u8; WOTS_SINGLE]; wots_total_digits(32)]); 2],
344+
vec![Wots256PublicKey::new([[2u8; WOTS_SINGLE]; wots_total_digits(32)]); 3],
345+
vec![Wots160PublicKey::new([[3u8; WOTS_SINGLE]; wots_total_digits(20)]); 4],
346+
);
347+
348+
// Create the WotsPublicKeys
349+
let wots_keys = WotsPublicKeys::new(
350+
withdrawal_fulfillment,
351+
groth16.public_inputs.clone(),
352+
groth16.fqs.clone(),
353+
groth16.hashes.clone(),
354+
);
355+
356+
// Convert to flattened bytes
357+
let flattened = wots_keys.to_flattened_bytes();
358+
359+
// Verify the length matches what we expect
360+
let expected_len = (WOTS_SINGLE * Wots256PublicKey::SIZE) + // withdrawal_fulfillment
361+
3 + // 3 bytes for counts
362+
(2 * WOTS_SINGLE * Wots256PublicKey::SIZE) + // public inputs
363+
(3 * WOTS_SINGLE * Wots256PublicKey::SIZE) + // field elements
364+
(4 * WOTS_SINGLE * Wots160PublicKey::SIZE); // hashes
365+
assert_eq!(flattened.len(), expected_len);
366+
367+
// Check that the first part is the withdrawal fulfillment key
368+
let withdrawal_bytes = withdrawal_fulfillment.to_flattened_bytes();
369+
assert_eq!(&flattened[0..withdrawal_bytes.len()], &withdrawal_bytes[..]);
370+
371+
// Convert back from flattened bytes
372+
let reconstructed = WotsPublicKeys::from_flattened_bytes(&flattened);
373+
374+
// Verify all fields match
375+
assert_eq!(
376+
wots_keys.withdrawal_fulfillment,
377+
reconstructed.withdrawal_fulfillment
378+
);
379+
assert_eq!(
380+
wots_keys.groth16.n_public_inputs,
381+
reconstructed.groth16.n_public_inputs
382+
);
383+
assert_eq!(
384+
wots_keys.groth16.n_field_elements,
385+
reconstructed.groth16.n_field_elements
386+
);
387+
assert_eq!(wots_keys.groth16.n_hashes, reconstructed.groth16.n_hashes);
388+
assert_eq!(
389+
wots_keys.groth16.public_inputs,
390+
reconstructed.groth16.public_inputs
391+
);
392+
assert_eq!(wots_keys.groth16.fqs, reconstructed.groth16.fqs);
393+
assert_eq!(wots_keys.groth16.hashes, reconstructed.groth16.hashes);
394+
395+
// Full equality check
396+
assert_eq!(wots_keys, reconstructed);
397+
}
398+
213399
#[cfg(feature = "proptest")]
214400
proptest! {
215401
#[test]
216-
fn proptest_flattened_bytes_roundtrip(
402+
fn proptest_groth16_wots_flattened_bytes_roundtrip(
217403
n_inputs in 0u8..5u8,
218404
n_fqs in 0u8..5u8,
219405
n_hashes in 0u8..5u8,
220406
value in 0u8..255u8
221407
) {
222-
let test_data = WotsPublicKeys::new(
408+
let test_data = Groth16PublicKeys::new(
223409
vec![Wots256PublicKey::new([[value; WOTS_SINGLE]; Wots256PublicKey::SIZE]); n_inputs as usize],
224410
vec![Wots256PublicKey::new([[value; WOTS_SINGLE]; Wots256PublicKey::SIZE]); n_fqs as usize],
225411
vec![Wots160PublicKey::new([[value; WOTS_SINGLE]; Wots160PublicKey::SIZE]); n_hashes as usize],
226412
);
227413

228414
let flattened = test_data.to_flattened_bytes();
229-
let reconstructed = WotsPublicKeys::from_flattened_bytes(&flattened);
415+
let reconstructed = Groth16PublicKeys::from_flattened_bytes(&flattened);
230416

231417
prop_assert_eq!(test_data.n_public_inputs, reconstructed.n_public_inputs);
232418
prop_assert_eq!(test_data.n_field_elements, reconstructed.n_field_elements);
@@ -235,5 +421,44 @@ mod tests {
235421
prop_assert_eq!(test_data.fqs, reconstructed.fqs);
236422
prop_assert_eq!(test_data.hashes, reconstructed.hashes);
237423
}
424+
425+
#[test]
426+
fn proptest_wots_public_keys_flattened_bytes_roundtrip(
427+
withdrawal_value in 0u8..255u8,
428+
n_inputs in 0u8..3u8,
429+
n_fqs in 0u8..3u8,
430+
n_hashes in 0u8..3u8,
431+
groth16_value in 0u8..255u8
432+
) {
433+
// Create test data with different values for withdrawal and groth16
434+
let withdrawal_fulfillment = Wots256PublicKey::new(
435+
[[withdrawal_value; WOTS_SINGLE]; Wots256PublicKey::SIZE]
436+
);
437+
438+
let groth16 = Groth16PublicKeys::new(
439+
vec![Wots256PublicKey::new([[groth16_value; WOTS_SINGLE]; Wots256PublicKey::SIZE]); n_inputs as usize],
440+
vec![Wots256PublicKey::new([[groth16_value; WOTS_SINGLE]; Wots256PublicKey::SIZE]); n_fqs as usize],
441+
vec![Wots160PublicKey::new([[groth16_value; WOTS_SINGLE]; Wots160PublicKey::SIZE]); n_hashes as usize],
442+
);
443+
444+
let wots_keys = WotsPublicKeys::new(
445+
withdrawal_fulfillment,
446+
groth16.public_inputs.clone(),
447+
groth16.fqs.clone(),
448+
groth16.hashes.clone(),
449+
);
450+
451+
let flattened = wots_keys.to_flattened_bytes();
452+
let reconstructed = WotsPublicKeys::from_flattened_bytes(&flattened);
453+
454+
prop_assert_eq!(wots_keys.withdrawal_fulfillment, reconstructed.withdrawal_fulfillment);
455+
prop_assert_eq!(wots_keys.groth16.n_public_inputs, reconstructed.groth16.n_public_inputs);
456+
prop_assert_eq!(wots_keys.groth16.n_field_elements, reconstructed.groth16.n_field_elements);
457+
prop_assert_eq!(wots_keys.groth16.n_hashes, reconstructed.groth16.n_hashes);
458+
prop_assert_eq!(wots_keys.groth16.public_inputs.clone(), reconstructed.groth16.public_inputs.clone());
459+
prop_assert_eq!(wots_keys.groth16.fqs.clone(), reconstructed.groth16.fqs.clone());
460+
prop_assert_eq!(wots_keys.groth16.hashes.clone(), reconstructed.groth16.hashes.clone());
461+
prop_assert_eq!(wots_keys, reconstructed);
462+
}
238463
}
239464
}

‎crates/types/src/lib.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ mod stake_chain_id;
1212
mod stake_data;
1313
mod wots;
1414

15-
pub use deposit_data::WotsPublicKeys;
15+
pub use deposit_data::{Groth16PublicKeys, WotsPublicKeys};
1616
pub use operator::OperatorPubKey;
1717
pub use scope::Scope;
1818
pub use session_id::SessionId;
1919
pub use stake_chain_id::StakeChainId;
2020
pub use stake_data::StakeData;
21-
pub use wots::{Wots160PublicKey, Wots256PublicKey, WotsPublicKey, WOTS_SINGLE};
21+
pub use wots::{Wots160PublicKey, Wots256PublicKey, WOTS_SINGLE};

‎crates/types/src/stake_data.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ use serde::{Deserialize, Serialize};
99
use crate::{Wots256PublicKey, WOTS_SINGLE};
1010

1111
/// Size of a [`sha256::Hash`] in bytes.
12-
pub const HASH_SIZE: usize = 32;
12+
pub(crate) const HASH_SIZE: usize = 32;
1313

1414
/// Size of a [`Txid`](bitcoin::Txid) in bytes.
15-
pub const TXID_SIZE: usize = 32;
15+
pub(crate) const TXID_SIZE: usize = 32;
1616

1717
/// Size of a vout in bytes.
18-
pub const VOUT_SIZE: usize = 4;
18+
pub(crate) const VOUT_SIZE: usize = 4;
1919

2020
/// Size of Wots256PublicKey in arrays (2 * 32 + 4 = 68)
2121
const WOTS256_ARRAYS: usize = 68;

‎crates/types/src/wots.rs

+17-12
Original file line numberDiff line numberDiff line change
@@ -16,27 +16,30 @@ pub const WOTS_SINGLE: usize = 20;
1616

1717
/// The number of bits in an individual WOTS digit.
1818
// WARNING(proofofkeags): MUST BE A FACTOR OF 8 WITH CURRENT IMPLEMENTATION (1,2,4,8)
19-
pub const WOTS_DIGIT_WIDTH: usize = 4;
19+
pub(crate) const WOTS_DIGIT_WIDTH: usize = 4;
2020

2121
/// The number of WOTS digits needed to commit a byte.
22-
pub const WOTS_DIGITS_PER_BYTE: usize = 8 / WOTS_DIGIT_WIDTH;
22+
pub(crate) const WOTS_DIGITS_PER_BYTE: usize = 8 / WOTS_DIGIT_WIDTH;
2323

2424
/// The maximum number a WOTS digit can hold.
25-
#[allow(dead_code)]
26-
pub const WOTS_MAX_DIGIT: usize = (2 << WOTS_DIGIT_WIDTH) - 1;
25+
#[allow(unfulfilled_lint_expectations)]
26+
#[expect(dead_code)]
27+
pub(crate) const WOTS_MAX_DIGIT: usize = (2 << WOTS_DIGIT_WIDTH) - 1;
2728

2829
/// The number of WOTS digits required to represent a message for a given message length.
29-
#[allow(dead_code)]
30-
pub const fn wots_msg_digits(msg_len_bytes: usize) -> usize {
30+
#[allow(unfulfilled_lint_expectations)]
31+
#[expect(dead_code)]
32+
pub(crate) const fn wots_msg_digits(msg_len_bytes: usize) -> usize {
3133
WOTS_DIGITS_PER_BYTE * msg_len_bytes
3234
}
3335

3436
/// The number of WOTS digits required to sign the checksum for a given message length.
3537
///
3638
/// The checksum of a WOTS commitment is the sum of the digit values themselves which is then
3739
/// encoded as a base256 integer. That integer is then signed using the same WOTS scheme.
38-
#[allow(dead_code)]
39-
pub const fn wots_checksum_digits(msg_len_bytes: usize) -> usize {
40+
#[allow(unfulfilled_lint_expectations)]
41+
#[expect(dead_code)]
42+
pub(crate) const fn wots_checksum_digits(msg_len_bytes: usize) -> usize {
4043
let max_checksum = wots_msg_digits(msg_len_bytes) * WOTS_MAX_DIGIT;
4144

4245
// Compute how many bytes we need to represent the checksum itself
@@ -55,8 +58,9 @@ pub const fn wots_checksum_digits(msg_len_bytes: usize) -> usize {
5558
}
5659

5760
/// The total number of WOTS digit keys
58-
#[allow(dead_code)]
59-
pub const fn wots_total_digits(msg_len_bytes: usize) -> usize {
61+
#[allow(unfulfilled_lint_expectations)]
62+
#[expect(dead_code)]
63+
pub(crate) const fn wots_total_digits(msg_len_bytes: usize) -> usize {
6064
wots_msg_digits(msg_len_bytes) + wots_checksum_digits(msg_len_bytes)
6165
}
6266

@@ -87,12 +91,12 @@ where
8791
}
8892

8993
/// Converts the public key to a byte array.
90-
pub fn to_bytes(&self) -> [[u8; WOTS_SINGLE]; MSG_LEN_BYTES] {
94+
pub fn to_bytes(self) -> [[u8; WOTS_SINGLE]; MSG_LEN_BYTES] {
9195
self.0
9296
}
9397

9498
/// Converts the public key to a flattened byte array.
95-
pub fn to_flattened_bytes(&self) -> Vec<u8> {
99+
pub fn to_flattened_bytes(self) -> Vec<u8> {
96100
// Changed return type to Vec<u8>
97101
let mut bytes = Vec::with_capacity(WOTS_SINGLE * MSG_LEN_BYTES);
98102
for byte_array in &self.0 {
@@ -184,6 +188,7 @@ where
184188
{
185189
type Value = WotsPublicKey<M>;
186190

191+
#[expect(elided_lifetimes_in_paths)]
187192
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
188193
formatter.write_str(&format!("a WotsPublicKey with {} bytes", WOTS_SINGLE * M))
189194
}

‎crates/wire/Cargo.toml

+7
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@ name = "strata-p2p-wire"
33
version = "0.1.0"
44
edition = "2021"
55

6+
[lints]
7+
rust.missing_debug_implementations = "warn"
8+
rust.unreachable_pub = "warn"
9+
rust.unused_crate_dependencies = "deny"
10+
rust.unused_must_use = "deny"
11+
rust.rust_2018_idioms = { level = "deny", priority = -1 }
12+
613
[dependencies]
714
bitcoin.workspace = true
815
musig2.workspace = true

‎crates/wire/src/p2p/v1/typed.rs

+193-178
Large diffs are not rendered by default.

‎proto/strata/bitvm2/p2p/v1/gossipsub.proto

+15-9
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,28 @@ package strata.bitvm2.p2p.v1;
66
message StakeChainExchange {
77
/// 32-byte hash of some unique to stake chain data.
88
bytes stake_chain_id = 1;
9-
// Output number of pre stake tx.
10-
uint32 pre_stake_vout = 2;
119
// Transaction hash of pre stake tx.
12-
bytes pre_stake_txid = 3;
13-
// Keys `Y_{i, j}`.
14-
repeated bytes checkpoint_pubkeys = 4;
15-
// Stake data for each stake tx.
16-
repeated bytes stake_data = 5;
10+
bytes pre_stake_txid = 2;
11+
// vout of pre stake tx.
12+
uint32 pre_stake_vout = 3;
1713
}
1814

1915
// Primarily used for the WOTS PKs for the Deposit.
2016
message DepositSetupExchange {
2117
// 32-byte hash of some unique to deposit data.
2218
bytes scope = 1;
23-
// Deposit data with all WOTS 160- and 256-bit public keys.
24-
bytes wots_pks = 2;
19+
// Hash to use in the hashlock output from the Stake Chain
20+
bytes hash = 2;
21+
// Funding Txid to cover for operator costs in dust connector outputs
22+
bytes funding_txid = 3;
23+
// Funding vout to cover for operator costs in dust connector outputs
24+
uint32 funding_vout = 4;
25+
// Operator's X-only public key to construct a P2TR address to reimburse the
26+
// operator for a valid withdraw fulfillment.
27+
bytes operator_pk = 5;
28+
// Deposit data and Withdraw fulfillment transaction data with
29+
// all WOTS 160- and 256-bit public keys.
30+
bytes wots_pks = 6;
2531
}
2632

2733
// Musig2 first-round (public) nonces exchange.

0 commit comments

Comments
 (0)
Please sign in to comment.