Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: use block proposal struct for miner -> signers communication #4511

Merged
merged 1 commit into from
Mar 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
37 changes: 34 additions & 3 deletions libsigner/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>),
Expand All @@ -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.
Expand Down Expand Up @@ -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)
Expand Down
3 changes: 2 additions & 1 deletion libsigner/src/libsigner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
25 changes: 20 additions & 5 deletions stacks-signer/src/signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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:?}");
});
Expand Down
4 changes: 2 additions & 2 deletions stackslib/src/chainstate/nakamoto/miner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down
54 changes: 35 additions & 19 deletions testnet/stacks-node/src/nakamoto_node/miner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) =
Expand Down