From d14e5b402b41d4524a70c0a1fe6e147551610109 Mon Sep 17 00:00:00 2001 From: Hank Stoever Date: Wed, 15 May 2024 13:09:26 -0700 Subject: [PATCH] feat: update sign_coordinator and blind_signer for new message types --- .../stacks-node/src/nakamoto_node/miner.rs | 52 +++++++- .../src/nakamoto_node/sign_coordinator.rs | 113 ++++++++++++++++-- .../src/tests/nakamoto_integrations.rs | 38 ++++-- 3 files changed, 174 insertions(+), 29 deletions(-) diff --git a/testnet/stacks-node/src/nakamoto_node/miner.rs b/testnet/stacks-node/src/nakamoto_node/miner.rs index 6ee63ef0fe7..42009d53804 100644 --- a/testnet/stacks-node/src/nakamoto_node/miner.rs +++ b/testnet/stacks-node/src/nakamoto_node/miner.rs @@ -321,7 +321,7 @@ impl BlockMinerThread { })?; *attempts += 1; - let signature = coordinator.begin_sign( + let signature = coordinator.begin_sign_v1( new_block, burn_block_height, *attempts, @@ -339,10 +339,15 @@ impl BlockMinerThread { fn gather_signatures( &mut self, new_block: &mut NakamotoBlock, - _burn_block_height: u64, - _stackerdbs: &mut StackerDBs, - _attempts: &mut u64, + burn_block_height: u64, + stackerdbs: &mut StackerDBs, + attempts: &mut u64, ) -> Result<(RewardSet, Vec), NakamotoNodeError> { + let Some(miner_privkey) = self.config.miner.mining_key else { + return Err(NakamotoNodeError::MinerConfigurationFailed( + "No mining key configured, cannot mine", + )); + }; let sort_db = SortitionDB::open( &self.config.get_burn_db_file_path(), true, @@ -356,6 +361,15 @@ impl BlockMinerThread { .expect("FATAL: could not retrieve chain tip") .expect("FATAL: could not retrieve chain tip"); + let reward_cycle = self + .burnchain + .pox_constants + .block_height_to_reward_cycle( + self.burnchain.first_block_height, + self.burn_block.block_height, + ) + .expect("FATAL: building on a burn block that is before the first burn block"); + let reward_info = match sort_db.get_preprocessed_reward_set_of(&tip.sortition_id) { Ok(Some(x)) => x, Ok(None) => { @@ -376,8 +390,34 @@ impl BlockMinerThread { )); }; - // TODO: collect signatures from signers - return Ok((reward_set, vec![])); + let miner_privkey_as_scalar = Scalar::from(miner_privkey.as_slice().clone()); + let mut coordinator = SignCoordinator::new( + &reward_set, + reward_cycle, + miner_privkey_as_scalar, + Point::new(), + &stackerdbs, + &self.config, + ) + .map_err(|e| { + NakamotoNodeError::SigningCoordinatorFailure(format!( + "Failed to initialize the signing coordinator. Cannot mine! {e:?}" + )) + })?; + + *attempts += 1; + let signature = coordinator.begin_sign_v0( + new_block, + burn_block_height, + *attempts, + &tip, + &self.burnchain, + &sort_db, + &stackerdbs, + &self.globals.counters, + )?; + + return Ok((reward_set, signature)); } fn get_stackerdb_contract_and_slots( diff --git a/testnet/stacks-node/src/nakamoto_node/sign_coordinator.rs b/testnet/stacks-node/src/nakamoto_node/sign_coordinator.rs index 764ae60c3c4..49204166abb 100644 --- a/testnet/stacks-node/src/nakamoto_node/sign_coordinator.rs +++ b/testnet/stacks-node/src/nakamoto_node/sign_coordinator.rs @@ -17,7 +17,8 @@ use std::sync::mpsc::Receiver; use std::time::{Duration, Instant}; use hashbrown::{HashMap, HashSet}; -use libsigner::v1::messages::{MessageSlotID, SignerMessage}; +use libsigner::v0::messages::SignerMessage as SignerMessageV0; +use libsigner::v1::messages::{MessageSlotID, SignerMessage as SignerMessageV1}; use libsigner::{BlockProposal, SignerEntries, SignerEvent, SignerSession, StackerDBSession}; use stacks::burnchains::Burnchain; use stacks::chainstate::burn::db::sortdb::SortitionDB; @@ -28,6 +29,7 @@ use stacks::chainstate::stacks::events::StackerDBChunksEvent; use stacks::chainstate::stacks::{Error as ChainstateError, ThresholdSignature}; use stacks::libstackerdb::StackerDBChunkData; use stacks::net::stackerdb::StackerDBs; +use stacks::util::secp256k1::MessageSignature; use stacks::util_lib::boot::boot_code_id; use stacks_common::bitvec::BitVec; use stacks_common::codec::StacksMessageCodec; @@ -140,10 +142,10 @@ fn get_signer_commitments( ); continue; }; - let Ok(SignerMessage::DkgResults { + let Ok(SignerMessageV1::DkgResults { aggregate_key, party_polynomials, - }) = SignerMessage::consensus_deserialize(&mut signer_data.as_slice()) + }) = SignerMessageV1::consensus_deserialize(&mut signer_data.as_slice()) else { warn!( "Failed to parse DKG result, will look for results from other signers."; @@ -314,12 +316,12 @@ impl SignCoordinator { .expect("FATAL: tried to initialize WSTS coordinator before first burn block height") } - fn send_signers_message( + fn send_signers_message( message_key: &Scalar, sortdb: &SortitionDB, tip: &BlockSnapshot, stackerdbs: &StackerDBs, - message: SignerMessage, + message: M, is_mainnet: bool, miners_session: &mut StackerDBSession, ) -> Result<(), String> { @@ -363,7 +365,7 @@ impl SignCoordinator { } #[cfg_attr(test, mutants::skip)] - pub fn begin_sign( + pub fn begin_sign_v1( &mut self, block: &NakamotoBlock, burn_block_height: u64, @@ -397,7 +399,7 @@ impl SignCoordinator { "Failed to start signing round in FIRE coordinator: {e:?}" )) })?; - Self::send_signers_message( + Self::send_signers_message::( &self.message_key, sortdb, burn_tip, @@ -483,11 +485,11 @@ impl SignCoordinator { let packets: Vec<_> = messages .into_iter() .filter_map(|msg| match msg { - SignerMessage::DkgResults { .. } - | SignerMessage::BlockResponse(_) - | SignerMessage::EncryptedSignerState(_) - | SignerMessage::Transactions(_) => None, - SignerMessage::Packet(packet) => { + SignerMessageV1::DkgResults { .. } + | SignerMessageV1::BlockResponse(_) + | SignerMessageV1::EncryptedSignerState(_) + | SignerMessageV1::Transactions(_) => None, + SignerMessageV1::Packet(packet) => { debug!("Received signers packet: {packet:?}"); if !packet.verify(&self.wsts_public_keys, &coordinator_pk) { warn!("Failed to verify StackerDB packet: {packet:?}"); @@ -548,7 +550,7 @@ impl SignCoordinator { } } for msg in outbound_msgs { - match Self::send_signers_message( + match Self::send_signers_message::( &self.message_key, sortdb, burn_tip, @@ -573,4 +575,89 @@ impl SignCoordinator { "Timed out waiting for group signature".into(), )) } + + pub fn begin_sign_v0( + &mut self, + block: &NakamotoBlock, + burn_block_height: u64, + block_attempt: u64, + burn_tip: &BlockSnapshot, + burnchain: &Burnchain, + sortdb: &SortitionDB, + stackerdbs: &StackerDBs, + counters: &Counters, + ) -> Result, NakamotoNodeError> { + let sign_id = Self::get_sign_id(burn_tip.block_height, burnchain); + let sign_iter_id = block_attempt; + let reward_cycle_id = burnchain + .block_height_to_reward_cycle(burn_tip.block_height) + .expect("FATAL: tried to initialize coordinator before first burn block height"); + self.coordinator.current_sign_id = sign_id; + self.coordinator.current_sign_iter_id = sign_iter_id; + + let block_proposal = BlockProposal { + block: block.clone(), + burn_height: burn_block_height, + reward_cycle: reward_cycle_id, + }; + + let block_proposal_message = SignerMessageV0::BlockProposal(block_proposal); + Self::send_signers_message::( + &self.message_key, + sortdb, + burn_tip, + &stackerdbs, + block_proposal_message, + self.is_mainnet, + &mut self.miners_session, + ) + .map_err(NakamotoNodeError::SigningCoordinatorFailure)?; + counters.bump_naka_proposed_blocks(); + #[cfg(test)] + { + // In test mode, short-circuit waiting for the signers if the TEST_SIGNING + // channel has been created. This allows integration tests for the stacks-node + // independent of the stacks-signer. + if let Some(signatures) = + crate::tests::nakamoto_integrations::TestSigningChannel::get_signature() + { + debug!("Short-circuiting waiting for signers, using test signature"); + return Ok(signatures); + } + } + + let Some(ref mut receiver) = self.receiver else { + return Err(NakamotoNodeError::SigningCoordinatorFailure( + "Failed to obtain the StackerDB event receiver".into(), + )); + }; + + let start_ts = Instant::now(); + while start_ts.elapsed() <= self.signing_round_timeout { + let event = match receiver.recv_timeout(EVENT_RECEIVER_POLL) { + Ok(event) => event, + Err(std::sync::mpsc::RecvTimeoutError::Timeout) => { + continue; + } + Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => { + return Err(NakamotoNodeError::SigningCoordinatorFailure( + "StackerDB event receiver disconnected".into(), + )) + } + }; + + let is_signer_event = + event.contract_id.name.starts_with(SIGNERS_NAME) && event.contract_id.is_boot(); + if !is_signer_event { + debug!("Ignoring StackerDB event for non-signer contract"; "contract" => %event.contract_id); + continue; + } + + // TODO: get messages from signers + } + + Err(NakamotoNodeError::SignerSignatureError( + "Timed out waiting for group signature".into(), + )) + } } diff --git a/testnet/stacks-node/src/tests/nakamoto_integrations.rs b/testnet/stacks-node/src/tests/nakamoto_integrations.rs index 4204fe5ba42..4cdb11d8f81 100644 --- a/testnet/stacks-node/src/tests/nakamoto_integrations.rs +++ b/testnet/stacks-node/src/tests/nakamoto_integrations.rs @@ -26,7 +26,8 @@ use clarity::vm::costs::ExecutionCost; use clarity::vm::types::{PrincipalData, QualifiedContractIdentifier}; use http_types::headers::AUTHORIZATION; use lazy_static::lazy_static; -use libsigner::v1::messages::SignerMessage; +use libsigner::v0::messages::SignerMessage as SignerMessageV0; +use libsigner::v1::messages::SignerMessage as SignerMessageV1; use libsigner::{BlockProposal, SignerSession, StackerDBSession}; use rand::RngCore; use stacks::burnchains::{MagicBytes, Txid}; @@ -314,24 +315,41 @@ pub fn get_latest_block_proposal( let proposed_block = { let miner_contract_id = boot_code_id(MINERS_NAME, false); let mut miners_stackerdb = StackerDBSession::new(&conf.node.rpc_bind, miner_contract_id); - let message: SignerMessage = miners_stackerdb + let message: SignerMessageV0 = miners_stackerdb .get_latest(miner_slot_id.start) .expect("Failed to get latest chunk from the miner slot ID") .expect("No chunk found"); - let SignerMessage::Packet(packet) = message else { - panic!("Expected a signer message packet. Got {message:?}"); + let SignerMessageV0::BlockProposal(block_proposal) = message else { + panic!("Expected a signer message block proposal. Got {message:?}"); }; - let Message::NonceRequest(nonce_request) = packet.msg else { - panic!("Expected a nonce request. Got {:?}", packet.msg); - }; - let block_proposal = - BlockProposal::consensus_deserialize(&mut nonce_request.message.as_slice()) - .expect("Failed to deserialize block proposal"); + // TODO: use v1 message types behind epoch gate + // get_block_proposal_msg_v1(&mut miners_stackerdb, miner_slot_id.start); block_proposal.block }; Ok(proposed_block) } +#[allow(dead_code)] +fn get_block_proposal_msg_v1( + miners_stackerdb: &mut StackerDBSession, + slot_id: u32, +) -> NakamotoBlock { + let message: SignerMessageV1 = miners_stackerdb + .get_latest(slot_id) + .expect("Failed to get latest chunk from the miner slot ID") + .expect("No chunk found"); + let SignerMessageV1::Packet(packet) = message else { + panic!("Expected a signer message packet. Got {message:?}"); + }; + let Message::NonceRequest(nonce_request) = packet.msg else { + panic!("Expected a nonce request. Got {:?}", packet.msg); + }; + let block_proposal = + BlockProposal::consensus_deserialize(&mut nonce_request.message.as_slice()) + .expect("Failed to deserialize block proposal"); + block_proposal.block +} + pub fn read_and_sign_block_proposal( conf: &Config, signers: &TestSigners,