diff --git a/libsigner/src/events.rs b/libsigner/src/events.rs index 0d73b9579a..ce26447e5a 100644 --- a/libsigner/src/events.rs +++ b/libsigner/src/events.rs @@ -50,11 +50,22 @@ use wsts::state_machine::signer; use crate::http::{decode_http_body, decode_http_request}; use crate::{EventError, SignerMessage}; +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +/// BlockProposal sent to signers +pub struct BlockProposalSigners { + /// The block itself + pub block: NakamotoBlock, + /// The burn height the block is mined during + pub burn_height: u64, + /// The reward cycle the block is mined during + pub reward_cycle: u64, +} + /// Event enum for newly-arrived signer subscribed events #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum SignerEvent { /// The miner proposed blocks for signers to observe and sign - ProposedBlocks(Vec), + ProposedBlocks(Vec), /// The signer messages for other signers and miners to observe /// The u32 is the signer set to which the message belongs (either 0 or 1) SignerMessages(u32, Vec), @@ -64,6 +75,26 @@ pub enum SignerEvent { StatusCheck, } +impl StacksMessageCodec for BlockProposalSigners { + fn consensus_serialize(&self, fd: &mut W) -> Result<(), CodecError> { + self.block.consensus_serialize(fd)?; + self.burn_height.consensus_serialize(fd)?; + self.reward_cycle.consensus_serialize(fd)?; + Ok(()) + } + + fn consensus_deserialize(fd: &mut R) -> Result { + let block = NakamotoBlock::consensus_deserialize(fd)?; + let burn_height = u64::consensus_deserialize(fd)?; + let reward_cycle = u64::consensus_deserialize(fd)?; + Ok(BlockProposalSigners { + block, + burn_height, + reward_cycle, + }) + } +} + /// Trait to implement a stop-signaler for the event receiver thread. /// The caller calls `send()` and the event receiver loop (which lives in a separate thread) will /// terminate. @@ -337,10 +368,10 @@ fn process_stackerdb_event( .map_err(|e| EventError::Deserialize(format!("Could not decode body to JSON: {:?}", &e)))?; let signer_event = if event.contract_id == boot_code_id(MINERS_NAME, is_mainnet) { - let blocks: Vec = event + let blocks: Vec = event .modified_slots .iter() - .filter_map(|chunk| read_next::(&mut &chunk.data[..]).ok()) + .filter_map(|chunk| read_next::(&mut &chunk.data[..]).ok()) .collect(); SignerEvent::ProposedBlocks(blocks) } else if event.contract_id.name.to_string().starts_with(SIGNERS_NAME) diff --git a/libsigner/src/libsigner.rs b/libsigner/src/libsigner.rs index e48f4014e1..1ae699d6ec 100644 --- a/libsigner/src/libsigner.rs +++ b/libsigner/src/libsigner.rs @@ -45,7 +45,8 @@ mod session; pub use crate::error::{EventError, RPCError}; pub use crate::events::{ - EventReceiver, EventStopSignaler, SignerEvent, SignerEventReceiver, SignerStopSignaler, + BlockProposalSigners, EventReceiver, EventStopSignaler, SignerEvent, SignerEventReceiver, + SignerStopSignaler, }; pub use crate::messages::{ BlockRejection, BlockResponse, RejectCode, SignerMessage, BLOCK_MSG_ID, TRANSACTIONS_MSG_ID, diff --git a/stacks-signer/src/signer.rs b/stacks-signer/src/signer.rs index 053be6755b..4159eedf36 100644 --- a/stacks-signer/src/signer.rs +++ b/stacks-signer/src/signer.rs @@ -24,7 +24,9 @@ use blockstack_lib::chainstate::stacks::boot::SIGNERS_VOTING_FUNCTION_NAME; use blockstack_lib::chainstate::stacks::StacksTransaction; use blockstack_lib::net::api::postblock_proposal::BlockValidateResponse; use hashbrown::HashSet; -use libsigner::{BlockRejection, BlockResponse, RejectCode, SignerEvent, SignerMessage}; +use libsigner::{ + BlockProposalSigners, BlockRejection, BlockResponse, RejectCode, SignerEvent, SignerMessage, +}; use serde_derive::{Deserialize, Serialize}; use slog::{slog_debug, slog_error, slog_info, slog_warn}; use stacks_common::codec::{read_next, StacksMessageCodec}; @@ -497,17 +499,30 @@ impl Signer { } /// Handle proposed blocks submitted by the miners to stackerdb - fn handle_proposed_blocks(&mut self, stacks_client: &StacksClient, blocks: &[NakamotoBlock]) { - for block in blocks { + fn handle_proposed_blocks( + &mut self, + stacks_client: &StacksClient, + proposals: &[BlockProposalSigners], + ) { + for proposal in proposals { + if proposal.reward_cycle != self.reward_cycle { + debug!( + "Signer #{}: Received proposal for block outside of my reward cycle, ignoring.", + self.signer_id; + "proposal_reward_cycle" => proposal.reward_cycle, + "proposal_burn_height" => proposal.burn_height, + ); + continue; + } // Store the block in our cache self.signer_db - .insert_block(&BlockInfo::new(block.clone())) + .insert_block(&BlockInfo::new(proposal.block.clone())) .unwrap_or_else(|e| { error!("{self}: Failed to insert block in DB: {e:?}"); }); // Submit the block for validation stacks_client - .submit_block_for_validation(block.clone()) + .submit_block_for_validation(proposal.block.clone()) .unwrap_or_else(|e| { warn!("{self}: Failed to submit block for validation: {e:?}"); }); diff --git a/stackslib/src/chainstate/nakamoto/miner.rs b/stackslib/src/chainstate/nakamoto/miner.rs index 5edeac4c63..961fd32db0 100644 --- a/stackslib/src/chainstate/nakamoto/miner.rs +++ b/stackslib/src/chainstate/nakamoto/miner.rs @@ -524,11 +524,11 @@ impl NakamotoBlockBuilder { /// Returns Some(chunk) if the given key corresponds to one of the expected miner slots /// Returns None if not /// Returns an error on signing or DB error - pub fn make_stackerdb_block_proposal( + pub fn make_stackerdb_block_proposal( sortdb: &SortitionDB, tip: &BlockSnapshot, stackerdbs: &StackerDBs, - block: &NakamotoBlock, + block: &T, miner_privkey: &StacksPrivateKey, miners_contract_id: &QualifiedContractIdentifier, ) -> Result, Error> { diff --git a/testnet/stacks-node/src/nakamoto_node/miner.rs b/testnet/stacks-node/src/nakamoto_node/miner.rs index 4a4411479d..c7e772b20e 100644 --- a/testnet/stacks-node/src/nakamoto_node/miner.rs +++ b/testnet/stacks-node/src/nakamoto_node/miner.rs @@ -23,8 +23,8 @@ use clarity::vm::clarity::ClarityConnection; use clarity::vm::types::{PrincipalData, QualifiedContractIdentifier}; use hashbrown::HashSet; use libsigner::{ - BlockResponse, RejectCode, SignerMessage, SignerSession, StackerDBSession, BLOCK_MSG_ID, - TRANSACTIONS_MSG_ID, + BlockProposalSigners, BlockResponse, RejectCode, SignerMessage, SignerSession, + StackerDBSession, BLOCK_MSG_ID, TRANSACTIONS_MSG_ID, }; use stacks::burnchains::{Burnchain, BurnchainParameters}; use stacks::chainstate::burn::db::sortdb::SortitionDB; @@ -199,37 +199,53 @@ impl BlockMinerThread { .expect("FATAL: could not open sortition DB"); let tip = SortitionDB::get_canonical_burn_chain_tip(sort_db.conn()) .expect("FATAL: could not retrieve chain tip"); + let reward_cycle = self + .burnchain + .pox_constants + .block_height_to_reward_cycle( + self.burnchain.first_block_height, + self.burn_block.block_height, + ) + .expect("FATAL: building on a burn block that is before the first burn block"); if let Some(new_block) = new_block { - match NakamotoBlockBuilder::make_stackerdb_block_proposal( + let proposal_msg = BlockProposalSigners { + block: new_block.clone(), + burn_height: self.burn_block.block_height, + reward_cycle, + }; + let proposal = match NakamotoBlockBuilder::make_stackerdb_block_proposal( &sort_db, &tip, &stackerdbs, - &new_block, + &proposal_msg, &miner_privkey, &miners_contract_id, ) { - Ok(Some(chunk)) => { - // Propose the block to the observing signers through the .miners stackerdb instance - 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); - match miners_stackerdb.put_chunk(&chunk) { - Ok(ack) => { - info!("Proposed block to stackerdb: {ack:?}"); - } - Err(e) => { - warn!("Failed to propose block to stackerdb {e:?}"); - return; - } - } - } + Ok(Some(chunk)) => chunk, Ok(None) => { warn!("Failed to propose block to stackerdb: no slot available"); + continue; } Err(e) => { warn!("Failed to propose block to stackerdb: {e:?}"); + continue; + } + }; + + // Propose the block to the observing signers through the .miners stackerdb instance + 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); + match miners_stackerdb.put_chunk(&proposal) { + Ok(ack) => { + info!("Proposed block to stackerdb: {ack:?}"); + } + Err(e) => { + warn!("Failed to propose block to stackerdb {e:?}"); + return; } } + self.globals.counters.bump_naka_proposed_blocks(); if let Err(e) =