diff --git a/Cargo.lock b/Cargo.lock index 6071bcd7..8895369a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -662,7 +662,7 @@ dependencies = [ "alloy-rlp", "alloy-serde 0.11.1", "alloy-sol-types", - "itertools 0.13.0", + "itertools 0.14.0", "serde", "serde_json", "thiserror 2.0.11", @@ -1665,7 +1665,7 @@ dependencies = [ "bitflags 2.9.0", "cexpr", "clang-sys", - "itertools 0.13.0", + "itertools 0.12.1", "log", "prettyplease", "proc-macro2", @@ -2918,7 +2918,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18e4fdb82bd54a12e42fb58a800dcae6b9e13982238ce2296dc3570b92148e1f" dependencies = [ "data-encoding", - "syn 2.0.98", + "syn 1.0.109", ] [[package]] @@ -7176,8 +7176,8 @@ version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" dependencies = [ - "heck 0.5.0", - "itertools 0.13.0", + "heck 0.4.1", + "itertools 0.12.1", "log", "multimap", "once_cell", @@ -7197,7 +7197,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" dependencies = [ "anyhow", - "itertools 0.13.0", + "itertools 0.12.1", "proc-macro2", "quote", "syn 2.0.98", @@ -10053,7 +10053,7 @@ dependencies = [ [[package]] name = "strata-p2p" version = "0.1.0" -source = "git+https://github.com/alpenlabs/strata-p2p.git#0f457eda515c8619175451abe1da9eaff0f4b131" +source = "git+https://github.com/alpenlabs/strata-p2p.git#c036205b4a46c6a210c255fddeb195d86b7c59b2" dependencies = [ "async-trait", "bitcoin", @@ -10066,7 +10066,6 @@ dependencies = [ "strata-p2p-types", "strata-p2p-wire", "thiserror 2.0.11", - "threadpool", "tokio", "tokio-util", "tracing", @@ -10075,7 +10074,7 @@ dependencies = [ [[package]] name = "strata-p2p-db" version = "0.1.0" -source = "git+https://github.com/alpenlabs/strata-p2p.git#0f457eda515c8619175451abe1da9eaff0f4b131" +source = "git+https://github.com/alpenlabs/strata-p2p.git#c036205b4a46c6a210c255fddeb195d86b7c59b2" dependencies = [ "async-trait", "bitcoin", @@ -10118,7 +10117,7 @@ dependencies = [ [[package]] name = "strata-p2p-types" version = "0.1.0" -source = "git+https://github.com/alpenlabs/strata-p2p.git#0f457eda515c8619175451abe1da9eaff0f4b131" +source = "git+https://github.com/alpenlabs/strata-p2p.git#c036205b4a46c6a210c255fddeb195d86b7c59b2" dependencies = [ "bitcoin", "hex", @@ -10129,7 +10128,7 @@ dependencies = [ [[package]] name = "strata-p2p-wire" version = "0.1.0" -source = "git+https://github.com/alpenlabs/strata-p2p.git#0f457eda515c8619175451abe1da9eaff0f4b131" +source = "git+https://github.com/alpenlabs/strata-p2p.git#c036205b4a46c6a210c255fddeb195d86b7c59b2" dependencies = [ "bitcoin", "musig2", diff --git a/crates/p2p-impl/src/message_handler.rs b/crates/p2p-impl/src/message_handler.rs index a362e973..7732ae43 100644 --- a/crates/p2p-impl/src/message_handler.rs +++ b/crates/p2p-impl/src/message_handler.rs @@ -1,6 +1,6 @@ //! Message handler for the Strata Bridge P2P. -use bitcoin::{OutPoint, XOnlyPublicKey}; +use bitcoin::{hashes::sha256, Txid, XOnlyPublicKey}; use libp2p::identity::secp256k1::Keypair as Libp2pSecpKeypair; use musig2::{PartialSignature, PubNonce}; use strata_p2p::{ @@ -8,7 +8,7 @@ use strata_p2p::{ events::Event, swarm::handle::P2PHandle, }; -use strata_p2p_types::{OperatorPubKey, Scope, SessionId, StakeChainId, StakeData, WotsPublicKeys}; +use strata_p2p_types::{OperatorPubKey, Scope, SessionId, StakeChainId, WotsPublicKeys}; use strata_p2p_wire::p2p::v1::GetMessageRequest; use tracing::{error, info, trace}; @@ -62,8 +62,23 @@ impl MessageHandler { } /// Sends a deposit setup message to the network. - pub async fn send_deposit_setup(&self, scope: Scope, wots_pks: WotsPublicKeys) { - let msg = UnsignedPublishMessage::DepositSetup { scope, wots_pks }; + pub async fn send_deposit_setup( + &self, + scope: Scope, + hash: sha256::Hash, + funding_txid: Txid, + funding_vout: u32, + operator_pk: XOnlyPublicKey, + wots_pks: WotsPublicKeys, + ) { + let msg = UnsignedPublishMessage::DepositSetup { + scope, + hash, + funding_txid, + funding_vout, + operator_pk, + wots_pks, + }; self.dispatch(msg, "deposit setup message").await; } @@ -71,15 +86,13 @@ impl MessageHandler { pub async fn send_stake_chain_exchange( &self, stake_chain_id: StakeChainId, - pre_stake_outpoint: OutPoint, - checkpoint_pubkeys: Vec, - stake_data: Vec, + pre_stake_txid: Txid, + pre_stake_vout: u32, ) { let msg = UnsignedPublishMessage::StakeChainExchange { stake_chain_id, - pre_stake_outpoint, - checkpoint_pubkeys, - stake_data, + pre_stake_txid, + pre_stake_vout, }; self.dispatch(msg, "stake chain exchange message").await; } diff --git a/crates/p2p-impl/src/tests/common.rs b/crates/p2p-impl/src/tests/common.rs index 3d44bb12..6d102e1e 100644 --- a/crates/p2p-impl/src/tests/common.rs +++ b/crates/p2p-impl/src/tests/common.rs @@ -3,43 +3,29 @@ use std::{sync::Arc, time::Duration}; use anyhow::bail; -use bitcoin::{hashes::sha256, OutPoint, XOnlyPublicKey}; +use bitcoin::{ + hashes::{sha256, Hash}, + Txid, XOnlyPublicKey, +}; use futures::future::join_all; use libp2p::{ build_multiaddr, identity::{secp256k1::Keypair as SecpKeypair, Keypair}, Multiaddr, PeerId, }; -use musig2::{PartialSignature, PubNonce}; -use strata_bridge_test_utils::{ - musig2::{generate_partial_signature, generate_pubnonce}, - prelude::generate_keypair, -}; +use strata_bridge_test_utils::musig2::{generate_partial_signature, generate_pubnonce}; use strata_p2p::{ commands::{Command, UnsignedPublishMessage}, events::Event, swarm::{self, handle::P2PHandle, P2PConfig, P2P}, }; use strata_p2p_db::sled::AsyncDB; -use strata_p2p_types::{ - OperatorPubKey, Scope, SessionId, StakeChainId, StakeData, Wots256PublicKey, -}; +use strata_p2p_types::{OperatorPubKey, Scope, SessionId, StakeChainId, WotsPublicKeys}; use strata_p2p_wire::p2p::v1::{GossipsubMsg, UnsignedGossipsubMsg}; use threadpool::ThreadPool; use tokio_util::{sync::CancellationToken, task::TaskTracker}; use tracing::{info, trace}; -use crate::{constants::DEFAULT_NUM_THREADS, Configuration}; - -/// Auxiliary structure to control operators from outside. -pub(crate) struct OperatorHandle { - pub(crate) handle: P2PHandle, - pub(crate) peer_id: PeerId, - pub(crate) kp: SecpKeypair, - pub(crate) db: AsyncDB, -} - -/// Represents an operator with its associated P2P instance, handle, keypair, and database. pub(crate) struct Operator { pub(crate) p2p: P2P, pub(crate) handle: P2PHandle, @@ -48,7 +34,6 @@ pub(crate) struct Operator { } impl Operator { - /// Creates a new operator instance. pub(crate) fn new( keypair: SecpKeypair, allowlist: Vec, @@ -57,34 +42,20 @@ impl Operator { cancel: CancellationToken, signers_allowlist: Vec, ) -> anyhow::Result { - let config = Configuration { + let db = sled::Config::new().temporary(true).open()?; + + let config = P2PConfig { keypair: keypair.clone(), idle_connection_timeout: Duration::from_secs(30), listening_addr: local_addr, allowlist, connect_to, signers_allowlist, - num_threads: Some(DEFAULT_NUM_THREADS), - }; - let p2p_config = P2PConfig { - keypair: config.keypair.clone(), - idle_connection_timeout: config.idle_connection_timeout, - listening_addr: config.listening_addr.clone(), - allowlist: config.allowlist.clone(), - connect_to: config.connect_to.clone(), - signers_allowlist: config.signers_allowlist.clone(), }; - info!("creating an in-memory sled database"); - let db = sled::Config::new().temporary(true).open()?; - let pool = ThreadPool::new(1); - let db = AsyncDB::new(pool, Arc::new(db)); - - info!("creating a swarm"); - // let swarm = swarm::with_tcp_transport(&p2p_config)?; - let swarm = swarm::with_inmemory_transport(&p2p_config)?; - let (p2p, handle) = - P2P::::from_config(p2p_config, cancel, db.clone(), swarm, None)?; + let swarm = swarm::with_inmemory_transport(&config)?; + let db = AsyncDB::new(ThreadPool::new(1), Arc::new(db)); + let (p2p, handle) = P2P::::from_config(config, cancel, db.clone(), swarm, None)?; Ok(Self { handle, @@ -95,7 +66,14 @@ impl Operator { } } -/// A setup for testing purposes. +/// Auxiliary structure to control operators from outside. +pub(crate) struct OperatorHandle { + pub(crate) handle: P2PHandle, + pub(crate) peer_id: PeerId, + pub(crate) kp: SecpKeypair, + pub(crate) db: AsyncDB, // We include DB here to manipulate internal data and flow mechanics. +} + pub(crate) struct Setup { pub(crate) cancel: CancellationToken, pub(crate) operators: Vec, @@ -119,7 +97,6 @@ impl Setup { for (idx, (keypair, addr)) in keypairs.iter().zip(&multiaddresses).enumerate() { let mut other_addrs = multiaddresses.clone(); - trace!(?other_addrs, "connecting to other addresses"); other_addrs.remove(idx); let mut other_peerids = peer_ids.clone(); other_peerids.remove(idx); @@ -145,20 +122,64 @@ impl Setup { }) } - /// Create N random keypairs, peer ids from them and sequential localhost - /// addresses. - pub(crate) fn setup_keys_ids_addrs_of_n_operators( + /// Spawn `n` operators that are connected "all-to-all" with handles to them, task tracker + /// to stop control async tasks they are spawned in with an extra signers allowlist. + #[expect(dead_code)] + pub(crate) async fn with_extra_signers( number: usize, + extra_signers: Vec, + ) -> anyhow::Result { + let (keypairs, peer_ids, multiaddresses) = + Self::setup_keys_ids_addrs_of_n_operators(number); + + let cancel = CancellationToken::new(); + let mut operators = Vec::new(); + let mut signers_allowlist: Vec = keypairs + .clone() + .into_iter() + .map(|kp| kp.public().clone().into()) + .collect(); + + // Add the extra signers to the allowlist + signers_allowlist.extend(extra_signers); + + for (idx, (keypair, addr)) in keypairs.iter().zip(&multiaddresses).enumerate() { + let mut other_addrs = multiaddresses.clone(); + other_addrs.remove(idx); + let mut other_peerids = peer_ids.clone(); + other_peerids.remove(idx); + + let operator = Operator::new( + keypair.clone(), + other_peerids, + other_addrs, + addr.clone(), + cancel.child_token(), + signers_allowlist.clone(), + )?; + + operators.push(operator); + } + + let (operators, tasks) = Self::start_operators(operators).await; + + Ok(Self { + cancel, + tasks, + operators, + }) + } + + /// Create `n` random keypairs, peer ids from them and sequential in-memory + /// addresses. + fn setup_keys_ids_addrs_of_n_operators( + n: usize, ) -> (Vec, Vec, Vec) { - let keypairs = (0..number) - .map(|_| SecpKeypair::generate()) - .collect::>(); + let keypairs = (0..n).map(|_| SecpKeypair::generate()).collect::>(); let peer_ids = keypairs .iter() .map(|key| PeerId::from_public_key(&Keypair::from(key.clone()).public())) .collect::>(); - // let multiaddresses = (10_000..(keypairs.len() + 10_000) as u16) - // .map(|port| build_multiaddr!(Ip4([0, 0, 0, 0]), Tcp(port))) let multiaddresses = (1..(keypairs.len() + 1) as u16) .map(|idx| build_multiaddr!(Memory(idx))) .collect::>(); @@ -196,14 +217,53 @@ impl Setup { } } +pub(crate) fn mock_stake_chain_info(kp: &SecpKeypair, stake_chain_id: StakeChainId) -> Command { + let kind = UnsignedPublishMessage::StakeChainExchange { + stake_chain_id, + pre_stake_txid: Txid::all_zeros(), + pre_stake_vout: 0, + }; + kind.sign_secp256k1(kp).into() +} + +pub(crate) fn mock_deposit_setup(kp: &SecpKeypair, scope: Scope) -> Command { + let mock_bytes = [0u8; 1_360 + 362_960]; + let unsigned = UnsignedPublishMessage::DepositSetup { + scope, + hash: sha256::Hash::const_hash(b"hash me!"), + funding_txid: Txid::all_zeros(), + funding_vout: 0, + operator_pk: XOnlyPublicKey::from_slice(&[2u8; 32]).unwrap(), + wots_pks: WotsPublicKeys::from_flattened_bytes(&mock_bytes), + }; + unsigned.sign_secp256k1(kp).into() +} + +pub(crate) fn mock_deposit_nonces(kp: &SecpKeypair, session_id: SessionId) -> Command { + let unsigned = UnsignedPublishMessage::Musig2NoncesExchange { + session_id, + pub_nonces: (0..5).map(|_| generate_pubnonce()).collect(), + }; + unsigned.sign_secp256k1(kp).into() +} + +pub(crate) fn mock_deposit_sigs(kp: &SecpKeypair, session_id: SessionId) -> Command { + let unsigned = UnsignedPublishMessage::Musig2SignaturesExchange { + session_id, + partial_sigs: (0..5).map(|_| generate_partial_signature()).collect(), + }; + unsigned.sign_secp256k1(kp).into() +} + pub(crate) async fn exchange_stake_chain_info( operators: &mut [OperatorHandle], operators_num: usize, + stake_chain_id: StakeChainId, ) -> anyhow::Result<()> { for operator in operators.iter() { operator .handle - .send_command(mock_stake_chain_info(&operator.kp)) + .send_command(mock_stake_chain_info(&operator.kp, stake_chain_id)) .await; } for operator in operators.iter_mut() { @@ -319,58 +379,3 @@ pub(crate) async fn exchange_deposit_sigs( Ok(()) } - -pub(crate) fn mock_stake_chain_info(kp: &SecpKeypair) -> Command { - const QUANTITY: usize = 5; - let checkpoint_pubkeys: Vec = (0..QUANTITY) - .map(|_| { - let keypair = generate_keypair(); - keypair.x_only_public_key().0 - }) - .collect(); - let stake_data: Vec = (0..QUANTITY) - .map(|_| StakeData { - withdrawal_fulfillment_pk: Wots256PublicKey::from_flattened_bytes(&[1u8; 1_360]), - hash: sha256::Hash::const_hash(b"foo"), - operator_funds: OutPoint::null(), - }) - .collect(); - let kind = UnsignedPublishMessage::StakeChainExchange { - stake_chain_id: StakeChainId::hash(b"stake_chain_id"), - pre_stake_outpoint: OutPoint::null(), - checkpoint_pubkeys, - stake_data, - }; - kind.sign_secp256k1(kp).into() -} - -pub(crate) fn mock_deposit_setup(kp: &SecpKeypair, scope: Scope) -> Command { - let mock_bytes = [1u8; 362_960]; - let unsigned = UnsignedPublishMessage::DepositSetup { - scope, - wots_pks: strata_p2p_types::WotsPublicKeys::from_flattened_bytes(&mock_bytes), - }; - unsigned.sign_secp256k1(kp).into() -} - -pub(crate) fn mock_deposit_nonces(kp: &SecpKeypair, session_id: SessionId) -> Command { - const QUANTITY: usize = 5; - let pub_nonces: Vec = (0..QUANTITY).map(|_| generate_pubnonce()).collect(); - let unsigned = UnsignedPublishMessage::Musig2NoncesExchange { - session_id, - pub_nonces, - }; - unsigned.sign_secp256k1(kp).into() -} - -pub(crate) fn mock_deposit_sigs(kp: &SecpKeypair, session_id: SessionId) -> Command { - const QUANTITY: usize = 5; - let partial_sigs: Vec = (0..QUANTITY) - .map(|_| generate_partial_signature()) - .collect(); - let unsigned = UnsignedPublishMessage::Musig2SignaturesExchange { - session_id, - partial_sigs, - }; - unsigned.sign_secp256k1(kp).into() -} diff --git a/crates/p2p-impl/src/tests/gossipsub.rs b/crates/p2p-impl/src/tests/gossipsub.rs index 4e405eed..503d252d 100644 --- a/crates/p2p-impl/src/tests/gossipsub.rs +++ b/crates/p2p-impl/src/tests/gossipsub.rs @@ -1,5 +1,5 @@ use strata_common::logging::{self, LoggerConfig}; -use strata_p2p_types::{Scope, SessionId}; +use strata_p2p_types::{Scope, SessionId, StakeChainId}; use crate::tests::common::{ exchange_deposit_nonces, exchange_deposit_setup, exchange_deposit_sigs, @@ -20,10 +20,11 @@ async fn all_to_all_one_scope() -> anyhow::Result<()> { tasks, } = Setup::all_to_all(OPERATORS_NUM).await?; + let stake_chain_id = StakeChainId::hash(b"stake_chain_id"); let scope = Scope::hash(b"scope"); let session_id = SessionId::hash(b"session_id"); - exchange_stake_chain_info(&mut operators, OPERATORS_NUM).await?; + exchange_stake_chain_info(&mut operators, OPERATORS_NUM, stake_chain_id).await?; exchange_deposit_setup(&mut operators, OPERATORS_NUM, scope).await?; exchange_deposit_nonces(&mut operators, OPERATORS_NUM, session_id).await?; exchange_deposit_sigs(&mut operators, OPERATORS_NUM, session_id).await?; diff --git a/crates/p2p-impl/src/tests/request.rs b/crates/p2p-impl/src/tests/request.rs index b446d001..0413994a 100644 --- a/crates/p2p-impl/src/tests/request.rs +++ b/crates/p2p-impl/src/tests/request.rs @@ -26,7 +26,13 @@ async fn request_response() -> anyhow::Result<()> { } = Setup::all_to_all(OPERATORS_NUM).await?; // last operator won't send his info to others - exchange_stake_chain_info(&mut operators[..OPERATORS_NUM - 1], OPERATORS_NUM - 1).await?; + let stake_chain_id = StakeChainId::hash(b"stake_chain_id"); + exchange_stake_chain_info( + &mut operators[..OPERATORS_NUM - 1], + OPERATORS_NUM - 1, + stake_chain_id, + ) + .await?; // create command to request info from the last operator let operator_pk: OperatorPubKey = operators[OPERATORS_NUM - 1].kp.public().clone().into(); @@ -37,16 +43,15 @@ async fn request_response() -> anyhow::Result<()> { }); // put data in the last operator, so that he can respond it - match mock_stake_chain_info(&operators[OPERATORS_NUM - 1].kp.clone()) { + match mock_stake_chain_info(&operators[OPERATORS_NUM - 1].kp.clone(), stake_chain_id) { Command::PublishMessage(msg) => match msg.msg { UnsignedPublishMessage::StakeChainExchange { stake_chain_id, - pre_stake_outpoint, - checkpoint_pubkeys, - stake_data, + pre_stake_txid, + pre_stake_vout, } => { let entry = StakeChainEntry { - entry: (pre_stake_outpoint, checkpoint_pubkeys, stake_data), + entry: (pre_stake_txid, pre_stake_vout), signature: msg.signature, key: msg.key, };