feat: use block proposal struct for miner -> signers comms. check claimed reward-cycle

This commit is contained in:
Aaron Blankstein
2024-03-08 18:50:57 -06:00
parent db8f0cce88
commit d56895a600
5 changed files with 93 additions and 30 deletions

View File

@@ -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<NakamotoBlock>),
ProposedBlocks(Vec<BlockProposalSigners>),
/// 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<SignerMessage>),
@@ -64,6 +75,26 @@ pub enum SignerEvent {
StatusCheck,
}
impl StacksMessageCodec for BlockProposalSigners {
fn consensus_serialize<W: Write>(&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<R: Read>(fd: &mut R) -> Result<Self, CodecError> {
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<NakamotoBlock> = event
let blocks: Vec<BlockProposalSigners> = event
.modified_slots
.iter()
.filter_map(|chunk| read_next::<NakamotoBlock, _>(&mut &chunk.data[..]).ok())
.filter_map(|chunk| read_next::<BlockProposalSigners, _>(&mut &chunk.data[..]).ok())
.collect();
SignerEvent::ProposedBlocks(blocks)
} else if event.contract_id.name.to_string().starts_with(SIGNERS_NAME)

View File

@@ -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,

View File

@@ -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:?}");
});

View File

@@ -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<T: StacksMessageCodec>(
sortdb: &SortitionDB,
tip: &BlockSnapshot,
stackerdbs: &StackerDBs,
block: &NakamotoBlock,
block: &T,
miner_privkey: &StacksPrivateKey,
miners_contract_id: &QualifiedContractIdentifier,
) -> Result<Option<StackerDBChunkData>, Error> {

View File

@@ -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) =