From a574768cadf3d6c3d2d307db273ad223df3a96ff Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Tue, 13 Aug 2024 08:19:24 -0400 Subject: [PATCH 01/20] Add pre_nakamoto_miner_messaging option to MinerConfig Signed-off-by: Jacinta Ferrant --- testnet/stacks-node/src/config.rs | 5 +++++ testnet/stacks-node/src/tests/signer/v0.rs | 3 +-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/testnet/stacks-node/src/config.rs b/testnet/stacks-node/src/config.rs index 4eef0bbdd0..04def859d1 100644 --- a/testnet/stacks-node/src/config.rs +++ b/testnet/stacks-node/src/config.rs @@ -2332,6 +2332,8 @@ pub struct MinerConfig { pub max_reorg_depth: u64, /// Amount of time while mining in nakamoto to wait for signers to respond to a proposed block pub wait_on_signers: Duration, + /// Whether to send miner messages in Epoch 2.5 through the .miners contract. This is used for testing. + pub pre_nakamoto_miner_messaging: bool, } impl Default for MinerConfig { @@ -2362,6 +2364,7 @@ impl Default for MinerConfig { max_reorg_depth: 3, // TODO: update to a sane value based on stackerdb benchmarking wait_on_signers: Duration::from_secs(200), + pre_nakamoto_miner_messaging: true, } } } @@ -2693,6 +2696,7 @@ pub struct MinerConfigFile { pub filter_origins: Option, pub max_reorg_depth: Option, pub wait_on_signers_ms: Option, + pub pre_nakamoto_miner_messaging: Option, } impl MinerConfigFile { @@ -2795,6 +2799,7 @@ impl MinerConfigFile { .wait_on_signers_ms .map(Duration::from_millis) .unwrap_or(miner_default_config.wait_on_signers), + pre_nakamoto_miner_messaging: self.pre_nakamoto_miner_messaging.unwrap_or(true) }) } } diff --git a/testnet/stacks-node/src/tests/signer/v0.rs b/testnet/stacks-node/src/tests/signer/v0.rs index f589416746..705cb2e011 100644 --- a/testnet/stacks-node/src/tests/signer/v0.rs +++ b/testnet/stacks-node/src/tests/signer/v0.rs @@ -49,7 +49,6 @@ use stacks_common::bitvec::BitVec; use stacks_signer::chainstate::{ProposalEvalConfig, SortitionsView}; use stacks_signer::client::{SignerSlotID, StackerDB}; use stacks_signer::config::{build_signer_config_tomls, GlobalConfig as SignerConfig, Network}; -use stacks_signer::runloop::State; use stacks_signer::v0::SpawnedSigner; use tracing_subscriber::prelude::*; use tracing_subscriber::{fmt, EnvFilter}; @@ -61,7 +60,7 @@ use crate::nakamoto_node::miner::TEST_BROADCAST_STALL; use crate::neon::Counters; use crate::run_loop::boot_nakamoto; use crate::tests::nakamoto_integrations::{ - boot_to_epoch_25, boot_to_epoch_3_reward_set, boot_to_epoch_3_reward_set_calculation_boundary, + boot_to_epoch_25, boot_to_epoch_3_reward_set, next_block_and, setup_epoch_3_reward_set, wait_for, POX_4_DEFAULT_STACKER_BALANCE, POX_4_DEFAULT_STACKER_STX_AMT, }; From 4e37d0bdaec28f27c2a252a1b6b9226cbc0a8e02 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Tue, 13 Aug 2024 15:23:58 -0400 Subject: [PATCH 02/20] Have miners respond to mock signature messages in epoch 2.5 via stackerdb Signed-off-by: Jacinta Ferrant --- libsigner/src/v0/messages.rs | 84 +++++++++- testnet/stacks-node/src/config.rs | 2 +- .../src/nakamoto_node/sign_coordinator.rs | 4 + testnet/stacks-node/src/neon_node.rs | 147 +++++++++++++++++- testnet/stacks-node/src/tests/signer/v0.rs | 5 +- 5 files changed, 232 insertions(+), 10 deletions(-) diff --git a/libsigner/src/v0/messages.rs b/libsigner/src/v0/messages.rs index 7d411f89b5..af7c38e22a 100644 --- a/libsigner/src/v0/messages.rs +++ b/libsigner/src/v0/messages.rs @@ -88,9 +88,18 @@ MinerSlotID { /// Block proposal from the miner BlockProposal = 0, /// Block pushed from the miner - BlockPushed = 1 + BlockPushed = 1, + /// Mock message from the miner + MockMinerMessage = 2 }); +#[cfg_attr(test, mutants::skip)] +impl Display for MinerSlotID { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}({})", self, self.to_u8()) + } +} + impl MessageSlotIDTrait for MessageSlotID { fn stacker_db_contract(&self, mainnet: bool, reward_cycle: u64) -> QualifiedContractIdentifier { NakamotoSigners::make_signers_db_contract_id(reward_cycle, self.to_u32(), mainnet) @@ -116,7 +125,9 @@ SignerMessageTypePrefix { /// Block Pushed message from miners BlockPushed = 2, /// Mock Signature message from Epoch 2.5 signers - MockSignature = 3 + MockSignature = 3, + /// Mock Pre-Nakamoto message from Epoch 2.5 miners + MockMinerMessage = 4 }); #[cfg_attr(test, mutants::skip)] @@ -160,6 +171,7 @@ impl From<&SignerMessage> for SignerMessageTypePrefix { SignerMessage::BlockResponse(_) => SignerMessageTypePrefix::BlockResponse, SignerMessage::BlockPushed(_) => SignerMessageTypePrefix::BlockPushed, SignerMessage::MockSignature(_) => SignerMessageTypePrefix::MockSignature, + SignerMessage::MockMinerMessage(_) => SignerMessageTypePrefix::MockMinerMessage, } } } @@ -175,6 +187,8 @@ pub enum SignerMessage { BlockPushed(NakamotoBlock), /// A mock signature from the epoch 2.5 signers MockSignature(MockSignature), + /// A mock message from the epoch 2.5 miners + MockMinerMessage(MockMinerMessage), } impl SignerMessage { @@ -184,7 +198,7 @@ impl SignerMessage { #[cfg_attr(test, mutants::skip)] pub fn msg_id(&self) -> Option { match self { - Self::BlockProposal(_) | Self::BlockPushed(_) => None, + Self::BlockProposal(_) | Self::BlockPushed(_) | Self::MockMinerMessage(_) => None, Self::BlockResponse(_) => Some(MessageSlotID::BlockResponse), Self::MockSignature(_) => Some(MessageSlotID::MockSignature), } @@ -201,6 +215,7 @@ impl StacksMessageCodec for SignerMessage { SignerMessage::BlockResponse(block_response) => block_response.consensus_serialize(fd), SignerMessage::BlockPushed(block) => block.consensus_serialize(fd), SignerMessage::MockSignature(signature) => signature.consensus_serialize(fd), + SignerMessage::MockMinerMessage(message) => message.consensus_serialize(fd), }?; Ok(()) } @@ -226,6 +241,10 @@ impl StacksMessageCodec for SignerMessage { let signature = StacksMessageCodec::consensus_deserialize(fd)?; SignerMessage::MockSignature(signature) } + SignerMessageTypePrefix::MockMinerMessage => { + let message = StacksMessageCodec::consensus_deserialize(fd)?; + SignerMessage::MockMinerMessage(message) + } }; Ok(message) } @@ -441,6 +460,43 @@ impl StacksMessageCodec for MockSignature { } } +/// A mock message for the stacks node to be used for mock mining messages +/// This is only used by Epoch 2.5 miners to simulate miners responding to mock signatures +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct MockMinerMessage { + /// The view of the stacks node peer information at the time of the mock signature + pub peer_info: PeerInfo, + /// The burn block height of the miner's tenure + pub tenure_burn_block_height: u64, + /// The chain id for the mock signature + pub chain_id: u32, + /// The mock signatures that the miner received + pub mock_signatures: Vec, +} + +impl StacksMessageCodec for MockMinerMessage { + fn consensus_serialize(&self, fd: &mut W) -> Result<(), CodecError> { + self.peer_info.consensus_serialize(fd)?; + write_next(fd, &self.tenure_burn_block_height)?; + write_next(fd, &self.chain_id)?; + write_next(fd, &self.mock_signatures)?; + Ok(()) + } + + fn consensus_deserialize(fd: &mut R) -> Result { + let peer_info = PeerInfo::consensus_deserialize(fd)?; + let tenure_burn_block_height = read_next::(fd)?; + let chain_id = read_next::(fd)?; + let mock_signatures = read_next::, _>(fd)?; + Ok(Self { + peer_info, + tenure_burn_block_height, + chain_id, + mock_signatures, + }) + } +} + define_u8_enum!( /// Enum representing the reject code type prefix RejectCodeTypePrefix { @@ -940,4 +996,26 @@ mod test { .expect("Failed to deserialize MockSignData"); assert_eq!(sign_data, deserialized_data); } + + #[test] + fn serde_mock_miner_message() { + let mock_signature_1 = MockSignature { + signature: MessageSignature::empty(), + sign_data: random_mock_sign_data(), + }; + let mock_signature_2 = MockSignature { + signature: MessageSignature::empty(), + sign_data: random_mock_sign_data(), + }; + let mock_miner_message = MockMinerMessage { + peer_info: random_peer_data(), + tenure_burn_block_height: thread_rng().next_u64(), + chain_id: thread_rng().gen_range(0..=1), + mock_signatures: vec![mock_signature_1, mock_signature_2], + }; + let serialized_data = mock_miner_message.serialize_to_vec(); + let deserialized_data = read_next::(&mut &serialized_data[..]) + .expect("Failed to deserialize MockSignData"); + assert_eq!(mock_miner_message, deserialized_data); + } } diff --git a/testnet/stacks-node/src/config.rs b/testnet/stacks-node/src/config.rs index 04def859d1..4528e07222 100644 --- a/testnet/stacks-node/src/config.rs +++ b/testnet/stacks-node/src/config.rs @@ -2799,7 +2799,7 @@ impl MinerConfigFile { .wait_on_signers_ms .map(Duration::from_millis) .unwrap_or(miner_default_config.wait_on_signers), - pre_nakamoto_miner_messaging: self.pre_nakamoto_miner_messaging.unwrap_or(true) + pre_nakamoto_miner_messaging: self.pre_nakamoto_miner_messaging.unwrap_or(true), }) } } diff --git a/testnet/stacks-node/src/nakamoto_node/sign_coordinator.rs b/testnet/stacks-node/src/nakamoto_node/sign_coordinator.rs index 6a5f026a16..b366d93132 100644 --- a/testnet/stacks-node/src/nakamoto_node/sign_coordinator.rs +++ b/testnet/stacks-node/src/nakamoto_node/sign_coordinator.rs @@ -774,6 +774,10 @@ impl SignCoordinator { debug!("Received mock signature message. Ignoring."); continue; } + SignerMessageV0::MockMinerMessage(_) => { + debug!("Received mock miner message. Ignoring."); + continue; + } }; let block_sighash = block.header.signer_signature_hash(); if block_sighash != response_hash { diff --git a/testnet/stacks-node/src/neon_node.rs b/testnet/stacks-node/src/neon_node.rs index 8c3c4ed179..f3170a1c00 100644 --- a/testnet/stacks-node/src/neon_node.rs +++ b/testnet/stacks-node/src/neon_node.rs @@ -147,9 +147,14 @@ use std::thread::JoinHandle; use std::time::Duration; use std::{fs, mem, thread}; +use clarity::boot_util::boot_code_id; use clarity::vm::ast::ASTRules; use clarity::vm::costs::ExecutionCost; use clarity::vm::types::{PrincipalData, QualifiedContractIdentifier}; +use libsigner::v0::messages::{ + MessageSlotID, MinerSlotID, MockMinerMessage, MockSignature, PeerInfo, SignerMessage, +}; +use libsigner::StackerDBSession; use stacks::burnchains::bitcoin::address::{BitcoinAddress, LegacyBitcoinAddressType}; use stacks::burnchains::db::BurnchainHeaderReader; use stacks::burnchains::{Burnchain, BurnchainSigner, PoxConstants, Txid}; @@ -164,10 +169,11 @@ use stacks::chainstate::burn::{BlockSnapshot, ConsensusHash}; use stacks::chainstate::coordinator::{get_next_recipients, OnChainRewardSetProvider}; use stacks::chainstate::nakamoto::NakamotoChainState; use stacks::chainstate::stacks::address::PoxAddress; +use stacks::chainstate::stacks::boot::MINERS_NAME; use stacks::chainstate::stacks::db::blocks::StagingBlock; use stacks::chainstate::stacks::db::{StacksChainState, StacksHeaderInfo, MINER_REWARD_MATURITY}; use stacks::chainstate::stacks::miner::{ - signal_mining_blocked, signal_mining_ready, BlockBuilderSettings, StacksMicroblockBuilder, + signal_mining_blocked, signal_mining_ready, BlockBuilderSettings, StacksMicroblockBuilder }; use stacks::chainstate::stacks::{ CoinbasePayload, Error as ChainstateError, StacksBlock, StacksBlockBuilder, StacksBlockHeader, @@ -178,7 +184,6 @@ use stacks::core::mempool::MemPoolDB; use stacks::core::{FIRST_BURNCHAIN_CONSENSUS_HASH, STACKS_EPOCH_3_0_MARKER}; use stacks::cost_estimates::metrics::{CostMetric, UnitMetric}; use stacks::cost_estimates::{CostEstimator, FeeEstimator, UnitEstimator}; -use stacks::monitoring; use stacks::monitoring::{increment_stx_blocks_mined_counter, update_active_miners_count_gauge}; use stacks::net::atlas::{AtlasConfig, AtlasDB}; use stacks::net::db::{LocalPeer, PeerDB}; @@ -190,6 +195,7 @@ use stacks::net::{ Error as NetError, NetworkResult, PeerNetworkComms, RPCHandlerArgs, ServiceFlags, }; use stacks::util_lib::strings::{UrlString, VecDisplay}; +use stacks::{monitoring, version_string}; use stacks_common::codec::StacksMessageCodec; use stacks_common::types::chainstate::{ BlockHeaderHash, BurnchainHeaderHash, SortitionId, StacksAddress, StacksBlockId, @@ -210,6 +216,7 @@ use crate::burnchains::make_bitcoin_indexer; use crate::chain_data::MinerStats; use crate::config::NodeConfig; use crate::globals::{NeonGlobals as Globals, RelayerDirective}; +use crate::nakamoto_node::sign_coordinator::SignCoordinator; use crate::run_loop::neon::RunLoop; use crate::run_loop::RegisteredKey; use crate::ChainTip; @@ -2255,6 +2262,133 @@ impl BlockMinerThread { return false; } + /// Read any mock signatures from stackerdb and respond to them + pub fn respond_to_mock_signatures(&mut self) -> Result<(), ChainstateError> { + let miner_config = self.config.get_miner_config(); + if miner_config.pre_nakamoto_miner_messaging { + debug!("Pre-Nakamoto miner messaging is disabled"); + return Ok(()); + } + + let burn_db_path = self.config.get_burn_db_file_path(); + let burn_db = SortitionDB::open(&burn_db_path, false, self.burnchain.pox_constants.clone()) + .expect("FATAL: could not open sortition DB"); + + let target_epoch_id = + SortitionDB::get_stacks_epoch(burn_db.conn(), self.burn_block.block_height + 1)? + .expect("FATAL: no epoch defined") + .epoch_id; + if target_epoch_id != StacksEpochId::Epoch25 { + debug!("Mock signing is disabled for non-epoch 2.5 blocks."; + "target_epoch_id" => target_epoch_id.to_string() + ); + return Ok(()); + } + // Retrieve any MockSignatures from stackerdb + let mut mock_signatures = Vec::new(); + let reward_cycle = self + .burnchain + .block_height_to_reward_cycle(self.burn_block.block_height) + .expect("BUG: block commit exists before first block height"); + let signers_contract_id = MessageSlotID::MockSignature + .stacker_db_contract(self.config.is_mainnet(), reward_cycle); + // Get the slots for every signer + let stackerdbs = StackerDBs::connect(&self.config.get_stacker_db_file_path(), false)?; + let slot_ids: Vec<_> = stackerdbs + .get_signers(&signers_contract_id) + .expect("FATAL: could not get signers from stacker DB") + .into_iter() + .enumerate() + .map(|(slot_id, _)| { + u32::try_from(slot_id).expect("FATAL: too many signers to fit into u32 range") + }) + .collect(); + let chunks = stackerdbs.get_latest_chunks(&signers_contract_id, &slot_ids)?; + for chunk in chunks { + if let Some(chunk) = chunk { + match MockSignature::consensus_deserialize(&mut chunk.as_slice()) { + Ok(mock_signature) => { + if mock_signature.sign_data.event_burn_block_height + == self.burn_block.block_height + { + mock_signatures.push(mock_signature); + } + } + Err(e) => { + warn!("Failed to deserialize mock signature: {:?}", &e); + continue; + } + } + } + } + info!( + "Miner responding to {} mock signatures", + mock_signatures.len() + ); + let miner_contract_id = boot_code_id(MINERS_NAME, self.config.is_mainnet()); + let mut miners_stackerdb = + StackerDBSession::new(&self.config.node.rpc_bind, miner_contract_id); + + let p2p_net = StacksNode::setup_peer_network( + &self.config, + &self.config.atlas, + self.burnchain.clone(), + ); + + let server_version = version_string( + "stacks-node", + option_env!("STACKS_NODE_VERSION") + .or(option_env!("CARGO_PKG_VERSION")) + .unwrap_or("0.0.0.0"), + ); + let stacks_tip_height = p2p_net.stacks_tip.height; + let stacks_tip = p2p_net.stacks_tip.block_hash.clone(); + let stacks_tip_consensus_hash = p2p_net.stacks_tip.consensus_hash.clone(); + let pox_consensus = p2p_net.burnchain_tip.consensus_hash.clone(); + let burn_block_height = p2p_net.chain_view.burn_block_height; + + let peer_info = PeerInfo { + burn_block_height, + stacks_tip_consensus_hash, + stacks_tip, + stacks_tip_height, + pox_consensus, + server_version, + }; + + info!("Responding to mock signatures for burn block {:?}", &self.burn_block.block_height; + "stacks_tip_consensus_hash" => ?peer_info.stacks_tip_consensus_hash.clone(), + "stacks_tip" => ?peer_info.stacks_tip.clone(), + "peer_burn_block_height" => peer_info.burn_block_height, + "pox_consensus" => ?peer_info.pox_consensus.clone(), + "server_version" => peer_info.server_version.clone(), + "chain_id" => self.config.burnchain.chain_id + ); + let message = MockMinerMessage { + peer_info, + tenure_burn_block_height: self.burn_block.block_height, + chain_id: self.config.burnchain.chain_id, + mock_signatures, + }; + let sort_db = SortitionDB::open(&burn_db_path, true, self.burnchain.pox_constants.clone()) + .expect("FATAL: failed to open burnchain DB"); + + if let Err(e) = SignCoordinator::send_miners_message( + &miner_config.mining_key.expect("BUG: no mining key"), + &sort_db, + &self.burn_block, + &stackerdbs, + SignerMessage::MockMinerMessage(message), + MinerSlotID::MockMinerMessage, + self.config.is_mainnet(), + &mut miners_stackerdb, + &self.burn_block.consensus_hash, + ) { + warn!("Failed to send mock miner message: {:?}", &e); + } + Ok(()) + } + // TODO: add tests from mutation testing results #4871 #[cfg_attr(test, mutants::skip)] /// Try to mine a Stacks block by assembling one from mempool transactions and sending a @@ -3595,7 +3729,14 @@ impl RelayerThread { if let Ok(miner_handle) = thread::Builder::new() .name(format!("miner-block-{}", self.local_peer.data_url)) .stack_size(BLOCK_PROCESSOR_STACK_SIZE) - .spawn(move || miner_thread_state.run_tenure()) + .spawn(move || { + let result = miner_thread_state.run_tenure(); + if let Err(e) = miner_thread_state.respond_to_mock_signatures() { + warn!("Failed to respond to mock signatures: {}", e); + } + result + + }) .map_err(|e| { error!("Relayer: Failed to start tenure thread: {:?}", &e); e diff --git a/testnet/stacks-node/src/tests/signer/v0.rs b/testnet/stacks-node/src/tests/signer/v0.rs index 705cb2e011..4b3fea46a0 100644 --- a/testnet/stacks-node/src/tests/signer/v0.rs +++ b/testnet/stacks-node/src/tests/signer/v0.rs @@ -60,9 +60,8 @@ use crate::nakamoto_node::miner::TEST_BROADCAST_STALL; use crate::neon::Counters; use crate::run_loop::boot_nakamoto; use crate::tests::nakamoto_integrations::{ - boot_to_epoch_25, boot_to_epoch_3_reward_set, - next_block_and, setup_epoch_3_reward_set, wait_for, POX_4_DEFAULT_STACKER_BALANCE, - POX_4_DEFAULT_STACKER_STX_AMT, + boot_to_epoch_25, boot_to_epoch_3_reward_set, next_block_and, setup_epoch_3_reward_set, + wait_for, POX_4_DEFAULT_STACKER_BALANCE, POX_4_DEFAULT_STACKER_STX_AMT, }; use crate::tests::neon_integrations::{ get_account, get_chain_info, next_block_and_wait, run_until_burnchain_height, submit_tx, From c6c3aa4dafaecd87dd1af7deb02a16cb9a834dd1 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Tue, 13 Aug 2024 15:25:48 -0400 Subject: [PATCH 03/20] Rust fmt Signed-off-by: Jacinta Ferrant --- testnet/stacks-node/src/neon_node.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/testnet/stacks-node/src/neon_node.rs b/testnet/stacks-node/src/neon_node.rs index f3170a1c00..6cbbd3b9f6 100644 --- a/testnet/stacks-node/src/neon_node.rs +++ b/testnet/stacks-node/src/neon_node.rs @@ -173,7 +173,7 @@ use stacks::chainstate::stacks::boot::MINERS_NAME; use stacks::chainstate::stacks::db::blocks::StagingBlock; use stacks::chainstate::stacks::db::{StacksChainState, StacksHeaderInfo, MINER_REWARD_MATURITY}; use stacks::chainstate::stacks::miner::{ - signal_mining_blocked, signal_mining_ready, BlockBuilderSettings, StacksMicroblockBuilder + signal_mining_blocked, signal_mining_ready, BlockBuilderSettings, StacksMicroblockBuilder, }; use stacks::chainstate::stacks::{ CoinbasePayload, Error as ChainstateError, StacksBlock, StacksBlockBuilder, StacksBlockHeader, @@ -3735,8 +3735,7 @@ impl RelayerThread { warn!("Failed to respond to mock signatures: {}", e); } result - - }) + }) .map_err(|e| { error!("Relayer: Failed to start tenure thread: {:?}", &e); e From 5d77eabf6c33cf1fed539cbb9bf8afdf236db742 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Wed, 14 Aug 2024 15:20:53 -0400 Subject: [PATCH 04/20] WIP: add test for mock miner messages. Failing to write to miner slot Signed-off-by: Jacinta Ferrant --- libsigner/src/v0/messages.rs | 11 +- stackslib/src/chainstate/nakamoto/mod.rs | 1 - testnet/stacks-node/src/neon_node.rs | 72 +++++++----- testnet/stacks-node/src/tests/signer/v0.rs | 122 ++++++++++++++++++++- 4 files changed, 167 insertions(+), 39 deletions(-) diff --git a/libsigner/src/v0/messages.rs b/libsigner/src/v0/messages.rs index af7c38e22a..117a8c4912 100644 --- a/libsigner/src/v0/messages.rs +++ b/libsigner/src/v0/messages.rs @@ -88,18 +88,9 @@ MinerSlotID { /// Block proposal from the miner BlockProposal = 0, /// Block pushed from the miner - BlockPushed = 1, - /// Mock message from the miner - MockMinerMessage = 2 + BlockPushed = 1 }); -#[cfg_attr(test, mutants::skip)] -impl Display for MinerSlotID { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}({})", self, self.to_u8()) - } -} - impl MessageSlotIDTrait for MessageSlotID { fn stacker_db_contract(&self, mainnet: bool, reward_cycle: u64) -> QualifiedContractIdentifier { NakamotoSigners::make_signers_db_contract_id(reward_cycle, self.to_u32(), mainnet) diff --git a/stackslib/src/chainstate/nakamoto/mod.rs b/stackslib/src/chainstate/nakamoto/mod.rs index d059a96cb6..7e70c9940c 100644 --- a/stackslib/src/chainstate/nakamoto/mod.rs +++ b/stackslib/src/chainstate/nakamoto/mod.rs @@ -4210,7 +4210,6 @@ impl NakamotoChainState { "stackerdb_slots" => ?stackerdb_config.signers, "queried_sortition" => %election_sortition, "sortition_hashes" => ?miners_info.get_sortitions()); - return Ok(None); } let slot_id_range = signer_ranges.swap_remove(signer_ix); diff --git a/testnet/stacks-node/src/neon_node.rs b/testnet/stacks-node/src/neon_node.rs index 6cbbd3b9f6..6e17328f6d 100644 --- a/testnet/stacks-node/src/neon_node.rs +++ b/testnet/stacks-node/src/neon_node.rs @@ -154,7 +154,7 @@ use clarity::vm::types::{PrincipalData, QualifiedContractIdentifier}; use libsigner::v0::messages::{ MessageSlotID, MinerSlotID, MockMinerMessage, MockSignature, PeerInfo, SignerMessage, }; -use libsigner::StackerDBSession; +use libsigner::{SignerSession, StackerDBSession}; use stacks::burnchains::bitcoin::address::{BitcoinAddress, LegacyBitcoinAddressType}; use stacks::burnchains::db::BurnchainHeaderReader; use stacks::burnchains::{Burnchain, BurnchainSigner, PoxConstants, Txid}; @@ -190,7 +190,7 @@ use stacks::net::db::{LocalPeer, PeerDB}; use stacks::net::dns::{DNSClient, DNSResolver}; use stacks::net::p2p::PeerNetwork; use stacks::net::relay::Relayer; -use stacks::net::stackerdb::{StackerDBConfig, StackerDBSync, StackerDBs}; +use stacks::net::stackerdb::{StackerDBConfig, StackerDBSync, StackerDBs, MINER_SLOT_COUNT}; use stacks::net::{ Error as NetError, NetworkResult, PeerNetworkComms, RPCHandlerArgs, ServiceFlags, }; @@ -2263,10 +2263,11 @@ impl BlockMinerThread { } /// Read any mock signatures from stackerdb and respond to them - pub fn respond_to_mock_signatures(&mut self) -> Result<(), ChainstateError> { + pub fn send_mock_miner_message(&mut self) -> Result<(), ChainstateError> { + let new_burn_block_height = self.burn_block.block_height + 1; let miner_config = self.config.get_miner_config(); - if miner_config.pre_nakamoto_miner_messaging { - debug!("Pre-Nakamoto miner messaging is disabled"); + if !miner_config.pre_nakamoto_miner_messaging { + debug!("Pre-Nakamoto mock miner messaging is disabled"); return Ok(()); } @@ -2274,16 +2275,43 @@ impl BlockMinerThread { let burn_db = SortitionDB::open(&burn_db_path, false, self.burnchain.pox_constants.clone()) .expect("FATAL: could not open sortition DB"); - let target_epoch_id = - SortitionDB::get_stacks_epoch(burn_db.conn(), self.burn_block.block_height + 1)? - .expect("FATAL: no epoch defined") - .epoch_id; + let target_epoch_id = SortitionDB::get_stacks_epoch(burn_db.conn(), new_burn_block_height)? + .expect("FATAL: no epoch defined") + .epoch_id; if target_epoch_id != StacksEpochId::Epoch25 { - debug!("Mock signing is disabled for non-epoch 2.5 blocks."; + debug!("Mock miner messaging is disabled for non-epoch 2.5 blocks."; "target_epoch_id" => target_epoch_id.to_string() ); return Ok(()); } + let miner_contract_id = boot_code_id(MINERS_NAME, self.config.is_mainnet()); + let mut miners_stackerdb = + StackerDBSession::new(&self.config.node.rpc_bind, miner_contract_id); + let slot_id = MinerSlotID::BlockProposal.to_u8().into(); + if let Ok(messages) = + miners_stackerdb.get_latest_chunks(&[slot_id, slot_id * MINER_SLOT_COUNT]) + { + debug!("Miner got messages: {:?}", messages.len()); + for message in messages { + if let Some(message) = message { + if message.is_empty() { + continue; + } + let Ok(SignerMessage::MockMinerMessage(miner_message)) = + SignerMessage::consensus_deserialize(&mut message.as_slice()) + else { + continue; + }; + if miner_message.peer_info.burn_block_height == new_burn_block_height { + debug!( + "Already sent mock miner message for tenure burn block height {:?}", + self.burn_block.block_height + ); + return Ok(()); + } + } + } + } // Retrieve any MockSignatures from stackerdb let mut mock_signatures = Vec::new(); let reward_cycle = self @@ -2321,13 +2349,6 @@ impl BlockMinerThread { } } } - info!( - "Miner responding to {} mock signatures", - mock_signatures.len() - ); - let miner_contract_id = boot_code_id(MINERS_NAME, self.config.is_mainnet()); - let mut miners_stackerdb = - StackerDBSession::new(&self.config.node.rpc_bind, miner_contract_id); let p2p_net = StacksNode::setup_peer_network( &self.config, @@ -2356,30 +2377,29 @@ impl BlockMinerThread { server_version, }; - info!("Responding to mock signatures for burn block {:?}", &self.burn_block.block_height; + info!("Sending mock miner message in response to mock signatures for burn block {:?}", &self.burn_block.block_height; "stacks_tip_consensus_hash" => ?peer_info.stacks_tip_consensus_hash.clone(), "stacks_tip" => ?peer_info.stacks_tip.clone(), "peer_burn_block_height" => peer_info.burn_block_height, "pox_consensus" => ?peer_info.pox_consensus.clone(), "server_version" => peer_info.server_version.clone(), - "chain_id" => self.config.burnchain.chain_id + "chain_id" => self.config.burnchain.chain_id, + "num_mock_signatures" => mock_signatures.len(), ); let message = MockMinerMessage { peer_info, - tenure_burn_block_height: self.burn_block.block_height, chain_id: self.config.burnchain.chain_id, mock_signatures, + tenure_burn_block_height: new_burn_block_height, }; - let sort_db = SortitionDB::open(&burn_db_path, true, self.burnchain.pox_constants.clone()) - .expect("FATAL: failed to open burnchain DB"); if let Err(e) = SignCoordinator::send_miners_message( &miner_config.mining_key.expect("BUG: no mining key"), - &sort_db, + &burn_db, &self.burn_block, &stackerdbs, SignerMessage::MockMinerMessage(message), - MinerSlotID::MockMinerMessage, + MinerSlotID::BlockProposal, // We are sending a mock miner message NOT a block proposal, but since we do not propose blocks in epoch 2.5, it is fine self.config.is_mainnet(), &mut miners_stackerdb, &self.burn_block.consensus_hash, @@ -3731,8 +3751,8 @@ impl RelayerThread { .stack_size(BLOCK_PROCESSOR_STACK_SIZE) .spawn(move || { let result = miner_thread_state.run_tenure(); - if let Err(e) = miner_thread_state.respond_to_mock_signatures() { - warn!("Failed to respond to mock signatures: {}", e); + if let Err(e) = miner_thread_state.send_mock_miner_message() { + warn!("Failed to send mock miner message: {}", e); } result }) diff --git a/testnet/stacks-node/src/tests/signer/v0.rs b/testnet/stacks-node/src/tests/signer/v0.rs index 4b3fea46a0..92cd23a3af 100644 --- a/testnet/stacks-node/src/tests/signer/v0.rs +++ b/testnet/stacks-node/src/tests/signer/v0.rs @@ -23,7 +23,7 @@ use std::{env, thread}; use clarity::vm::types::PrincipalData; use clarity::vm::StacksEpoch; use libsigner::v0::messages::{ - BlockRejection, BlockResponse, MessageSlotID, RejectCode, SignerMessage, + BlockRejection, BlockResponse, MessageSlotID, MinerSlotID, RejectCode, SignerMessage, }; use libsigner::{BlockProposal, SignerSession, StackerDBSession}; use rand::RngCore; @@ -184,7 +184,7 @@ impl SignerTest { ); debug!("Waiting for signer set calculation."); let mut reward_set_calculated = false; - let short_timeout = Duration::from_secs(30); + let short_timeout = Duration::from_secs(60); let now = std::time::Instant::now(); // Make sure the signer set is calculated before continuing or signers may not // recognize that they are registered signers in the subsequent burn block event @@ -2359,6 +2359,124 @@ fn mock_sign_epoch_25() { assert_eq!(old_signatures, new_signatures); } +#[test] +#[ignore] +/// This test checks that Epoch 2.5 miners will issue a MockMinerMessage per burn block they receive +/// including the mock signature from the signers. +fn mock_miner_message_epoch_25() { + if env::var("BITCOIND_TEST") != Ok("1".into()) { + return; + } + + tracing_subscriber::registry() + .with(fmt::layer()) + .with(EnvFilter::from_default_env()) + .init(); + + info!("------------------------- Test Setup -------------------------"); + let num_signers = 5; + let sender_sk = Secp256k1PrivateKey::new(); + let sender_addr = tests::to_addr(&sender_sk); + let send_amt = 100; + let send_fee = 180; + + let mut signer_test: SignerTest = SignerTest::new_with_config_modifications( + num_signers, + vec![(sender_addr.clone(), send_amt + send_fee)], + Some(Duration::from_secs(5)), + |_| {}, + |node_config| { + let epochs = node_config.burnchain.epochs.as_mut().unwrap(); + for epoch in epochs.iter_mut() { + if epoch.epoch_id == StacksEpochId::Epoch25 { + epoch.end_height = 251; + } + if epoch.epoch_id == StacksEpochId::Epoch30 { + epoch.start_height = 251; + } + } + }, + &[], + ); + + let epochs = signer_test + .running_nodes + .conf + .burnchain + .epochs + .clone() + .unwrap(); + let epoch_3 = &epochs[StacksEpoch::find_epoch_by_id(&epochs, StacksEpochId::Epoch30).unwrap()]; + let epoch_3_start_height = epoch_3.start_height; + + signer_test.boot_to_epoch_25_reward_cycle(); + + info!("------------------------- Test Processing Epoch 2.5 Tenures -------------------------"); + let miners_stackerdb_contract = boot_code_id(MINERS_NAME, false); + let main_poll_time = Instant::now(); + let mut mock_miner_message = None; + while signer_test + .running_nodes + .btc_regtest_controller + .get_headers_height() + < epoch_3_start_height + { + let current_burn_block_height = signer_test + .running_nodes + .btc_regtest_controller + .get_headers_height(); + let mock_poll_time = Instant::now(); + next_block_and( + &mut signer_test.running_nodes.btc_regtest_controller, + 60, + || Ok(true), + ) + .unwrap(); + debug!("Waiting for mock miner message for burn block height {current_burn_block_height}"); + + while mock_miner_message.is_none() { + std::thread::sleep(Duration::from_millis(100)); + let chunks = test_observer::get_stackerdb_chunks(); + for chunk in chunks + .into_iter() + .filter_map(|chunk| { + if chunk.contract_id != miners_stackerdb_contract { + return None; + } + Some(chunk.modified_slots) + }) + .flatten() + { + if chunk.slot_id == MinerSlotID::BlockProposal.to_u8() as u32 { + if chunk.data.is_empty() { + continue; + } + let SignerMessage::MockMinerMessage(message) = + SignerMessage::consensus_deserialize(&mut chunk.data.as_slice()) + .expect("Failed to deserialize MockMinerMessage") + else { + continue; + }; + if message.peer_info.burn_block_height == current_burn_block_height { + mock_miner_message = Some(message); + break; + } + } + } + assert!( + mock_poll_time.elapsed() <= Duration::from_secs(15), + "Failed to find mock miner message within timeout" + ); + } + test_observer::clear(); + mock_miner_message = None; + assert!( + main_poll_time.elapsed() <= Duration::from_secs(45), + "Timed out waiting to advance epoch 3.0" + ); + } +} + #[test] #[ignore] /// This test asserts that signer set rollover works as expected. From 4c7598c418c996e2353695ee058ade2c4ff47027 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Wed, 14 Aug 2024 15:35:38 -0400 Subject: [PATCH 05/20] WIP: Failing to write to miner slot Signed-off-by: Jacinta Ferrant --- testnet/stacks-node/src/neon_node.rs | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/testnet/stacks-node/src/neon_node.rs b/testnet/stacks-node/src/neon_node.rs index 6e17328f6d..9749b2625d 100644 --- a/testnet/stacks-node/src/neon_node.rs +++ b/testnet/stacks-node/src/neon_node.rs @@ -152,7 +152,7 @@ use clarity::vm::ast::ASTRules; use clarity::vm::costs::ExecutionCost; use clarity::vm::types::{PrincipalData, QualifiedContractIdentifier}; use libsigner::v0::messages::{ - MessageSlotID, MinerSlotID, MockMinerMessage, MockSignature, PeerInfo, SignerMessage, + MessageSlotID, MinerSlotID, MockMinerMessage, PeerInfo, SignerMessage, }; use libsigner::{SignerSession, StackerDBSession}; use stacks::burnchains::bitcoin::address::{BitcoinAddress, LegacyBitcoinAddressType}; @@ -2334,17 +2334,13 @@ impl BlockMinerThread { let chunks = stackerdbs.get_latest_chunks(&signers_contract_id, &slot_ids)?; for chunk in chunks { if let Some(chunk) = chunk { - match MockSignature::consensus_deserialize(&mut chunk.as_slice()) { - Ok(mock_signature) => { - if mock_signature.sign_data.event_burn_block_height - == self.burn_block.block_height - { - mock_signatures.push(mock_signature); - } - } - Err(e) => { - warn!("Failed to deserialize mock signature: {:?}", &e); - continue; + if let Ok(SignerMessage::MockSignature(mock_signature)) = + SignerMessage::consensus_deserialize(&mut chunk.as_slice()) + { + if mock_signature.sign_data.event_burn_block_height + == self.burn_block.block_height + { + mock_signatures.push(mock_signature); } } } From edfaa10b524d65f73e9c759d371b4c1d4664bf15 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Wed, 14 Aug 2024 15:57:23 -0400 Subject: [PATCH 06/20] WIP: No longer failing to write to .miners but failing to find appropro message Signed-off-by: Jacinta Ferrant --- testnet/stacks-node/src/neon_node.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/testnet/stacks-node/src/neon_node.rs b/testnet/stacks-node/src/neon_node.rs index 9749b2625d..0fc40c135a 100644 --- a/testnet/stacks-node/src/neon_node.rs +++ b/testnet/stacks-node/src/neon_node.rs @@ -3746,11 +3746,10 @@ impl RelayerThread { .name(format!("miner-block-{}", self.local_peer.data_url)) .stack_size(BLOCK_PROCESSOR_STACK_SIZE) .spawn(move || { - let result = miner_thread_state.run_tenure(); if let Err(e) = miner_thread_state.send_mock_miner_message() { warn!("Failed to send mock miner message: {}", e); } - result + miner_thread_state.run_tenure() }) .map_err(|e| { error!("Relayer: Failed to start tenure thread: {:?}", &e); From c590bcf42039771f0bc42a5b6fa160d4154b576c Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Wed, 14 Aug 2024 16:16:47 -0400 Subject: [PATCH 07/20] WIP: failing at 222 Signed-off-by: Jacinta Ferrant --- testnet/stacks-node/src/neon_node.rs | 25 +++++++++------------- testnet/stacks-node/src/tests/signer/v0.rs | 1 - 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/testnet/stacks-node/src/neon_node.rs b/testnet/stacks-node/src/neon_node.rs index 0fc40c135a..43eb114414 100644 --- a/testnet/stacks-node/src/neon_node.rs +++ b/testnet/stacks-node/src/neon_node.rs @@ -2264,7 +2264,6 @@ impl BlockMinerThread { /// Read any mock signatures from stackerdb and respond to them pub fn send_mock_miner_message(&mut self) -> Result<(), ChainstateError> { - let new_burn_block_height = self.burn_block.block_height + 1; let miner_config = self.config.get_miner_config(); if !miner_config.pre_nakamoto_miner_messaging { debug!("Pre-Nakamoto mock miner messaging is disabled"); @@ -2274,13 +2273,15 @@ impl BlockMinerThread { let burn_db_path = self.config.get_burn_db_file_path(); let burn_db = SortitionDB::open(&burn_db_path, false, self.burnchain.pox_constants.clone()) .expect("FATAL: could not open sortition DB"); - - let target_epoch_id = SortitionDB::get_stacks_epoch(burn_db.conn(), new_burn_block_height)? - .expect("FATAL: no epoch defined") - .epoch_id; - if target_epoch_id != StacksEpochId::Epoch25 { + let p2p_net = StacksNode::setup_peer_network( + &self.config, + &self.config.atlas, + self.burnchain.clone(), + ); + let epoch_id = p2p_net.get_current_epoch().epoch_id; + if epoch_id != StacksEpochId::Epoch25 { debug!("Mock miner messaging is disabled for non-epoch 2.5 blocks."; - "target_epoch_id" => target_epoch_id.to_string() + "epoch_id" => epoch_id.to_string() ); return Ok(()); } @@ -2302,7 +2303,7 @@ impl BlockMinerThread { else { continue; }; - if miner_message.peer_info.burn_block_height == new_burn_block_height { + if miner_message.peer_info.burn_block_height == self.burn_block.block_height { debug!( "Already sent mock miner message for tenure burn block height {:?}", self.burn_block.block_height @@ -2346,12 +2347,6 @@ impl BlockMinerThread { } } - let p2p_net = StacksNode::setup_peer_network( - &self.config, - &self.config.atlas, - self.burnchain.clone(), - ); - let server_version = version_string( "stacks-node", option_env!("STACKS_NODE_VERSION") @@ -2386,7 +2381,7 @@ impl BlockMinerThread { peer_info, chain_id: self.config.burnchain.chain_id, mock_signatures, - tenure_burn_block_height: new_burn_block_height, + tenure_burn_block_height: self.burn_block.block_height, }; if let Err(e) = SignCoordinator::send_miners_message( diff --git a/testnet/stacks-node/src/tests/signer/v0.rs b/testnet/stacks-node/src/tests/signer/v0.rs index 92cd23a3af..9034a8a523 100644 --- a/testnet/stacks-node/src/tests/signer/v0.rs +++ b/testnet/stacks-node/src/tests/signer/v0.rs @@ -2468,7 +2468,6 @@ fn mock_miner_message_epoch_25() { "Failed to find mock miner message within timeout" ); } - test_observer::clear(); mock_miner_message = None; assert!( main_poll_time.elapsed() <= Duration::from_secs(45), From 993d55b2d97044018f9e01771cf415860c1a7f85 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Thu, 15 Aug 2024 09:54:32 -0400 Subject: [PATCH 08/20] WIP: missing stackerdb messages Signed-off-by: Jacinta Ferrant --- libsigner/src/v0/messages.rs | 4 ++- stackslib/src/net/stackerdb/mod.rs | 2 +- testnet/stacks-node/src/neon_node.rs | 32 ++++++++++----------- testnet/stacks-node/src/tests/signer/v0.rs | 33 ++++++++++++---------- 4 files changed, 37 insertions(+), 34 deletions(-) diff --git a/libsigner/src/v0/messages.rs b/libsigner/src/v0/messages.rs index 117a8c4912..f51d7965de 100644 --- a/libsigner/src/v0/messages.rs +++ b/libsigner/src/v0/messages.rs @@ -88,7 +88,9 @@ MinerSlotID { /// Block proposal from the miner BlockProposal = 0, /// Block pushed from the miner - BlockPushed = 1 + BlockPushed = 1, + /// Mock Miner Message from the miner + MockMinerMessage = 2 }); impl MessageSlotIDTrait for MessageSlotID { diff --git a/stackslib/src/net/stackerdb/mod.rs b/stackslib/src/net/stackerdb/mod.rs index 847363b2e3..bfbb6e0a10 100644 --- a/stackslib/src/net/stackerdb/mod.rs +++ b/stackslib/src/net/stackerdb/mod.rs @@ -152,7 +152,7 @@ pub const STACKERDB_MAX_PAGE_COUNT: u32 = 2; pub const STACKERDB_SLOTS_FUNCTION: &str = "stackerdb-get-signer-slots"; pub const STACKERDB_CONFIG_FUNCTION: &str = "stackerdb-get-config"; -pub const MINER_SLOT_COUNT: u32 = 2; +pub const MINER_SLOT_COUNT: u32 = 3; /// Final result of synchronizing state with a remote set of DB replicas #[derive(Clone)] diff --git a/testnet/stacks-node/src/neon_node.rs b/testnet/stacks-node/src/neon_node.rs index 43eb114414..19d8bb966f 100644 --- a/testnet/stacks-node/src/neon_node.rs +++ b/testnet/stacks-node/src/neon_node.rs @@ -2288,11 +2288,8 @@ impl BlockMinerThread { let miner_contract_id = boot_code_id(MINERS_NAME, self.config.is_mainnet()); let mut miners_stackerdb = StackerDBSession::new(&self.config.node.rpc_bind, miner_contract_id); - let slot_id = MinerSlotID::BlockProposal.to_u8().into(); - if let Ok(messages) = - miners_stackerdb.get_latest_chunks(&[slot_id, slot_id * MINER_SLOT_COUNT]) - { - debug!("Miner got messages: {:?}", messages.len()); + let miner_slot_ids: Vec<_> = (0..MINER_SLOT_COUNT * 2).collect(); + if let Ok(messages) = miners_stackerdb.get_latest_chunks(&miner_slot_ids) { for message in messages { if let Some(message) = message { if message.is_empty() { @@ -2303,7 +2300,7 @@ impl BlockMinerThread { else { continue; }; - if miner_message.peer_info.burn_block_height == self.burn_block.block_height { + if miner_message.tenure_burn_block_height == self.burn_block.block_height { debug!( "Already sent mock miner message for tenure burn block height {:?}", self.burn_block.block_height @@ -2368,15 +2365,6 @@ impl BlockMinerThread { server_version, }; - info!("Sending mock miner message in response to mock signatures for burn block {:?}", &self.burn_block.block_height; - "stacks_tip_consensus_hash" => ?peer_info.stacks_tip_consensus_hash.clone(), - "stacks_tip" => ?peer_info.stacks_tip.clone(), - "peer_burn_block_height" => peer_info.burn_block_height, - "pox_consensus" => ?peer_info.pox_consensus.clone(), - "server_version" => peer_info.server_version.clone(), - "chain_id" => self.config.burnchain.chain_id, - "num_mock_signatures" => mock_signatures.len(), - ); let message = MockMinerMessage { peer_info, chain_id: self.config.burnchain.chain_id, @@ -2384,13 +2372,23 @@ impl BlockMinerThread { tenure_burn_block_height: self.burn_block.block_height, }; + info!("Sending mock miner message in response to mock signatures for burn block {:?}", message.tenure_burn_block_height; + "stacks_tip_consensus_hash" => ?message.peer_info.stacks_tip_consensus_hash.clone(), + "stacks_tip" => ?message.peer_info.stacks_tip.clone(), + "peer_burn_block_height" => message.peer_info.burn_block_height, + "pox_consensus" => ?message.peer_info.pox_consensus.clone(), + "server_version" => message.peer_info.server_version.clone(), + "chain_id" => message.chain_id, + "num_mock_signatures" => message.mock_signatures.len(), + ); + if let Err(e) = SignCoordinator::send_miners_message( &miner_config.mining_key.expect("BUG: no mining key"), &burn_db, &self.burn_block, &stackerdbs, - SignerMessage::MockMinerMessage(message), - MinerSlotID::BlockProposal, // We are sending a mock miner message NOT a block proposal, but since we do not propose blocks in epoch 2.5, it is fine + SignerMessage::MockMinerMessage(message.clone()), + MinerSlotID::MockMinerMessage, self.config.is_mainnet(), &mut miners_stackerdb, &self.burn_block.consensus_hash, diff --git a/testnet/stacks-node/src/tests/signer/v0.rs b/testnet/stacks-node/src/tests/signer/v0.rs index 9034a8a523..0f94898244 100644 --- a/testnet/stacks-node/src/tests/signer/v0.rs +++ b/testnet/stacks-node/src/tests/signer/v0.rs @@ -23,7 +23,7 @@ use std::{env, thread}; use clarity::vm::types::PrincipalData; use clarity::vm::StacksEpoch; use libsigner::v0::messages::{ - BlockRejection, BlockResponse, MessageSlotID, MinerSlotID, RejectCode, SignerMessage, + BlockRejection, BlockResponse, MessageSlotID, RejectCode, SignerMessage, }; use libsigner::{BlockProposal, SignerSession, StackerDBSession}; use rand::RngCore; @@ -2408,6 +2408,7 @@ fn mock_miner_message_epoch_25() { .unwrap(); let epoch_3 = &epochs[StacksEpoch::find_epoch_by_id(&epochs, StacksEpochId::Epoch30).unwrap()]; let epoch_3_start_height = epoch_3.start_height; + debug!("Epoch 3.0 starts at height {}", epoch_3_start_height); signer_test.boot_to_epoch_25_reward_cycle(); @@ -2447,20 +2448,22 @@ fn mock_miner_message_epoch_25() { }) .flatten() { - if chunk.slot_id == MinerSlotID::BlockProposal.to_u8() as u32 { - if chunk.data.is_empty() { - continue; - } - let SignerMessage::MockMinerMessage(message) = - SignerMessage::consensus_deserialize(&mut chunk.data.as_slice()) - .expect("Failed to deserialize MockMinerMessage") - else { - continue; - }; - if message.peer_info.burn_block_height == current_burn_block_height { - mock_miner_message = Some(message); - break; - } + if chunk.data.is_empty() { + continue; + } + let SignerMessage::MockMinerMessage(message) = + SignerMessage::consensus_deserialize(&mut chunk.data.as_slice()) + .expect("Failed to deserialize SignerMessage") + else { + continue; + }; + if message.tenure_burn_block_height == current_burn_block_height { + mock_miner_message = Some(message); + break; + } else { + info!( + "Received MockMinerMessage for burn block height {} but expected {current_burn_block_height}", message.tenure_burn_block_height + ); } } assert!( From d93d07da0068e92da97226ae0a52d086be0d1ac8 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Thu, 15 Aug 2024 10:31:40 -0400 Subject: [PATCH 09/20] WIP: use latest election winner to send mock miner messages Signed-off-by: Jacinta Ferrant --- testnet/stacks-node/src/neon_node.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/testnet/stacks-node/src/neon_node.rs b/testnet/stacks-node/src/neon_node.rs index 19d8bb966f..b88ec5ffe2 100644 --- a/testnet/stacks-node/src/neon_node.rs +++ b/testnet/stacks-node/src/neon_node.rs @@ -2381,6 +2381,15 @@ impl BlockMinerThread { "chain_id" => message.chain_id, "num_mock_signatures" => message.mock_signatures.len(), ); + let (_, miners_info) = + NakamotoChainState::make_miners_stackerdb_config(&burn_db, &self.burn_block)?; + + // find out which slot we're in. If we are not the latest sortition winner, we should not be sending anymore messages anyway + let idx = miners_info.get_latest_winner_index(); + let sortitions = miners_info.get_sortitions(); + let election_sortition = *sortitions + .get(idx as usize) + .expect("FATAL: latest winner index out of bounds"); if let Err(e) = SignCoordinator::send_miners_message( &miner_config.mining_key.expect("BUG: no mining key"), @@ -2391,7 +2400,7 @@ impl BlockMinerThread { MinerSlotID::MockMinerMessage, self.config.is_mainnet(), &mut miners_stackerdb, - &self.burn_block.consensus_hash, + &election_sortition, ) { warn!("Failed to send mock miner message: {:?}", &e); } From b597a119cab6c014e59cf4abd80d707ad5eff269 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Thu, 15 Aug 2024 11:42:07 -0400 Subject: [PATCH 10/20] WIP: stuck at 250 Signed-off-by: Jacinta Ferrant --- testnet/stacks-node/src/tests/signer/v0.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/testnet/stacks-node/src/tests/signer/v0.rs b/testnet/stacks-node/src/tests/signer/v0.rs index 0f94898244..2fe0cf934c 100644 --- a/testnet/stacks-node/src/tests/signer/v0.rs +++ b/testnet/stacks-node/src/tests/signer/v0.rs @@ -2422,10 +2422,6 @@ fn mock_miner_message_epoch_25() { .get_headers_height() < epoch_3_start_height { - let current_burn_block_height = signer_test - .running_nodes - .btc_regtest_controller - .get_headers_height(); let mock_poll_time = Instant::now(); next_block_and( &mut signer_test.running_nodes.btc_regtest_controller, @@ -2433,6 +2429,10 @@ fn mock_miner_message_epoch_25() { || Ok(true), ) .unwrap(); + let current_burn_block_height = signer_test + .running_nodes + .btc_regtest_controller + .get_headers_height(); debug!("Waiting for mock miner message for burn block height {current_burn_block_height}"); while mock_miner_message.is_none() { From 37a2533fcae14f2d61655940925e1ae2f60f5c2b Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Thu, 15 Aug 2024 12:32:58 -0400 Subject: [PATCH 11/20] WIP: need to fix stacks tip consensus hash and stacks tip Signed-off-by: Jacinta Ferrant --- testnet/stacks-node/src/tests/signer/v0.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/testnet/stacks-node/src/tests/signer/v0.rs b/testnet/stacks-node/src/tests/signer/v0.rs index 2fe0cf934c..74790bd15d 100644 --- a/testnet/stacks-node/src/tests/signer/v0.rs +++ b/testnet/stacks-node/src/tests/signer/v0.rs @@ -2407,8 +2407,7 @@ fn mock_miner_message_epoch_25() { .clone() .unwrap(); let epoch_3 = &epochs[StacksEpoch::find_epoch_by_id(&epochs, StacksEpochId::Epoch30).unwrap()]; - let epoch_3_start_height = epoch_3.start_height; - debug!("Epoch 3.0 starts at height {}", epoch_3_start_height); + let epoch_3_boundary = epoch_3.start_height - 1; signer_test.boot_to_epoch_25_reward_cycle(); @@ -2416,11 +2415,12 @@ fn mock_miner_message_epoch_25() { let miners_stackerdb_contract = boot_code_id(MINERS_NAME, false); let main_poll_time = Instant::now(); let mut mock_miner_message = None; + // Only advance to the boundary as the epoch 2.5 miner will be shut down at this point. while signer_test .running_nodes .btc_regtest_controller .get_headers_height() - < epoch_3_start_height + < epoch_3_boundary { let mock_poll_time = Instant::now(); next_block_and( From be1f6ed8c2d1e5cc905d5e6fe76ec71db0794bc3 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Thu, 15 Aug 2024 13:44:59 -0400 Subject: [PATCH 12/20] Fix consensus hash and stacks tip in MockMinerMessage Signed-off-by: Jacinta Ferrant --- libsigner/src/v0/messages.rs | 6 ---- testnet/stacks-node/src/neon_node.rs | 36 +++++++++++++--------- testnet/stacks-node/src/tests/signer/v0.rs | 6 +--- 3 files changed, 23 insertions(+), 25 deletions(-) diff --git a/libsigner/src/v0/messages.rs b/libsigner/src/v0/messages.rs index f51d7965de..40a679d0f8 100644 --- a/libsigner/src/v0/messages.rs +++ b/libsigner/src/v0/messages.rs @@ -459,8 +459,6 @@ impl StacksMessageCodec for MockSignature { pub struct MockMinerMessage { /// The view of the stacks node peer information at the time of the mock signature pub peer_info: PeerInfo, - /// The burn block height of the miner's tenure - pub tenure_burn_block_height: u64, /// The chain id for the mock signature pub chain_id: u32, /// The mock signatures that the miner received @@ -470,7 +468,6 @@ pub struct MockMinerMessage { impl StacksMessageCodec for MockMinerMessage { fn consensus_serialize(&self, fd: &mut W) -> Result<(), CodecError> { self.peer_info.consensus_serialize(fd)?; - write_next(fd, &self.tenure_burn_block_height)?; write_next(fd, &self.chain_id)?; write_next(fd, &self.mock_signatures)?; Ok(()) @@ -478,12 +475,10 @@ impl StacksMessageCodec for MockMinerMessage { fn consensus_deserialize(fd: &mut R) -> Result { let peer_info = PeerInfo::consensus_deserialize(fd)?; - let tenure_burn_block_height = read_next::(fd)?; let chain_id = read_next::(fd)?; let mock_signatures = read_next::, _>(fd)?; Ok(Self { peer_info, - tenure_burn_block_height, chain_id, mock_signatures, }) @@ -1002,7 +997,6 @@ mod test { }; let mock_miner_message = MockMinerMessage { peer_info: random_peer_data(), - tenure_burn_block_height: thread_rng().next_u64(), chain_id: thread_rng().gen_range(0..=1), mock_signatures: vec![mock_signature_1, mock_signature_2], }; diff --git a/testnet/stacks-node/src/neon_node.rs b/testnet/stacks-node/src/neon_node.rs index b88ec5ffe2..c4880b3980 100644 --- a/testnet/stacks-node/src/neon_node.rs +++ b/testnet/stacks-node/src/neon_node.rs @@ -194,6 +194,7 @@ use stacks::net::stackerdb::{StackerDBConfig, StackerDBSync, StackerDBs, MINER_S use stacks::net::{ Error as NetError, NetworkResult, PeerNetworkComms, RPCHandlerArgs, ServiceFlags, }; +use stacks::types::StacksEpoch; use stacks::util_lib::strings::{UrlString, VecDisplay}; use stacks::{monitoring, version_string}; use stacks_common::codec::StacksMessageCodec; @@ -2273,12 +2274,20 @@ impl BlockMinerThread { let burn_db_path = self.config.get_burn_db_file_path(); let burn_db = SortitionDB::open(&burn_db_path, false, self.burnchain.pox_constants.clone()) .expect("FATAL: could not open sortition DB"); - let p2p_net = StacksNode::setup_peer_network( - &self.config, - &self.config.atlas, - self.burnchain.clone(), - ); - let epoch_id = p2p_net.get_current_epoch().epoch_id; + let epochs = SortitionDB::get_stacks_epochs(burn_db.conn()) + .expect("Error while loading stacks epochs"); + let epoch_index = StacksEpoch::find_epoch(&epochs, self.burn_block.block_height) + .unwrap_or_else(|| { + panic!( + "BUG: block {} is not in a known epoch", + self.burn_block.block_height + ) + }); + let epoch_id = epochs + .get(epoch_index) + .expect("BUG: no epoch at found index") + .epoch_id; + if epoch_id != StacksEpochId::Epoch25 { debug!("Mock miner messaging is disabled for non-epoch 2.5 blocks."; "epoch_id" => epoch_id.to_string() @@ -2300,7 +2309,7 @@ impl BlockMinerThread { else { continue; }; - if miner_message.tenure_burn_block_height == self.burn_block.block_height { + if miner_message.peer_info.burn_block_height == self.burn_block.block_height { debug!( "Already sent mock miner message for tenure burn block height {:?}", self.burn_block.block_height @@ -2350,11 +2359,11 @@ impl BlockMinerThread { .or(option_env!("CARGO_PKG_VERSION")) .unwrap_or("0.0.0.0"), ); - let stacks_tip_height = p2p_net.stacks_tip.height; - let stacks_tip = p2p_net.stacks_tip.block_hash.clone(); - let stacks_tip_consensus_hash = p2p_net.stacks_tip.consensus_hash.clone(); - let pox_consensus = p2p_net.burnchain_tip.consensus_hash.clone(); - let burn_block_height = p2p_net.chain_view.burn_block_height; + let stacks_tip_height = self.burn_block.canonical_stacks_tip_height; + let stacks_tip = self.burn_block.canonical_stacks_tip_hash; + let stacks_tip_consensus_hash = self.burn_block.canonical_stacks_tip_consensus_hash; + let pox_consensus = self.burn_block.consensus_hash; + let burn_block_height = self.burn_block.block_height; let peer_info = PeerInfo { burn_block_height, @@ -2369,10 +2378,9 @@ impl BlockMinerThread { peer_info, chain_id: self.config.burnchain.chain_id, mock_signatures, - tenure_burn_block_height: self.burn_block.block_height, }; - info!("Sending mock miner message in response to mock signatures for burn block {:?}", message.tenure_burn_block_height; + info!("Sending mock miner message in response to mock signatures for burn block {:?}", message.peer_info.burn_block_height; "stacks_tip_consensus_hash" => ?message.peer_info.stacks_tip_consensus_hash.clone(), "stacks_tip" => ?message.peer_info.stacks_tip.clone(), "peer_burn_block_height" => message.peer_info.burn_block_height, diff --git a/testnet/stacks-node/src/tests/signer/v0.rs b/testnet/stacks-node/src/tests/signer/v0.rs index 74790bd15d..631d92c83c 100644 --- a/testnet/stacks-node/src/tests/signer/v0.rs +++ b/testnet/stacks-node/src/tests/signer/v0.rs @@ -2457,13 +2457,9 @@ fn mock_miner_message_epoch_25() { else { continue; }; - if message.tenure_burn_block_height == current_burn_block_height { + if message.peer_info.burn_block_height == current_burn_block_height { mock_miner_message = Some(message); break; - } else { - info!( - "Received MockMinerMessage for burn block height {} but expected {current_burn_block_height}", message.tenure_burn_block_height - ); } } assert!( From 666119458c246822a7dca276b87fd0ddbb87ac24 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Thu, 15 Aug 2024 13:49:49 -0400 Subject: [PATCH 13/20] CRC: get sort db from SortitionDB Signed-off-by: Jacinta Ferrant --- libsigner/src/v0/messages.rs | 4 +--- stackslib/src/net/stackerdb/mod.rs | 2 +- testnet/stacks-node/src/neon_node.rs | 18 +++--------------- 3 files changed, 5 insertions(+), 19 deletions(-) diff --git a/libsigner/src/v0/messages.rs b/libsigner/src/v0/messages.rs index 40a679d0f8..779497b196 100644 --- a/libsigner/src/v0/messages.rs +++ b/libsigner/src/v0/messages.rs @@ -88,9 +88,7 @@ MinerSlotID { /// Block proposal from the miner BlockProposal = 0, /// Block pushed from the miner - BlockPushed = 1, - /// Mock Miner Message from the miner - MockMinerMessage = 2 + BlockPushed = 1 }); impl MessageSlotIDTrait for MessageSlotID { diff --git a/stackslib/src/net/stackerdb/mod.rs b/stackslib/src/net/stackerdb/mod.rs index bfbb6e0a10..847363b2e3 100644 --- a/stackslib/src/net/stackerdb/mod.rs +++ b/stackslib/src/net/stackerdb/mod.rs @@ -152,7 +152,7 @@ pub const STACKERDB_MAX_PAGE_COUNT: u32 = 2; pub const STACKERDB_SLOTS_FUNCTION: &str = "stackerdb-get-signer-slots"; pub const STACKERDB_CONFIG_FUNCTION: &str = "stackerdb-get-config"; -pub const MINER_SLOT_COUNT: u32 = 3; +pub const MINER_SLOT_COUNT: u32 = 2; /// Final result of synchronizing state with a remote set of DB replicas #[derive(Clone)] diff --git a/testnet/stacks-node/src/neon_node.rs b/testnet/stacks-node/src/neon_node.rs index c4880b3980..818a2cf00b 100644 --- a/testnet/stacks-node/src/neon_node.rs +++ b/testnet/stacks-node/src/neon_node.rs @@ -194,7 +194,6 @@ use stacks::net::stackerdb::{StackerDBConfig, StackerDBSync, StackerDBs, MINER_S use stacks::net::{ Error as NetError, NetworkResult, PeerNetworkComms, RPCHandlerArgs, ServiceFlags, }; -use stacks::types::StacksEpoch; use stacks::util_lib::strings::{UrlString, VecDisplay}; use stacks::{monitoring, version_string}; use stacks_common::codec::StacksMessageCodec; @@ -2274,20 +2273,9 @@ impl BlockMinerThread { let burn_db_path = self.config.get_burn_db_file_path(); let burn_db = SortitionDB::open(&burn_db_path, false, self.burnchain.pox_constants.clone()) .expect("FATAL: could not open sortition DB"); - let epochs = SortitionDB::get_stacks_epochs(burn_db.conn()) - .expect("Error while loading stacks epochs"); - let epoch_index = StacksEpoch::find_epoch(&epochs, self.burn_block.block_height) - .unwrap_or_else(|| { - panic!( - "BUG: block {} is not in a known epoch", - self.burn_block.block_height - ) - }); - let epoch_id = epochs - .get(epoch_index) - .expect("BUG: no epoch at found index") + let epoch_id = SortitionDB::get_stacks_epoch(burn_db.conn(), self.burn_block.block_height)? + .expect("FATAL: no epoch defined") .epoch_id; - if epoch_id != StacksEpochId::Epoch25 { debug!("Mock miner messaging is disabled for non-epoch 2.5 blocks."; "epoch_id" => epoch_id.to_string() @@ -2405,7 +2393,7 @@ impl BlockMinerThread { &self.burn_block, &stackerdbs, SignerMessage::MockMinerMessage(message.clone()), - MinerSlotID::MockMinerMessage, + MinerSlotID::BlockProposal, // There is no specific slot for mock miner messages self.config.is_mainnet(), &mut miners_stackerdb, &election_sortition, From 22ea25684d50a45bda94a91c23b73f99edf88572 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Thu, 15 Aug 2024 16:32:33 -0400 Subject: [PATCH 14/20] CRC: simulate block proposal, signatures, and appending a block in mock signing Signed-off-by: Jacinta Ferrant --- libsigner/src/v0/messages.rs | 315 ++++++++++-------- stacks-signer/src/v0/signer.rs | 61 ++-- .../src/nakamoto_node/sign_coordinator.rs | 10 +- testnet/stacks-node/src/neon_node.rs | 246 ++++++++------ testnet/stacks-node/src/tests/signer/v0.rs | 220 ++---------- 5 files changed, 374 insertions(+), 478 deletions(-) diff --git a/libsigner/src/v0/messages.rs b/libsigner/src/v0/messages.rs index 779497b196..b767431c60 100644 --- a/libsigner/src/v0/messages.rs +++ b/libsigner/src/v0/messages.rs @@ -77,9 +77,7 @@ define_u8_enum!( /// the contract index in the signers contracts (i.e., X in signers-0-X) MessageSlotID { /// Block Response message from signers - BlockResponse = 1, - /// Mock Signature message from Epoch 2.5 signers - MockSignature = 2 + BlockResponse = 1 }); define_u8_enum!( @@ -115,10 +113,12 @@ SignerMessageTypePrefix { BlockResponse = 1, /// Block Pushed message from miners BlockPushed = 2, - /// Mock Signature message from Epoch 2.5 signers - MockSignature = 3, - /// Mock Pre-Nakamoto message from Epoch 2.5 miners - MockMinerMessage = 4 + /// Mock block proposal message from Epoch 2.5 miners + MockProposal = 3, + /// Mock block signature message from Epoch 2.5 signers + MockSignature = 4, + /// Mock block message from Epoch 2.5 miners + MockBlock = 5 }); #[cfg_attr(test, mutants::skip)] @@ -161,8 +161,9 @@ impl From<&SignerMessage> for SignerMessageTypePrefix { SignerMessage::BlockProposal(_) => SignerMessageTypePrefix::BlockProposal, SignerMessage::BlockResponse(_) => SignerMessageTypePrefix::BlockResponse, SignerMessage::BlockPushed(_) => SignerMessageTypePrefix::BlockPushed, + SignerMessage::MockProposal(_) => SignerMessageTypePrefix::MockProposal, SignerMessage::MockSignature(_) => SignerMessageTypePrefix::MockSignature, - SignerMessage::MockMinerMessage(_) => SignerMessageTypePrefix::MockMinerMessage, + SignerMessage::MockBlock(_) => SignerMessageTypePrefix::MockBlock, } } } @@ -179,7 +180,9 @@ pub enum SignerMessage { /// A mock signature from the epoch 2.5 signers MockSignature(MockSignature), /// A mock message from the epoch 2.5 miners - MockMinerMessage(MockMinerMessage), + MockProposal(MockProposal), + /// A mock block from the epoch 2.5 miners + MockBlock(MockBlock), } impl SignerMessage { @@ -189,9 +192,11 @@ impl SignerMessage { #[cfg_attr(test, mutants::skip)] pub fn msg_id(&self) -> Option { match self { - Self::BlockProposal(_) | Self::BlockPushed(_) | Self::MockMinerMessage(_) => None, - Self::BlockResponse(_) => Some(MessageSlotID::BlockResponse), - Self::MockSignature(_) => Some(MessageSlotID::MockSignature), + Self::BlockProposal(_) + | Self::BlockPushed(_) + | Self::MockProposal(_) + | Self::MockBlock(_) => None, + Self::BlockResponse(_) | Self::MockSignature(_) => Some(MessageSlotID::BlockResponse), // Mock signature uses the same slot as block response since its exclusively for epoch 2.5 testing } } } @@ -206,7 +211,8 @@ impl StacksMessageCodec for SignerMessage { SignerMessage::BlockResponse(block_response) => block_response.consensus_serialize(fd), SignerMessage::BlockPushed(block) => block.consensus_serialize(fd), SignerMessage::MockSignature(signature) => signature.consensus_serialize(fd), - SignerMessage::MockMinerMessage(message) => message.consensus_serialize(fd), + SignerMessage::MockProposal(message) => message.consensus_serialize(fd), + SignerMessage::MockBlock(block) => block.consensus_serialize(fd), }?; Ok(()) } @@ -228,13 +234,17 @@ impl StacksMessageCodec for SignerMessage { let block = StacksMessageCodec::consensus_deserialize(fd)?; SignerMessage::BlockPushed(block) } + SignerMessageTypePrefix::MockProposal => { + let message = StacksMessageCodec::consensus_deserialize(fd)?; + SignerMessage::MockProposal(message) + } SignerMessageTypePrefix::MockSignature => { let signature = StacksMessageCodec::consensus_deserialize(fd)?; SignerMessage::MockSignature(signature) } - SignerMessageTypePrefix::MockMinerMessage => { - let message = StacksMessageCodec::consensus_deserialize(fd)?; - SignerMessage::MockMinerMessage(message) + SignerMessageTypePrefix::MockBlock => { + let block = StacksMessageCodec::consensus_deserialize(fd)?; + SignerMessage::MockBlock(block) } }; Ok(message) @@ -305,110 +315,75 @@ impl StacksMessageCodec for PeerInfo { } } -/// A snapshot of the signer view of the stacks node to be used for mock signing. +/// A mock block proposal for Epoch 2.5 mock signing #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct MockSignData { - /// The view of the stacks node peer information at the time of the mock signature +pub struct MockProposal { + /// The view of the stacks node peer information at the time of the mock proposal pub peer_info: PeerInfo, - /// The burn block height of the event that triggered the mock signature - pub event_burn_block_height: u64, - /// The chain id for the mock signature + /// The chain id for the mock proposal pub chain_id: u32, -} - -impl StacksMessageCodec for MockSignData { - fn consensus_serialize(&self, fd: &mut W) -> Result<(), CodecError> { - self.peer_info.consensus_serialize(fd)?; - write_next(fd, &self.event_burn_block_height)?; - write_next(fd, &self.chain_id)?; - Ok(()) - } - - fn consensus_deserialize(fd: &mut R) -> Result { - let peer_info = PeerInfo::consensus_deserialize(fd)?; - let event_burn_block_height = read_next::(fd)?; - let chain_id = read_next::(fd)?; - Ok(Self { - peer_info, - event_burn_block_height, - chain_id, - }) - } -} - -/// A mock signature for the stacks node to be used for mock signing. -/// This is only used by Epoch 2.5 signers to simulate the signing of a block for every sortition. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct MockSignature { - /// The signature of the mock signature + /// The miner's signature across the peer info signature: MessageSignature, - /// The data that was signed across - pub sign_data: MockSignData, } -impl MockSignature { - /// Create a new mock sign data struct from the provided event burn block height, peer info, chain id, and private key. - /// Note that peer burn block height and event burn block height may not be the same if the peer view is stale. - pub fn new( - event_burn_block_height: u64, - peer_info: PeerInfo, - chain_id: u32, - stacks_private_key: &StacksPrivateKey, - ) -> Self { +impl MockProposal { + /// Create a new mock proposal data struct from the provided peer info, chain id, and private key. + pub fn new(peer_info: PeerInfo, chain_id: u32, stacks_private_key: &StacksPrivateKey) -> Self { let mut sig = Self { signature: MessageSignature::empty(), - sign_data: MockSignData { - peer_info, - event_burn_block_height, - chain_id, - }, + chain_id, + peer_info, }; sig.sign(stacks_private_key) - .expect("Failed to sign MockSignature"); + .expect("Failed to sign MockProposal"); sig } - /// The signature hash for the mock signature - pub fn signature_hash(&self) -> Sha256Sum { - let domain_tuple = - make_structured_data_domain("mock-signer", "1.0.0", self.sign_data.chain_id); + /// The signature hash for the mock proposal + pub fn miner_signature_hash(&self) -> Sha256Sum { + let domain_tuple = make_structured_data_domain("mock-miner", "1.0.0", self.chain_id); let data_tuple = Value::Tuple( TupleData::from_data(vec![ ( "stacks-tip-consensus-hash".into(), - Value::buff_from( - self.sign_data - .peer_info - .stacks_tip_consensus_hash - .as_bytes() - .into(), - ) - .unwrap(), + Value::buff_from(self.peer_info.stacks_tip_consensus_hash.as_bytes().into()) + .unwrap(), ), ( "stacks-tip".into(), - Value::buff_from(self.sign_data.peer_info.stacks_tip.as_bytes().into()) - .unwrap(), + Value::buff_from(self.peer_info.stacks_tip.as_bytes().into()).unwrap(), ), ( "stacks-tip-height".into(), - Value::UInt(self.sign_data.peer_info.stacks_tip_height.into()), + Value::UInt(self.peer_info.stacks_tip_height.into()), ), ( "server-version".into(), - Value::string_ascii_from_bytes( - self.sign_data.peer_info.server_version.clone().into(), - ) - .unwrap(), + Value::string_ascii_from_bytes(self.peer_info.server_version.clone().into()) + .unwrap(), + ), + ( + "pox-consensus".into(), + Value::buff_from(self.peer_info.pox_consensus.as_bytes().into()).unwrap(), ), + ]) + .expect("Error creating signature hash"), + ); + structured_data_message_hash(data_tuple, domain_tuple) + } + + /// The signature hash including the miner's signature. Used by signers. + fn signer_signature_hash(&self) -> Sha256Sum { + let domain_tuple = make_structured_data_domain("mock-signer", "1.0.0", self.chain_id); + let data_tuple = Value::Tuple( + TupleData::from_data(vec![ ( - "event-burn-block-height".into(), - Value::UInt(self.sign_data.event_burn_block_height.into()), + "miner-signature-hash".into(), + Value::buff_from(self.miner_signature_hash().as_bytes().into()).unwrap(), ), ( - "pox-consensus".into(), - Value::buff_from(self.sign_data.peer_info.pox_consensus.as_bytes().into()) - .unwrap(), + "miner-signature".into(), + Value::buff_from(self.signature.as_bytes().into()).unwrap(), ), ]) .expect("Error creating signature hash"), @@ -416,18 +391,79 @@ impl MockSignature { structured_data_message_hash(data_tuple, domain_tuple) } + /// Sign the mock proposal and set the internal signature field + fn sign(&mut self, private_key: &StacksPrivateKey) -> Result<(), String> { + let signature_hash = self.miner_signature_hash(); + self.signature = private_key.sign(signature_hash.as_bytes())?; + Ok(()) + } + /// Verify the mock proposal against the provided miner public key + pub fn verify(&self, public_key: &StacksPublicKey) -> Result { + if self.signature == MessageSignature::empty() { + return Ok(false); + } + let signature_hash = self.miner_signature_hash(); + public_key + .verify(&signature_hash.0, &self.signature) + .map_err(|e| e.to_string()) + } +} + +impl StacksMessageCodec for MockProposal { + fn consensus_serialize(&self, fd: &mut W) -> Result<(), CodecError> { + self.peer_info.consensus_serialize(fd)?; + write_next(fd, &self.chain_id)?; + write_next(fd, &self.signature)?; + Ok(()) + } + + fn consensus_deserialize(fd: &mut R) -> Result { + let peer_info = PeerInfo::consensus_deserialize(fd)?; + let chain_id = read_next::(fd)?; + let signature = read_next::(fd)?; + Ok(Self { + peer_info, + chain_id, + signature, + }) + } +} + +/// A mock signature for the stacks node to be used for mock signing. +/// This is only used by Epoch 2.5 signers to simulate the signing of a block for every sortition. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct MockSignature { + /// The signer's signature across the mock proposal + signature: MessageSignature, + /// The mock block proposal that was signed across + pub mock_proposal: MockProposal, +} + +impl MockSignature { + /// Create a new mock signature from the provided proposal and signer private key. + pub fn new(mock_proposal: MockProposal, stacks_private_key: &StacksPrivateKey) -> Self { + let mut sig = Self { + signature: MessageSignature::empty(), + mock_proposal, + }; + sig.sign(stacks_private_key) + .expect("Failed to sign MockSignature"); + sig + } + /// Sign the mock signature and set the internal signature field fn sign(&mut self, private_key: &StacksPrivateKey) -> Result<(), String> { - let signature_hash = self.signature_hash(); + let signature_hash = self.mock_proposal.signer_signature_hash(); self.signature = private_key.sign(signature_hash.as_bytes())?; Ok(()) } - /// Verify the mock signature against the provided public key + + /// Verify the mock signature against the provided signer public key pub fn verify(&self, public_key: &StacksPublicKey) -> Result { if self.signature == MessageSignature::empty() { return Ok(false); } - let signature_hash = self.signature_hash(); + let signature_hash = self.mock_proposal.signer_signature_hash(); public_key .verify(&signature_hash.0, &self.signature) .map_err(|e| e.to_string()) @@ -437,47 +473,41 @@ impl MockSignature { impl StacksMessageCodec for MockSignature { fn consensus_serialize(&self, fd: &mut W) -> Result<(), CodecError> { write_next(fd, &self.signature)?; - self.sign_data.consensus_serialize(fd)?; + self.mock_proposal.consensus_serialize(fd)?; Ok(()) } fn consensus_deserialize(fd: &mut R) -> Result { let signature = read_next::(fd)?; - let sign_data = read_next::(fd)?; + let mock_proposal = MockProposal::consensus_deserialize(fd)?; Ok(Self { signature, - sign_data, + mock_proposal, }) } } -/// A mock message for the stacks node to be used for mock mining messages -/// This is only used by Epoch 2.5 miners to simulate miners responding to mock signatures +/// The mock block data for epoch 2.5 miners to broadcast to simulate block signing #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct MockMinerMessage { - /// The view of the stacks node peer information at the time of the mock signature - pub peer_info: PeerInfo, - /// The chain id for the mock signature - pub chain_id: u32, +pub struct MockBlock { + /// The mock proposal that was signed across + pub mock_proposal: MockProposal, /// The mock signatures that the miner received pub mock_signatures: Vec, } -impl StacksMessageCodec for MockMinerMessage { +impl StacksMessageCodec for MockBlock { fn consensus_serialize(&self, fd: &mut W) -> Result<(), CodecError> { - self.peer_info.consensus_serialize(fd)?; - write_next(fd, &self.chain_id)?; + self.mock_proposal.consensus_serialize(fd)?; write_next(fd, &self.mock_signatures)?; Ok(()) } fn consensus_deserialize(fd: &mut R) -> Result { - let peer_info = PeerInfo::consensus_deserialize(fd)?; - let chain_id = read_next::(fd)?; + let mock_proposal = MockProposal::consensus_deserialize(fd)?; let mock_signatures = read_next::, _>(fd)?; Ok(Self { - peer_info, - chain_id, + mock_proposal, mock_signatures, }) } @@ -781,6 +811,7 @@ mod test { use clarity::types::PrivateKey; use clarity::util::hash::MerkleTree; use clarity::util::secp256k1::MessageSignature; + use rand::rngs::mock; use rand::{thread_rng, Rng, RngCore}; use rand_core::OsRng; use stacks_common::bitvec::BitVec; @@ -910,7 +941,7 @@ mod test { pox_consensus: ConsensusHash([pox_consensus_byte; 20]), } } - fn random_mock_sign_data() -> MockSignData { + fn random_mock_proposal() -> MockProposal { let chain_byte: u8 = thread_rng().gen_range(0..=1); let chain_id = if chain_byte == 1 { CHAIN_ID_TESTNET @@ -918,25 +949,23 @@ mod test { CHAIN_ID_MAINNET }; let peer_info = random_peer_data(); - MockSignData { + MockProposal { peer_info, - event_burn_block_height: thread_rng().next_u64(), chain_id, + signature: MessageSignature::empty(), } } #[test] - fn verify_sign_mock_signature() { + fn verify_sign_mock_proposal() { let private_key = StacksPrivateKey::new(); let public_key = StacksPublicKey::from_private(&private_key); let bad_private_key = StacksPrivateKey::new(); let bad_public_key = StacksPublicKey::from_private(&bad_private_key); - let mut mock_signature = MockSignature { - signature: MessageSignature::empty(), - sign_data: random_mock_sign_data(), - }; + let mut mock_signature = random_mock_proposal(); + mock_signature.sign(&private_key).unwrap(); assert!(!mock_signature .verify(&public_key) .expect("Failed to verify MockSignature")); @@ -962,12 +991,25 @@ mod test { assert_eq!(peer_data, deserialized_data); } + #[test] + fn serde_mock_proposal() { + let mut mock_signature = random_mock_proposal(); + mock_signature.sign(&StacksPrivateKey::new()).unwrap(); + let serialized_signature = mock_signature.serialize_to_vec(); + let deserialized_signature = read_next::(&mut &serialized_signature[..]) + .expect("Failed to deserialize MockSignature"); + assert_eq!(mock_signature, deserialized_signature); + } + #[test] fn serde_mock_signature() { - let mock_signature = MockSignature { + let mut mock_signature = MockSignature { signature: MessageSignature::empty(), - sign_data: random_mock_sign_data(), + mock_proposal: random_mock_proposal(), }; + mock_signature + .sign(&StacksPrivateKey::new()) + .expect("Failed to sign MockSignature"); let serialized_signature = mock_signature.serialize_to_vec(); let deserialized_signature = read_next::(&mut &serialized_signature[..]) .expect("Failed to deserialize MockSignature"); @@ -975,32 +1017,17 @@ mod test { } #[test] - fn serde_sign_data() { - let sign_data = random_mock_sign_data(); - let serialized_data = sign_data.serialize_to_vec(); - let deserialized_data = read_next::(&mut &serialized_data[..]) - .expect("Failed to deserialize MockSignData"); - assert_eq!(sign_data, deserialized_data); - } - - #[test] - fn serde_mock_miner_message() { - let mock_signature_1 = MockSignature { - signature: MessageSignature::empty(), - sign_data: random_mock_sign_data(), - }; - let mock_signature_2 = MockSignature { - signature: MessageSignature::empty(), - sign_data: random_mock_sign_data(), - }; - let mock_miner_message = MockMinerMessage { - peer_info: random_peer_data(), - chain_id: thread_rng().gen_range(0..=1), + fn serde_mock_block() { + let mock_proposal = random_mock_proposal(); + let mock_signature_1 = MockSignature::new(mock_proposal.clone(), &StacksPrivateKey::new()); + let mock_signature_2 = MockSignature::new(mock_proposal.clone(), &StacksPrivateKey::new()); + let mock_block = MockBlock { + mock_proposal, mock_signatures: vec![mock_signature_1, mock_signature_2], }; - let serialized_data = mock_miner_message.serialize_to_vec(); - let deserialized_data = read_next::(&mut &serialized_data[..]) + let serialized_data = mock_block.serialize_to_vec(); + let deserialized_data = read_next::(&mut &serialized_data[..]) .expect("Failed to deserialize MockSignData"); - assert_eq!(mock_miner_message, deserialized_data); + assert_eq!(mock_block, deserialized_data); } } diff --git a/stacks-signer/src/v0/signer.rs b/stacks-signer/src/v0/signer.rs index c32af06f3f..64622646e3 100644 --- a/stacks-signer/src/v0/signer.rs +++ b/stacks-signer/src/v0/signer.rs @@ -16,13 +16,12 @@ use std::fmt::Debug; use std::sync::mpsc::Sender; use blockstack_lib::net::api::postblock_proposal::BlockValidateResponse; -use clarity::consts::{CHAIN_ID_MAINNET, CHAIN_ID_TESTNET}; use clarity::types::chainstate::StacksPrivateKey; use clarity::types::{PrivateKey, StacksEpochId}; use clarity::util::hash::MerkleHashFunc; use clarity::util::secp256k1::Secp256k1PublicKey; use libsigner::v0::messages::{ - BlockResponse, MessageSlotID, MockSignature, RejectCode, SignerMessage, + BlockResponse, MessageSlotID, MockProposal, MockSignature, RejectCode, SignerMessage, }; use libsigner::{BlockProposal, SignerEvent}; use slog::{slog_debug, slog_error, slog_info, slog_warn}; @@ -140,6 +139,25 @@ impl SignerTrait for Signer { "push_result" => ?block_push_result, ); } + SignerMessage::MockProposal(mock_proposal) => { + let epoch = match stacks_client.get_node_epoch() { + Ok(epoch) => epoch, + Err(e) => { + warn!("{self}: Failed to determine node epoch. Cannot mock sign: {e}"); + return; + } + }; + debug!("{self}: received a mock block proposal."; + "current_reward_cycle" => current_reward_cycle, + "epoch" => ?epoch + ); + if epoch == StacksEpochId::Epoch25 + && self.reward_cycle == current_reward_cycle + { + // We are in epoch 2.5, so we should mock mine to prove we are still alive. + self.mock_sign(mock_proposal.clone()); + } + } _ => {} } } @@ -165,22 +183,6 @@ impl SignerTrait for Signer { ); } *sortition_state = None; - let epoch = match stacks_client.get_node_epoch() { - Ok(epoch) => epoch, - Err(e) => { - warn!("{self}: Failed to determine node epoch. Cannot mock sign: {e}"); - return; - } - }; - debug!("{self}: Epoch 2.5 signer received a new burn block event."; - "burn_height" => burn_height, - "current_reward_cycle" => current_reward_cycle, - "epoch" => ?epoch - ); - if epoch == StacksEpochId::Epoch25 && self.reward_cycle == current_reward_cycle { - // We are in epoch 2.5, so we should mock mine to prove we are still alive. - self.mock_sign(*burn_height, stacks_client); - } } } } @@ -482,26 +484,9 @@ impl Signer { } /// Send a mock signature to stackerdb to prove we are still alive - fn mock_sign(&mut self, burn_block_height: u64, stacks_client: &StacksClient) { - let Ok(peer_info) = stacks_client.get_peer_info() else { - warn!("{self}: Failed to get peer info. Cannot mock sign."); - return; - }; - let chain_id = if self.mainnet { - CHAIN_ID_MAINNET - } else { - CHAIN_ID_TESTNET - }; - info!("Mock signing for burn block {burn_block_height:?}"; - "stacks_tip_consensus_hash" => ?peer_info.stacks_tip_consensus_hash.clone(), - "stacks_tip" => ?peer_info.stacks_tip.clone(), - "peer_burn_block_height" => peer_info.burn_block_height, - "pox_consensus" => ?peer_info.pox_consensus.clone(), - "server_version" => peer_info.server_version.clone(), - "chain_id" => chain_id - ); - let mock_signature = - MockSignature::new(burn_block_height, peer_info, chain_id, &self.private_key); + fn mock_sign(&mut self, mock_proposal: MockProposal) { + info!("{self}: Mock signing mock proposal: {mock_proposal:?}"); + let mock_signature = MockSignature::new(mock_proposal, &self.private_key); let message = SignerMessage::MockSignature(mock_signature); if let Err(e) = self .stackerdb diff --git a/testnet/stacks-node/src/nakamoto_node/sign_coordinator.rs b/testnet/stacks-node/src/nakamoto_node/sign_coordinator.rs index b366d93132..b266d700d4 100644 --- a/testnet/stacks-node/src/nakamoto_node/sign_coordinator.rs +++ b/testnet/stacks-node/src/nakamoto_node/sign_coordinator.rs @@ -770,12 +770,10 @@ impl SignCoordinator { debug!("Received block pushed message. Ignoring."); continue; } - SignerMessageV0::MockSignature(_) => { - debug!("Received mock signature message. Ignoring."); - continue; - } - SignerMessageV0::MockMinerMessage(_) => { - debug!("Received mock miner message. Ignoring."); + SignerMessageV0::MockSignature(_) + | SignerMessageV0::MockProposal(_) + | SignerMessageV0::MockBlock(_) => { + debug!("Received mock message. Ignoring."); continue; } }; diff --git a/testnet/stacks-node/src/neon_node.rs b/testnet/stacks-node/src/neon_node.rs index 818a2cf00b..fc5e0d8055 100644 --- a/testnet/stacks-node/src/neon_node.rs +++ b/testnet/stacks-node/src/neon_node.rs @@ -144,7 +144,7 @@ use std::io::{Read, Write}; use std::net::SocketAddr; use std::sync::mpsc::{Receiver, TrySendError}; use std::thread::JoinHandle; -use std::time::Duration; +use std::time::{Duration, Instant}; use std::{fs, mem, thread}; use clarity::boot_util::boot_code_id; @@ -152,7 +152,7 @@ use clarity::vm::ast::ASTRules; use clarity::vm::costs::ExecutionCost; use clarity::vm::types::{PrincipalData, QualifiedContractIdentifier}; use libsigner::v0::messages::{ - MessageSlotID, MinerSlotID, MockMinerMessage, PeerInfo, SignerMessage, + MessageSlotID, MinerSlotID, MockBlock, MockProposal, MockSignature, PeerInfo, SignerMessage, }; use libsigner::{SignerSession, StackerDBSession}; use stacks::burnchains::bitcoin::address::{BitcoinAddress, LegacyBitcoinAddressType}; @@ -2262,26 +2262,79 @@ impl BlockMinerThread { return false; } - /// Read any mock signatures from stackerdb and respond to them - pub fn send_mock_miner_message(&mut self) -> Result<(), ChainstateError> { - let miner_config = self.config.get_miner_config(); - if !miner_config.pre_nakamoto_miner_messaging { - debug!("Pre-Nakamoto mock miner messaging is disabled"); - return Ok(()); + /// Only used in mock signing to generate a peer info view + fn generate_peer_info(&self) -> PeerInfo { + // Create a peer info view of the current state + let server_version = version_string( + "stacks-node", + option_env!("STACKS_NODE_VERSION") + .or(option_env!("CARGO_PKG_VERSION")) + .unwrap_or("0.0.0.0"), + ); + let stacks_tip_height = self.burn_block.canonical_stacks_tip_height; + let stacks_tip = self.burn_block.canonical_stacks_tip_hash; + let stacks_tip_consensus_hash = self.burn_block.canonical_stacks_tip_consensus_hash; + let pox_consensus = self.burn_block.consensus_hash; + let burn_block_height = self.burn_block.block_height; + + PeerInfo { + burn_block_height, + stacks_tip_consensus_hash, + stacks_tip, + stacks_tip_height, + pox_consensus, + server_version, } + } - let burn_db_path = self.config.get_burn_db_file_path(); - let burn_db = SortitionDB::open(&burn_db_path, false, self.burnchain.pox_constants.clone()) - .expect("FATAL: could not open sortition DB"); - let epoch_id = SortitionDB::get_stacks_epoch(burn_db.conn(), self.burn_block.block_height)? - .expect("FATAL: no epoch defined") - .epoch_id; - if epoch_id != StacksEpochId::Epoch25 { - debug!("Mock miner messaging is disabled for non-epoch 2.5 blocks."; - "epoch_id" => epoch_id.to_string() - ); - return Ok(()); + /// Only used in mock signing to retrieve the mock signatures for the given mock proposal + fn wait_for_mock_signatures( + &self, + mock_proposal: &MockProposal, + stackerdbs: &StackerDBs, + timeout: Duration, + ) -> Result, ChainstateError> { + let reward_cycle = self + .burnchain + .block_height_to_reward_cycle(self.burn_block.block_height) + .expect("BUG: block commit exists before first block height"); + let signers_contract_id = MessageSlotID::BlockResponse + .stacker_db_contract(self.config.is_mainnet(), reward_cycle); + let slot_ids: Vec<_> = stackerdbs + .get_signers(&signers_contract_id) + .expect("FATAL: could not get signers from stacker DB") + .into_iter() + .enumerate() + .map(|(slot_id, _)| { + u32::try_from(slot_id).expect("FATAL: too many signers to fit into u32 range") + }) + .collect(); + let mock_poll_start = Instant::now(); + let mut mock_signatures = vec![]; + // Because we don't care really if all signers reach quorum and this is just for testing purposes, + // we don't need to wait for ALL signers to sign the mock proposal and should not slow down mining too much + // Just wait a min amount of time for the mock signatures to come in + while mock_signatures.len() < slot_ids.len() && mock_poll_start.elapsed() < timeout { + let chunks = stackerdbs.get_latest_chunks(&signers_contract_id, &slot_ids)?; + for chunk in chunks { + if let Some(chunk) = chunk { + if let Ok(SignerMessage::MockSignature(mock_signature)) = + SignerMessage::consensus_deserialize(&mut chunk.as_slice()) + { + if mock_signature.mock_proposal == *mock_proposal + && !mock_signatures.contains(&mock_signature) + { + mock_signatures.push(mock_signature); + } + } + } + } } + Ok(mock_signatures) + } + + /// Only used in mock signing to determine if the peer info view was already signed across + fn mock_block_exists(&self, peer_info: &PeerInfo) -> bool { let miner_contract_id = boot_code_id(MINERS_NAME, self.config.is_mainnet()); let mut miners_stackerdb = StackerDBSession::new(&self.config.node.rpc_bind, miner_contract_id); @@ -2292,113 +2345,110 @@ impl BlockMinerThread { if message.is_empty() { continue; } - let Ok(SignerMessage::MockMinerMessage(miner_message)) = + let Ok(SignerMessage::MockBlock(mock_block)) = SignerMessage::consensus_deserialize(&mut message.as_slice()) else { continue; }; - if miner_message.peer_info.burn_block_height == self.burn_block.block_height { - debug!( - "Already sent mock miner message for tenure burn block height {:?}", - self.burn_block.block_height - ); - return Ok(()); + if mock_block.mock_proposal.peer_info == *peer_info { + return true; } } } } - // Retrieve any MockSignatures from stackerdb - let mut mock_signatures = Vec::new(); - let reward_cycle = self - .burnchain - .block_height_to_reward_cycle(self.burn_block.block_height) - .expect("BUG: block commit exists before first block height"); - let signers_contract_id = MessageSlotID::MockSignature - .stacker_db_contract(self.config.is_mainnet(), reward_cycle); - // Get the slots for every signer - let stackerdbs = StackerDBs::connect(&self.config.get_stacker_db_file_path(), false)?; - let slot_ids: Vec<_> = stackerdbs - .get_signers(&signers_contract_id) - .expect("FATAL: could not get signers from stacker DB") - .into_iter() - .enumerate() - .map(|(slot_id, _)| { - u32::try_from(slot_id).expect("FATAL: too many signers to fit into u32 range") - }) - .collect(); - let chunks = stackerdbs.get_latest_chunks(&signers_contract_id, &slot_ids)?; - for chunk in chunks { - if let Some(chunk) = chunk { - if let Ok(SignerMessage::MockSignature(mock_signature)) = - SignerMessage::consensus_deserialize(&mut chunk.as_slice()) - { - if mock_signature.sign_data.event_burn_block_height - == self.burn_block.block_height - { - mock_signatures.push(mock_signature); - } - } - } + false + } + + /// Read any mock signatures from stackerdb and respond to them + pub fn send_mock_miner_messages(&mut self) -> Result<(), ChainstateError> { + let miner_config = self.config.get_miner_config(); + if !miner_config.pre_nakamoto_miner_messaging { + debug!("Pre-Nakamoto mock miner messaging is disabled"); + return Ok(()); } - let server_version = version_string( - "stacks-node", - option_env!("STACKS_NODE_VERSION") - .or(option_env!("CARGO_PKG_VERSION")) - .unwrap_or("0.0.0.0"), - ); - let stacks_tip_height = self.burn_block.canonical_stacks_tip_height; - let stacks_tip = self.burn_block.canonical_stacks_tip_hash; - let stacks_tip_consensus_hash = self.burn_block.canonical_stacks_tip_consensus_hash; - let pox_consensus = self.burn_block.consensus_hash; - let burn_block_height = self.burn_block.block_height; + let burn_db_path = self.config.get_burn_db_file_path(); + let burn_db = SortitionDB::open(&burn_db_path, false, self.burnchain.pox_constants.clone()) + .expect("FATAL: could not open sortition DB"); + let epoch_id = SortitionDB::get_stacks_epoch(burn_db.conn(), self.burn_block.block_height)? + .expect("FATAL: no epoch defined") + .epoch_id; + if epoch_id != StacksEpochId::Epoch25 { + debug!("Mock miner messaging is disabled for non-epoch 2.5 blocks."; + "epoch_id" => epoch_id.to_string() + ); + return Ok(()); + } - let peer_info = PeerInfo { - burn_block_height, - stacks_tip_consensus_hash, - stacks_tip, - stacks_tip_height, - pox_consensus, - server_version, - }; + let mining_key = miner_config + .mining_key + .expect("Cannot mock sign without mining key"); - let message = MockMinerMessage { - peer_info, - chain_id: self.config.burnchain.chain_id, - mock_signatures, - }; + // Create a peer info view of the current state + let peer_info = self.generate_peer_info(); + if self.mock_block_exists(&peer_info) { + debug!( + "Already sent mock miner block proposal for current peer info view. Not sending another mock proposal." + ); + return Ok(()); + } - info!("Sending mock miner message in response to mock signatures for burn block {:?}", message.peer_info.burn_block_height; - "stacks_tip_consensus_hash" => ?message.peer_info.stacks_tip_consensus_hash.clone(), - "stacks_tip" => ?message.peer_info.stacks_tip.clone(), - "peer_burn_block_height" => message.peer_info.burn_block_height, - "pox_consensus" => ?message.peer_info.pox_consensus.clone(), - "server_version" => message.peer_info.server_version.clone(), - "chain_id" => message.chain_id, - "num_mock_signatures" => message.mock_signatures.len(), - ); + // find out which slot we're in. If we are not the latest sortition winner, we should not be sending anymore messages anyway + let stackerdbs = StackerDBs::connect(&self.config.get_stacker_db_file_path(), false)?; let (_, miners_info) = NakamotoChainState::make_miners_stackerdb_config(&burn_db, &self.burn_block)?; - - // find out which slot we're in. If we are not the latest sortition winner, we should not be sending anymore messages anyway let idx = miners_info.get_latest_winner_index(); let sortitions = miners_info.get_sortitions(); let election_sortition = *sortitions .get(idx as usize) .expect("FATAL: latest winner index out of bounds"); + let miner_contract_id = boot_code_id(MINERS_NAME, self.config.is_mainnet()); + let mut miners_stackerdb = + StackerDBSession::new(&self.config.node.rpc_bind, miner_contract_id); + + let mock_proposal = + MockProposal::new(peer_info, self.config.burnchain.chain_id, &mining_key); + + info!("Sending mock proposal to stackerdb: {mock_proposal:?}"); + + if let Err(e) = SignCoordinator::send_miners_message( + &mining_key, + &burn_db, + &self.burn_block, + &stackerdbs, + SignerMessage::MockProposal(mock_proposal.clone()), + MinerSlotID::BlockProposal, // There is no specific slot for mock miner messages. We use BlockProposal for MockProposal as well. + self.config.is_mainnet(), + &mut miners_stackerdb, + &election_sortition, + ) { + warn!("Failed to send mock proposal to stackerdb: {:?}", &e); + return Ok(()); + } + + // Retrieve any MockSignatures from stackerdb + let mock_signatures = + self.wait_for_mock_signatures(&mock_proposal, &stackerdbs, Duration::from_secs(10))?; + + let mock_block = MockBlock { + mock_proposal, + mock_signatures, + }; + + info!("Sending mock block to stackerdb: {mock_block:?}"); if let Err(e) = SignCoordinator::send_miners_message( &miner_config.mining_key.expect("BUG: no mining key"), &burn_db, &self.burn_block, &stackerdbs, - SignerMessage::MockMinerMessage(message.clone()), - MinerSlotID::BlockProposal, // There is no specific slot for mock miner messages + SignerMessage::MockBlock(mock_block.clone()), + MinerSlotID::BlockPushed, // There is no specific slot for mock miner messages self.config.is_mainnet(), &mut miners_stackerdb, &election_sortition, ) { - warn!("Failed to send mock miner message: {:?}", &e); + warn!("Failed to send mock block to stackerdb: {:?}", &e); } Ok(()) } @@ -3744,8 +3794,8 @@ impl RelayerThread { .name(format!("miner-block-{}", self.local_peer.data_url)) .stack_size(BLOCK_PROCESSOR_STACK_SIZE) .spawn(move || { - if let Err(e) = miner_thread_state.send_mock_miner_message() { - warn!("Failed to send mock miner message: {}", e); + if let Err(e) = miner_thread_state.send_mock_miner_messages() { + warn!("Failed to send mock miner messages: {}", e); } miner_thread_state.run_tenure() }) diff --git a/testnet/stacks-node/src/tests/signer/v0.rs b/testnet/stacks-node/src/tests/signer/v0.rs index 631d92c83c..79bed1739f 100644 --- a/testnet/stacks-node/src/tests/signer/v0.rs +++ b/testnet/stacks-node/src/tests/signer/v0.rs @@ -2216,205 +2216,27 @@ fn mock_sign_epoch_25() { .clone() .unwrap(); let epoch_3 = &epochs[StacksEpoch::find_epoch_by_id(&epochs, StacksEpochId::Epoch30).unwrap()]; - let epoch_3_start_height = epoch_3.start_height; + let epoch_3_boundary = epoch_3.start_height - 1; // We only advance to the boundary as epoch 2.5 miner gets torn down at the boundary signer_test.boot_to_epoch_25_reward_cycle(); info!("------------------------- Test Processing Epoch 2.5 Tenures -------------------------"); // Mine until epoch 3.0 and ensure that no more mock signatures are received - let mut reward_cycle = signer_test.get_current_reward_cycle(); - let mut stackerdb = StackerDB::new( - &signer_test.running_nodes.conf.node.rpc_bind, - StacksPrivateKey::new(), // We are just reading so don't care what the key is - false, - reward_cycle, - SignerSlotID(0), // We are just reading so again, don't care about index. - ); - let mut signer_slot_ids: Vec<_> = signer_test + let reward_cycle = signer_test.get_current_reward_cycle(); + let signer_slot_ids: Vec<_> = signer_test .get_signer_indices(reward_cycle) .iter() .map(|id| id.0) .collect(); + let signer_keys = signer_test.get_signer_public_keys(reward_cycle); + let signer_public_keys: Vec<_> = signer_keys.signers.into_values().collect(); assert_eq!(signer_slot_ids.len(), num_signers); - // Mine until epoch 3.0 and ensure we get a new mock signature per epoch 2.5 sortition - let main_poll_time = Instant::now(); - while signer_test - .running_nodes - .btc_regtest_controller - .get_headers_height() - < epoch_3_start_height - { - next_block_and( - &mut signer_test.running_nodes.btc_regtest_controller, - 60, - || Ok(true), - ) - .unwrap(); - let current_burn_block_height = signer_test - .running_nodes - .btc_regtest_controller - .get_headers_height(); - if current_burn_block_height - % signer_test - .running_nodes - .conf - .get_burnchain() - .pox_constants - .reward_cycle_length as u64 - == 0 - { - reward_cycle += 1; - debug!("Rolling over reward cycle to {:?}", reward_cycle); - stackerdb = StackerDB::new( - &signer_test.running_nodes.conf.node.rpc_bind, - StacksPrivateKey::new(), // We are just reading so don't care what the key is - false, - reward_cycle, - SignerSlotID(0), // We are just reading so again, don't care about index. - ); - signer_slot_ids = signer_test - .get_signer_indices(reward_cycle) - .iter() - .map(|id| id.0) - .collect(); - assert_eq!(signer_slot_ids.len(), num_signers); - } - let mut mock_signatures = vec![]; - let mock_poll_time = Instant::now(); - debug!("Waiting for mock signatures for burn block height {current_burn_block_height}"); - while mock_signatures.len() != num_signers { - std::thread::sleep(Duration::from_millis(100)); - let messages: Vec = StackerDB::get_messages( - stackerdb - .get_session_mut(&MessageSlotID::MockSignature) - .expect("Failed to get BlockResponse stackerdb session"), - &signer_slot_ids, - ) - .expect("Failed to get message from stackerdb"); - for message in messages { - if let SignerMessage::MockSignature(mock_signature) = message { - if mock_signature.sign_data.event_burn_block_height == current_burn_block_height - { - if !mock_signatures.contains(&mock_signature) { - mock_signatures.push(mock_signature); - } - } - } - } - assert!( - mock_poll_time.elapsed() <= Duration::from_secs(15), - "Failed to find mock signatures within timeout" - ); - } - assert!( - main_poll_time.elapsed() <= Duration::from_secs(45), - "Timed out waiting to advance epoch 3.0" - ); - } - info!("------------------------- Test Processing Epoch 3.0 Tenure -------------------------"); - let old_messages: Vec = StackerDB::get_messages( - stackerdb - .get_session_mut(&MessageSlotID::MockSignature) - .expect("Failed to get BlockResponse stackerdb session"), - &signer_slot_ids, - ) - .expect("Failed to get message from stackerdb"); - let old_signatures = old_messages - .iter() - .filter_map(|message| { - if let SignerMessage::MockSignature(mock_signature) = message { - Some(mock_signature) - } else { - None - } - }) - .collect::>(); - next_block_and( - &mut signer_test.running_nodes.btc_regtest_controller, - 60, - || Ok(true), - ) - .unwrap(); - // Wait a bit to ensure no new mock signatures show up - std::thread::sleep(Duration::from_secs(5)); - let new_messages: Vec = StackerDB::get_messages( - stackerdb - .get_session_mut(&MessageSlotID::MockSignature) - .expect("Failed to get BlockResponse stackerdb session"), - &signer_slot_ids, - ) - .expect("Failed to get message from stackerdb"); - let new_signatures = new_messages - .iter() - .filter_map(|message| { - if let SignerMessage::MockSignature(mock_signature) = message { - Some(mock_signature) - } else { - None - } - }) - .collect::>(); - assert_eq!(old_signatures, new_signatures); -} - -#[test] -#[ignore] -/// This test checks that Epoch 2.5 miners will issue a MockMinerMessage per burn block they receive -/// including the mock signature from the signers. -fn mock_miner_message_epoch_25() { - if env::var("BITCOIND_TEST") != Ok("1".into()) { - return; - } - - tracing_subscriber::registry() - .with(fmt::layer()) - .with(EnvFilter::from_default_env()) - .init(); - - info!("------------------------- Test Setup -------------------------"); - let num_signers = 5; - let sender_sk = Secp256k1PrivateKey::new(); - let sender_addr = tests::to_addr(&sender_sk); - let send_amt = 100; - let send_fee = 180; - - let mut signer_test: SignerTest = SignerTest::new_with_config_modifications( - num_signers, - vec![(sender_addr.clone(), send_amt + send_fee)], - Some(Duration::from_secs(5)), - |_| {}, - |node_config| { - let epochs = node_config.burnchain.epochs.as_mut().unwrap(); - for epoch in epochs.iter_mut() { - if epoch.epoch_id == StacksEpochId::Epoch25 { - epoch.end_height = 251; - } - if epoch.epoch_id == StacksEpochId::Epoch30 { - epoch.start_height = 251; - } - } - }, - &[], - ); - - let epochs = signer_test - .running_nodes - .conf - .burnchain - .epochs - .clone() - .unwrap(); - let epoch_3 = &epochs[StacksEpoch::find_epoch_by_id(&epochs, StacksEpochId::Epoch30).unwrap()]; - let epoch_3_boundary = epoch_3.start_height - 1; - - signer_test.boot_to_epoch_25_reward_cycle(); - - info!("------------------------- Test Processing Epoch 2.5 Tenures -------------------------"); let miners_stackerdb_contract = boot_code_id(MINERS_NAME, false); + + // Mine until epoch 3.0 and ensure we get a new mock block per epoch 2.5 sortition let main_poll_time = Instant::now(); - let mut mock_miner_message = None; // Only advance to the boundary as the epoch 2.5 miner will be shut down at this point. while signer_test .running_nodes @@ -2422,6 +2244,7 @@ fn mock_miner_message_epoch_25() { .get_headers_height() < epoch_3_boundary { + let mut mock_block_mesage = None; let mock_poll_time = Instant::now(); next_block_and( &mut signer_test.running_nodes.btc_regtest_controller, @@ -2434,8 +2257,7 @@ fn mock_miner_message_epoch_25() { .btc_regtest_controller .get_headers_height(); debug!("Waiting for mock miner message for burn block height {current_burn_block_height}"); - - while mock_miner_message.is_none() { + while mock_block_mesage.is_none() { std::thread::sleep(Duration::from_millis(100)); let chunks = test_observer::get_stackerdb_chunks(); for chunk in chunks @@ -2451,14 +2273,29 @@ fn mock_miner_message_epoch_25() { if chunk.data.is_empty() { continue; } - let SignerMessage::MockMinerMessage(message) = + let SignerMessage::MockBlock(mock_block) = SignerMessage::consensus_deserialize(&mut chunk.data.as_slice()) .expect("Failed to deserialize SignerMessage") else { continue; }; - if message.peer_info.burn_block_height == current_burn_block_height { - mock_miner_message = Some(message); + if mock_block.mock_proposal.peer_info.burn_block_height == current_burn_block_height + { + assert_eq!(mock_block.mock_signatures.len(), num_signers); + mock_block + .mock_signatures + .iter() + .for_each(|mock_signature| { + assert!(signer_public_keys.iter().any(|signer| { + mock_signature + .verify( + &StacksPublicKey::from_slice(signer.to_bytes().as_slice()) + .unwrap(), + ) + .expect("Failed to verify mock signature") + })); + }); + mock_block_mesage = Some(mock_block); break; } } @@ -2467,10 +2304,9 @@ fn mock_miner_message_epoch_25() { "Failed to find mock miner message within timeout" ); } - mock_miner_message = None; assert!( main_poll_time.elapsed() <= Duration::from_secs(45), - "Timed out waiting to advance epoch 3.0" + "Timed out waiting to advance epoch 3.0 boundary" ); } } From f12961e7b704705bcca56bad154f2326afa101bb Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Thu, 15 Aug 2024 16:36:40 -0400 Subject: [PATCH 15/20] Rename pre_nakamoto_miner_messaging to pre_nakamoto_mock_signing Signed-off-by: Jacinta Ferrant --- testnet/stacks-node/src/config.rs | 10 +++++----- testnet/stacks-node/src/neon_node.rs | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/testnet/stacks-node/src/config.rs b/testnet/stacks-node/src/config.rs index 4528e07222..c6c0abfd25 100644 --- a/testnet/stacks-node/src/config.rs +++ b/testnet/stacks-node/src/config.rs @@ -2332,8 +2332,8 @@ pub struct MinerConfig { pub max_reorg_depth: u64, /// Amount of time while mining in nakamoto to wait for signers to respond to a proposed block pub wait_on_signers: Duration, - /// Whether to send miner messages in Epoch 2.5 through the .miners contract. This is used for testing. - pub pre_nakamoto_miner_messaging: bool, + /// Whether to mock sign in Epoch 2.5 through the .miners and .signers contracts. This is used for testing purposes in Epoch 2.5 only. + pub pre_nakamoto_mock_signing: bool, } impl Default for MinerConfig { @@ -2364,7 +2364,7 @@ impl Default for MinerConfig { max_reorg_depth: 3, // TODO: update to a sane value based on stackerdb benchmarking wait_on_signers: Duration::from_secs(200), - pre_nakamoto_miner_messaging: true, + pre_nakamoto_mock_signing: true, } } } @@ -2696,7 +2696,7 @@ pub struct MinerConfigFile { pub filter_origins: Option, pub max_reorg_depth: Option, pub wait_on_signers_ms: Option, - pub pre_nakamoto_miner_messaging: Option, + pub pre_nakamoto_mock_signing: Option, } impl MinerConfigFile { @@ -2799,7 +2799,7 @@ impl MinerConfigFile { .wait_on_signers_ms .map(Duration::from_millis) .unwrap_or(miner_default_config.wait_on_signers), - pre_nakamoto_miner_messaging: self.pre_nakamoto_miner_messaging.unwrap_or(true), + pre_nakamoto_mock_signing: self.pre_nakamoto_mock_signing.unwrap_or(true), }) } } diff --git a/testnet/stacks-node/src/neon_node.rs b/testnet/stacks-node/src/neon_node.rs index fc5e0d8055..238a677e4d 100644 --- a/testnet/stacks-node/src/neon_node.rs +++ b/testnet/stacks-node/src/neon_node.rs @@ -2362,8 +2362,8 @@ impl BlockMinerThread { /// Read any mock signatures from stackerdb and respond to them pub fn send_mock_miner_messages(&mut self) -> Result<(), ChainstateError> { let miner_config = self.config.get_miner_config(); - if !miner_config.pre_nakamoto_miner_messaging { - debug!("Pre-Nakamoto mock miner messaging is disabled"); + if !miner_config.pre_nakamoto_mock_signing { + debug!("Pre-Nakamoto mock signing is disabled"); return Ok(()); } From 82e390d19ba3d782d3a2a21d0bd85933afc604ad Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Thu, 15 Aug 2024 16:41:07 -0400 Subject: [PATCH 16/20] Add a bit more logging Signed-off-by: Jacinta Ferrant --- testnet/stacks-node/src/neon_node.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/testnet/stacks-node/src/neon_node.rs b/testnet/stacks-node/src/neon_node.rs index 238a677e4d..5f0720d1a4 100644 --- a/testnet/stacks-node/src/neon_node.rs +++ b/testnet/stacks-node/src/neon_node.rs @@ -2418,7 +2418,7 @@ impl BlockMinerThread { &self.burn_block, &stackerdbs, SignerMessage::MockProposal(mock_proposal.clone()), - MinerSlotID::BlockProposal, // There is no specific slot for mock miner messages. We use BlockProposal for MockProposal as well. + MinerSlotID::BlockProposal, // There is no specific slot for mock miner messages so we use BlockProposal for MockProposal as well. self.config.is_mainnet(), &mut miners_stackerdb, &election_sortition, @@ -2428,6 +2428,7 @@ impl BlockMinerThread { } // Retrieve any MockSignatures from stackerdb + info!("Waiting for mock signatures..."); let mock_signatures = self.wait_for_mock_signatures(&mock_proposal, &stackerdbs, Duration::from_secs(10))?; @@ -2443,7 +2444,7 @@ impl BlockMinerThread { &self.burn_block, &stackerdbs, SignerMessage::MockBlock(mock_block.clone()), - MinerSlotID::BlockPushed, // There is no specific slot for mock miner messages + MinerSlotID::BlockPushed, // There is no specific slot for mock miner messages. Let's use BlockPushed for MockBlock since MockProposal uses BlockProposal. self.config.is_mainnet(), &mut miners_stackerdb, &election_sortition, From b4987f7ed7b2324db70196ed1e495cdf03c16f24 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Fri, 16 Aug 2024 08:57:40 -0400 Subject: [PATCH 17/20] CRC: improve logging Signed-off-by: Jacinta Ferrant --- stacks-signer/src/v0/signer.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stacks-signer/src/v0/signer.rs b/stacks-signer/src/v0/signer.rs index 64622646e3..9245220e94 100644 --- a/stacks-signer/src/v0/signer.rs +++ b/stacks-signer/src/v0/signer.rs @@ -147,14 +147,14 @@ impl SignerTrait for Signer { return; } }; - debug!("{self}: received a mock block proposal."; + info!("{self}: received a mock block proposal."; "current_reward_cycle" => current_reward_cycle, "epoch" => ?epoch ); if epoch == StacksEpochId::Epoch25 && self.reward_cycle == current_reward_cycle { - // We are in epoch 2.5, so we should mock mine to prove we are still alive. + // We are in epoch 2.5, so we should mock sign to prove we are still alive. self.mock_sign(mock_proposal.clone()); } } From 62f5a6a7813ab94999063ba4aecdcc9612fe5cea Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Fri, 16 Aug 2024 16:42:36 -0400 Subject: [PATCH 18/20] Do not enable pre nakamoto mock signing unless the miner key is set Signed-off-by: Jacinta Ferrant --- testnet/stacks-node/src/config.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/testnet/stacks-node/src/config.rs b/testnet/stacks-node/src/config.rs index 20b5d07355..30a5990319 100644 --- a/testnet/stacks-node/src/config.rs +++ b/testnet/stacks-node/src/config.rs @@ -1168,6 +1168,10 @@ impl Config { .validate() .map_err(|e| format!("Atlas config error: {e}"))?; + if miner.mining_key.is_none() && miner.pre_nakamoto_mock_signing { + return Err("Cannot use pre_nakamoto_mock_signing without a mining_key".to_string()); + } + Ok(Config { config_path: config_file.__path, node, @@ -2384,7 +2388,7 @@ impl Default for MinerConfig { max_reorg_depth: 3, // TODO: update to a sane value based on stackerdb benchmarking wait_on_signers: Duration::from_secs(200), - pre_nakamoto_mock_signing: true, + pre_nakamoto_mock_signing: false, // Should only default true if mining key is set } } } @@ -2739,6 +2743,12 @@ pub struct MinerConfigFile { impl MinerConfigFile { fn into_config_default(self, miner_default_config: MinerConfig) -> Result { + let mining_key = self + .mining_key + .as_ref() + .map(|x| Secp256k1PrivateKey::from_hex(x)) + .transpose()?; + let pre_nakamoto_mock_signing = mining_key.is_some(); Ok(MinerConfig { first_attempt_time_ms: self .first_attempt_time_ms @@ -2837,7 +2847,9 @@ impl MinerConfigFile { .wait_on_signers_ms .map(Duration::from_millis) .unwrap_or(miner_default_config.wait_on_signers), - pre_nakamoto_mock_signing: self.pre_nakamoto_mock_signing.unwrap_or(true), + pre_nakamoto_mock_signing: self + .pre_nakamoto_mock_signing + .unwrap_or(pre_nakamoto_mock_signing), // Should only default true if mining key is set }) } } From c8d8743cd01d6cbfed1e1e9444ea2adf6621344e Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Fri, 16 Aug 2024 16:43:02 -0400 Subject: [PATCH 19/20] Remove panic in tests when deserializing the block proposal slot due to mock signing using it for mock proposals Signed-off-by: Jacinta Ferrant --- testnet/stacks-node/src/tests/nakamoto_integrations.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/testnet/stacks-node/src/tests/nakamoto_integrations.rs b/testnet/stacks-node/src/tests/nakamoto_integrations.rs index c370ca53f6..24b7745419 100644 --- a/testnet/stacks-node/src/tests/nakamoto_integrations.rs +++ b/testnet/stacks-node/src/tests/nakamoto_integrations.rs @@ -391,7 +391,8 @@ pub fn get_latest_block_proposal( let message: SignerMessageV0 = miners_stackerdb.get_latest(miner_slot_id.start).ok()??; let SignerMessageV0::BlockProposal(block_proposal) = message else { - panic!("Expected a signer message block proposal. Got {message:?}"); + warn!("Expected a block proposal. Got {message:?}"); + return None; }; block_proposal.block }; From 1610ce3698c0ab4a5fe12c75a3b72ec05674c8f9 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Fri, 16 Aug 2024 17:24:44 -0400 Subject: [PATCH 20/20] Set pre nakamoto mock signing to true for mock sign test Signed-off-by: Jacinta Ferrant --- libsigner/src/v0/messages.rs | 19 +++++++++---------- testnet/stacks-node/src/tests/signer/v0.rs | 1 + 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/libsigner/src/v0/messages.rs b/libsigner/src/v0/messages.rs index b767431c60..5f7b82a937 100644 --- a/libsigner/src/v0/messages.rs +++ b/libsigner/src/v0/messages.rs @@ -964,22 +964,21 @@ mod test { let bad_private_key = StacksPrivateKey::new(); let bad_public_key = StacksPublicKey::from_private(&bad_private_key); - let mut mock_signature = random_mock_proposal(); - mock_signature.sign(&private_key).unwrap(); - assert!(!mock_signature + let mut mock_proposal = random_mock_proposal(); + assert!(!mock_proposal .verify(&public_key) - .expect("Failed to verify MockSignature")); + .expect("Failed to verify MockProposal")); - mock_signature + mock_proposal .sign(&private_key) - .expect("Failed to sign MockSignature"); + .expect("Failed to sign MockProposal"); - assert!(mock_signature + assert!(mock_proposal .verify(&public_key) - .expect("Failed to verify MockSignature")); - assert!(!mock_signature + .expect("Failed to verify MockProposal")); + assert!(!mock_proposal .verify(&bad_public_key) - .expect("Failed to verify MockSignature")); + .expect("Failed to verify MockProposal")); } #[test] diff --git a/testnet/stacks-node/src/tests/signer/v0.rs b/testnet/stacks-node/src/tests/signer/v0.rs index 1d06c429e9..115fce4c83 100644 --- a/testnet/stacks-node/src/tests/signer/v0.rs +++ b/testnet/stacks-node/src/tests/signer/v0.rs @@ -2440,6 +2440,7 @@ fn mock_sign_epoch_25() { Some(Duration::from_secs(5)), |_| {}, |node_config| { + node_config.miner.pre_nakamoto_mock_signing = true; let epochs = node_config.burnchain.epochs.as_mut().unwrap(); for epoch in epochs.iter_mut() { if epoch.epoch_id == StacksEpochId::Epoch25 {