mirror of
https://github.com/alexgo-io/stacks-puppet-node.git
synced 2026-01-12 22:43:42 +08:00
Merge pull request #4527 from stacks-network/dream-team-fixes
Fixes discovered while bringing up the pre-launch testnet
This commit is contained in:
@@ -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.
|
||||
@@ -195,11 +226,15 @@ impl EventStopSignaler for SignerStopSignaler {
|
||||
// We need to send actual data to trigger the event receiver
|
||||
let body = "Yo. Shut this shit down!".to_string();
|
||||
let req = format!(
|
||||
"POST /shutdown HTTP/1.0\r\nContent-Length: {}\r\n\r\n{}",
|
||||
&body.len(),
|
||||
"POST /shutdown HTTP/1.1\r\nHost: {}\r\nConnection: close\r\nContent-Length: {}\r\nContent-Type: text/plain\r\n\r\n{}",
|
||||
self.local_addr,
|
||||
body.len(),
|
||||
body
|
||||
);
|
||||
stream.write_all(req.as_bytes()).unwrap();
|
||||
match stream.write_all(req.as_bytes()) {
|
||||
Err(e) => error!("Failed to send shutdown request: {}", e),
|
||||
_ => (),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -337,10 +372,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)
|
||||
|
||||
@@ -238,12 +238,12 @@ pub fn run_http_request<S: Read + Write>(
|
||||
|
||||
let req_txt = if let Some(content_type) = content_type {
|
||||
format!(
|
||||
"{} {} HTTP/1.0\r\nHost: {}\r\nConnection: close\r\nContent-Type: {}\r\n{}User-Agent: libsigner/0.1\r\nAccept: */*\r\n\r\n",
|
||||
"{} {} HTTP/1.1\r\nHost: {}\r\nConnection: close\r\nContent-Type: {}\r\n{}User-Agent: libsigner/0.1\r\nAccept: */*\r\n\r\n",
|
||||
verb, path, host, content_type, content_length_hdr
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"{} {} HTTP/1.0\r\nHost: {}\r\nConnection: close\r\n{}User-Agent: libsigner/0.1\r\nAccept: */*\r\n\r\n",
|
||||
"{} {} HTTP/1.1\r\nHost: {}\r\nConnection: close\r\n{}User-Agent: libsigner/0.1\r\nAccept: */*\r\n\r\n",
|
||||
verb, path, host, content_length_hdr
|
||||
)
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -823,6 +823,27 @@ pub enum BlockResponse {
|
||||
Rejected(BlockRejection),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for BlockResponse {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
BlockResponse::Accepted(a) => {
|
||||
write!(
|
||||
f,
|
||||
"BlockAccepted: signer_sighash = {}, signature = {}",
|
||||
a.0, a.1
|
||||
)
|
||||
}
|
||||
BlockResponse::Rejected(r) => {
|
||||
write!(
|
||||
f,
|
||||
"BlockRejected: signer_sighash = {}, code = {}, reason = {}",
|
||||
r.reason_code, r.reason, r.signer_signature_hash
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockResponse {
|
||||
/// Create a new accepted BlockResponse for the provided block signer signature hash and signature
|
||||
pub fn accepted(hash: Sha512Trunc256Sum, sig: Signature) -> Self {
|
||||
|
||||
@@ -135,7 +135,12 @@ fn test_simple_signer() {
|
||||
|
||||
let ev = &thread_chunks[num_sent];
|
||||
let body = serde_json::to_string(ev).unwrap();
|
||||
let req = format!("POST /stackerdb_chunks HTTP/1.0\r\nConnection: close\r\nContent-Length: {}\r\n\r\n{}", &body.len(), body);
|
||||
let req = format!(
|
||||
"POST /stackerdb_chunks HTTP/1.1\r\nHost: {}\r\nConnection: close\r\nContent-Type: application/json\r\nContent-Length: {}\r\n\r\n{}",
|
||||
endpoint,
|
||||
&body.len(),
|
||||
body
|
||||
);
|
||||
debug!("Send:\n{}", &req);
|
||||
|
||||
sock.write_all(req.as_bytes()).unwrap();
|
||||
@@ -188,13 +193,16 @@ fn test_status_endpoint() {
|
||||
return;
|
||||
}
|
||||
};
|
||||
let req = "GET /status HTTP/1.0\r\nConnection: close\r\n\r\n";
|
||||
let req = format!(
|
||||
"GET /status HTTP/1.1\r\nHost: {}\r\nConnection: close\r\n\r\n",
|
||||
endpoint
|
||||
);
|
||||
|
||||
sock.write_all(req.as_bytes()).unwrap();
|
||||
let mut buf = [0; 128];
|
||||
let _ = sock.read(&mut buf).unwrap();
|
||||
let res_str = std::str::from_utf8(&buf).unwrap();
|
||||
let expected_status_res = "HTTP/1.0 200 OK\r\n";
|
||||
let expected_status_res = "HTTP/1.1 200 OK\r\n";
|
||||
assert_eq!(expected_status_res, &res_str[..expected_status_res.len()]);
|
||||
sock.flush().unwrap();
|
||||
});
|
||||
|
||||
@@ -69,6 +69,9 @@ impl From<PublicKeys> for CoordinatorSelector {
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether or not to rotate to new coordinators in `update_coordinator`
|
||||
const ROTATE_COORDINATORS: bool = false;
|
||||
|
||||
impl CoordinatorSelector {
|
||||
/// Update the coordinator id
|
||||
fn update_coordinator(&mut self, new_coordinator_ids: Vec<u32>) {
|
||||
@@ -81,7 +84,7 @@ impl CoordinatorSelector {
|
||||
.coordinator_ids
|
||||
.first()
|
||||
.expect("FATAL: No registered signers");
|
||||
if new_coordinator_id == self.coordinator_id {
|
||||
if ROTATE_COORDINATORS && new_coordinator_id == self.coordinator_id {
|
||||
// If the newly selected coordinator is the same as the current and we have more than one available, advance immediately to the next
|
||||
if self.coordinator_ids.len() > 1 {
|
||||
new_index = new_index.saturating_add(1);
|
||||
@@ -89,12 +92,16 @@ impl CoordinatorSelector {
|
||||
}
|
||||
new_index
|
||||
} else {
|
||||
let mut new_index = self.coordinator_index.saturating_add(1);
|
||||
if new_index == self.coordinator_ids.len() {
|
||||
// We have exhausted all potential coordinators. Go back to the start
|
||||
new_index = 0;
|
||||
if ROTATE_COORDINATORS {
|
||||
let mut new_index = self.coordinator_index.saturating_add(1);
|
||||
if new_index == self.coordinator_ids.len() {
|
||||
// We have exhausted all potential coordinators. Go back to the start
|
||||
new_index = 0;
|
||||
}
|
||||
new_index
|
||||
} else {
|
||||
self.coordinator_index
|
||||
}
|
||||
new_index
|
||||
};
|
||||
self.coordinator_id = *self
|
||||
.coordinator_ids
|
||||
@@ -136,7 +143,7 @@ impl CoordinatorSelector {
|
||||
)
|
||||
}
|
||||
|
||||
/// Calculate the ordered list of coordinator ids by comparing the provided public keys against the pox consensus hash
|
||||
/// Calculate the ordered list of coordinator ids by comparing the provided public keys
|
||||
pub fn calculate_coordinator_ids(
|
||||
public_keys: &PublicKeys,
|
||||
pox_consensus_hash: &ConsensusHash,
|
||||
|
||||
@@ -229,7 +229,7 @@ impl RunLoop {
|
||||
}
|
||||
|
||||
/// Refresh signer configuration for a specific reward cycle
|
||||
fn refresh_signer_config(&mut self, reward_cycle: u64) {
|
||||
fn refresh_signer_config(&mut self, reward_cycle: u64, current: bool) {
|
||||
let reward_index = reward_cycle % 2;
|
||||
let mut needs_refresh = false;
|
||||
if let Some(signer) = self.stacks_signers.get_mut(&reward_index) {
|
||||
@@ -266,7 +266,12 @@ impl RunLoop {
|
||||
.insert(reward_index, Signer::from(new_signer_config));
|
||||
debug!("Reward cycle #{reward_cycle} Signer #{signer_id} initialized.");
|
||||
} else {
|
||||
warn!("Signer is not registered for reward cycle {reward_cycle}. Waiting for confirmed registration...");
|
||||
// TODO: Update `current` here once the signer binary is tracking its own latest burnchain/stacks views.
|
||||
if current {
|
||||
warn!("Signer is not registered for the current reward cycle ({reward_cycle}). Waiting for confirmed registration...");
|
||||
} else {
|
||||
debug!("Signer is not registered for reward cycle {reward_cycle}. Waiting for confirmed registration...");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -275,11 +280,19 @@ impl RunLoop {
|
||||
/// Note: this will trigger DKG if required
|
||||
fn refresh_signers(&mut self, current_reward_cycle: u64) -> Result<(), ClientError> {
|
||||
let next_reward_cycle = current_reward_cycle.saturating_add(1);
|
||||
self.refresh_signer_config(current_reward_cycle);
|
||||
self.refresh_signer_config(next_reward_cycle);
|
||||
self.refresh_signer_config(current_reward_cycle, true);
|
||||
self.refresh_signer_config(next_reward_cycle, false);
|
||||
// TODO: do not use an empty consensus hash
|
||||
let pox_consensus_hash = ConsensusHash::empty();
|
||||
for signer in self.stacks_signers.values_mut() {
|
||||
let mut to_delete = Vec::new();
|
||||
for (idx, signer) in &mut self.stacks_signers {
|
||||
if signer.reward_cycle < current_reward_cycle {
|
||||
debug!("{signer}: Signer's tenure has completed.");
|
||||
// We don't really need this state, but it's useful for debugging
|
||||
signer.state = SignerState::TenureCompleted;
|
||||
to_delete.push(*idx);
|
||||
continue;
|
||||
}
|
||||
let old_coordinator_id = signer.coordinator_selector.get_coordinator().0;
|
||||
let updated_coordinator_id = signer
|
||||
.coordinator_selector
|
||||
@@ -302,6 +315,11 @@ impl RunLoop {
|
||||
})?;
|
||||
}
|
||||
}
|
||||
for i in to_delete.into_iter() {
|
||||
if let Some(signer) = self.stacks_signers.remove(&i) {
|
||||
info!("{signer}: Tenure has completed. Removing signer from runloop.",);
|
||||
}
|
||||
}
|
||||
if self.stacks_signers.is_empty() {
|
||||
info!("Signer is not registered for the current reward cycle ({current_reward_cycle}) or next reward cycle ({next_reward_cycle}). Waiting for confirmed registration...");
|
||||
self.state = State::Uninitialized;
|
||||
@@ -357,6 +375,26 @@ impl SignerRunLoop<Vec<OperationResult>, RunLoopCommand> for RunLoop {
|
||||
error!("Failed to refresh signers: {e}. Signer may have an outdated view of the network. Attempting to process event anyway.");
|
||||
}
|
||||
for signer in self.stacks_signers.values_mut() {
|
||||
if signer.state == SignerState::TenureCompleted {
|
||||
warn!("{signer}: Signer's tenure has completed. This signer should have been cleaned up during refresh.");
|
||||
continue;
|
||||
}
|
||||
let event_parity = match event {
|
||||
Some(SignerEvent::BlockValidationResponse(_)) => Some(current_reward_cycle % 2),
|
||||
// Block proposal events do have reward cycles, but each proposal has its own cycle,
|
||||
// and the vec could be heterogenous, so, don't differentiate.
|
||||
Some(SignerEvent::ProposedBlocks(_)) => None,
|
||||
Some(SignerEvent::SignerMessages(msg_parity, ..)) => {
|
||||
Some(u64::from(msg_parity) % 2)
|
||||
}
|
||||
Some(SignerEvent::StatusCheck) => None,
|
||||
None => None,
|
||||
};
|
||||
let other_signer_parity = (signer.reward_cycle + 1) % 2;
|
||||
if event_parity == Some(other_signer_parity) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Err(e) = signer.process_event(
|
||||
&self.stacks_client,
|
||||
event.as_ref(),
|
||||
|
||||
@@ -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};
|
||||
@@ -71,7 +73,7 @@ pub struct BlockInfo {
|
||||
/// The associated packet nonce request if we have one
|
||||
nonce_request: Option<NonceRequest>,
|
||||
/// Whether this block is already being signed over
|
||||
signed_over: bool,
|
||||
pub signed_over: bool,
|
||||
}
|
||||
|
||||
impl BlockInfo {
|
||||
@@ -126,6 +128,8 @@ pub enum State {
|
||||
Idle,
|
||||
/// The signer is executing a DKG or Sign round
|
||||
OperationInProgress,
|
||||
/// The signer's reward cycle has finished
|
||||
TenureCompleted,
|
||||
}
|
||||
|
||||
/// The stacks signer registered for the reward cycle
|
||||
@@ -170,8 +174,10 @@ impl std::fmt::Display for Signer {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"Reward Cycle #{} Signer #{}",
|
||||
self.reward_cycle, self.signer_id,
|
||||
"Cycle #{} Signer #{}(C:{})",
|
||||
self.reward_cycle,
|
||||
self.signer_id,
|
||||
self.coordinator_selector.get_coordinator().0,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -311,7 +317,7 @@ impl Signer {
|
||||
let signer_signature_hash = block.header.signer_signature_hash();
|
||||
let mut block_info = self
|
||||
.signer_db
|
||||
.block_lookup(&signer_signature_hash)
|
||||
.block_lookup(self.reward_cycle, &signer_signature_hash)
|
||||
.unwrap_or_else(|_| Some(BlockInfo::new(block.clone())))
|
||||
.unwrap_or_else(|| BlockInfo::new(block.clone()));
|
||||
if block_info.signed_over {
|
||||
@@ -333,7 +339,7 @@ impl Signer {
|
||||
debug!("{self}: ACK: {ack:?}",);
|
||||
block_info.signed_over = true;
|
||||
self.signer_db
|
||||
.insert_block(&block_info)
|
||||
.insert_block(self.reward_cycle, &block_info)
|
||||
.unwrap_or_else(|e| {
|
||||
error!("{self}: Failed to insert block in DB: {e:?}");
|
||||
});
|
||||
@@ -367,7 +373,10 @@ impl Signer {
|
||||
}
|
||||
State::OperationInProgress => {
|
||||
// We cannot execute the next command until the current one is finished...
|
||||
debug!("{self}: Waiting for coordinator {coordinator_id:?} operation to finish...",);
|
||||
debug!("{self}: Waiting for coordinator {coordinator_id:?} operation to finish. Coordinator state = {:?}", self.coordinator.state);
|
||||
}
|
||||
State::TenureCompleted => {
|
||||
warn!("{self}: Tenure completed. This signer should have been cleaned up during refresh.",);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -383,7 +392,10 @@ impl Signer {
|
||||
BlockValidateResponse::Ok(block_validate_ok) => {
|
||||
let signer_signature_hash = block_validate_ok.signer_signature_hash;
|
||||
// For mutability reasons, we need to take the block_info out of the map and add it back after processing
|
||||
let mut block_info = match self.signer_db.block_lookup(&signer_signature_hash) {
|
||||
let mut block_info = match self
|
||||
.signer_db
|
||||
.block_lookup(self.reward_cycle, &signer_signature_hash)
|
||||
{
|
||||
Ok(Some(block_info)) => block_info,
|
||||
Ok(None) => {
|
||||
// We have not seen this block before. Why are we getting a response for it?
|
||||
@@ -398,7 +410,7 @@ impl Signer {
|
||||
let is_valid = self.verify_block_transactions(stacks_client, &block_info.block);
|
||||
block_info.valid = Some(is_valid);
|
||||
self.signer_db
|
||||
.insert_block(&block_info)
|
||||
.insert_block(self.reward_cycle, &block_info)
|
||||
.expect(&format!("{self}: Failed to insert block in DB"));
|
||||
info!(
|
||||
"{self}: Treating block validation for block {} as valid: {:?}",
|
||||
@@ -409,7 +421,10 @@ impl Signer {
|
||||
}
|
||||
BlockValidateResponse::Reject(block_validate_reject) => {
|
||||
let signer_signature_hash = block_validate_reject.signer_signature_hash;
|
||||
let mut block_info = match self.signer_db.block_lookup(&signer_signature_hash) {
|
||||
let mut block_info = match self
|
||||
.signer_db
|
||||
.block_lookup(self.reward_cycle, &signer_signature_hash)
|
||||
{
|
||||
Ok(Some(block_info)) => block_info,
|
||||
Ok(None) => {
|
||||
// We have not seen this block before. Why are we getting a response for it?
|
||||
@@ -452,8 +467,9 @@ impl Signer {
|
||||
{
|
||||
// We are the coordinator. Trigger a signing round for this block
|
||||
debug!(
|
||||
"{self}: triggering a signing round over the block {}",
|
||||
block_info.block.header.block_hash()
|
||||
"{self}: attempt to trigger a signing round for block";
|
||||
"signer_sighash" => %block_info.block.header.signer_signature_hash(),
|
||||
"block_hash" => %block_info.block.header.block_hash(),
|
||||
);
|
||||
self.commands.push_back(Command::Sign {
|
||||
block: block_info.block.clone(),
|
||||
@@ -471,7 +487,7 @@ impl Signer {
|
||||
}
|
||||
}
|
||||
self.signer_db
|
||||
.insert_block(&block_info)
|
||||
.insert_block(self.reward_cycle, &block_info)
|
||||
.expect(&format!("{self}: Failed to insert block in DB"));
|
||||
}
|
||||
|
||||
@@ -497,20 +513,59 @@ 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 {
|
||||
// Store the block in our cache
|
||||
self.signer_db
|
||||
.insert_block(&BlockInfo::new(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())
|
||||
.unwrap_or_else(|e| {
|
||||
warn!("{self}: Failed to submit block for validation: {e:?}");
|
||||
});
|
||||
fn handle_proposed_blocks(
|
||||
&mut self,
|
||||
stacks_client: &StacksClient,
|
||||
proposals: &[BlockProposalSigners],
|
||||
) {
|
||||
for proposal in proposals {
|
||||
if proposal.reward_cycle != self.reward_cycle {
|
||||
debug!(
|
||||
"{self}: Received proposal for block outside of my reward cycle, ignoring.";
|
||||
"proposal_reward_cycle" => proposal.reward_cycle,
|
||||
"proposal_burn_height" => proposal.burn_height,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
let sig_hash = proposal.block.header.signer_signature_hash();
|
||||
match self.signer_db.block_lookup(self.reward_cycle, &sig_hash) {
|
||||
Ok(Some(block)) => {
|
||||
debug!(
|
||||
"{self}: Received proposal for block already known, ignoring new proposal.";
|
||||
"signer_sighash" => %sig_hash,
|
||||
"proposal_burn_height" => proposal.burn_height,
|
||||
"vote" => ?block.vote.as_ref().map(|v| {
|
||||
if v.rejected {
|
||||
"REJECT"
|
||||
} else {
|
||||
"ACCEPT"
|
||||
}
|
||||
}),
|
||||
"signed_over" => block.signed_over,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
Ok(None) => {
|
||||
// Store the block in our cache
|
||||
self.signer_db
|
||||
.insert_block(self.reward_cycle, &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(proposal.block.clone())
|
||||
.unwrap_or_else(|e| {
|
||||
warn!("{self}: Failed to submit block for validation: {e:?}");
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
error!(
|
||||
"{self}: Failed to lookup block in DB: {e:?}. Dropping proposal request."
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -568,13 +623,16 @@ impl Signer {
|
||||
|
||||
match self
|
||||
.signer_db
|
||||
.block_lookup(&block_vote.signer_signature_hash)
|
||||
.block_lookup(self.reward_cycle, &block_vote.signer_signature_hash)
|
||||
.expect(&format!("{self}: Failed to connect to DB"))
|
||||
.map(|b| b.vote)
|
||||
{
|
||||
Some(Some(vote)) => {
|
||||
// Overwrite with our agreed upon value in case another message won majority or the coordinator is trying to cheat...
|
||||
debug!("{self}: set vote for {} to {vote:?}", block_vote.rejected);
|
||||
debug!(
|
||||
"{self}: Set vote (rejected = {}) to {vote:?}", block_vote.rejected;
|
||||
"requested_sighash" => %block_vote.signer_signature_hash,
|
||||
);
|
||||
request.message = vote.serialize_to_vec();
|
||||
true
|
||||
}
|
||||
@@ -582,7 +640,10 @@ impl Signer {
|
||||
// We never agreed to sign this block. Reject it.
|
||||
// This can happen if the coordinator received enough votes to sign yes
|
||||
// or no on a block before we received validation from the stacks node.
|
||||
debug!("{self}: Received a signature share request for a block we never agreed to sign. Ignore it.");
|
||||
debug!(
|
||||
"{self}: Received a signature share request for a block we never agreed to sign. Ignore it.";
|
||||
"requested_sighash" => %block_vote.signer_signature_hash,
|
||||
);
|
||||
false
|
||||
}
|
||||
None => {
|
||||
@@ -590,7 +651,8 @@ impl Signer {
|
||||
// blocks we have seen a Nonce Request for (and subsequent validation)
|
||||
// We are missing the context here necessary to make a decision. Reject the block
|
||||
debug!(
|
||||
"{self}: Received a signature share request from an unknown block. Reject it."
|
||||
"{self}: Received a signature share request from an unknown block. Reject it.";
|
||||
"requested_sighash" => %block_vote.signer_signature_hash,
|
||||
);
|
||||
false
|
||||
}
|
||||
@@ -615,7 +677,7 @@ impl Signer {
|
||||
let signer_signature_hash = block.header.signer_signature_hash();
|
||||
let mut block_info = match self
|
||||
.signer_db
|
||||
.block_lookup(&signer_signature_hash)
|
||||
.block_lookup(self.reward_cycle, &signer_signature_hash)
|
||||
.expect("Failed to connect to signer DB")
|
||||
{
|
||||
Some(block_info) => block_info,
|
||||
@@ -623,7 +685,7 @@ impl Signer {
|
||||
debug!("{self}: We have received a block sign request for a block we have not seen before. Cache the nonce request and submit the block for validation...");
|
||||
let block_info = BlockInfo::new_with_request(block.clone(), nonce_request.clone());
|
||||
self.signer_db
|
||||
.insert_block(&block_info)
|
||||
.insert_block(self.reward_cycle, &block_info)
|
||||
.expect(&format!("{self}: Failed to insert block in DB"));
|
||||
stacks_client
|
||||
.submit_block_for_validation(block)
|
||||
@@ -643,7 +705,7 @@ impl Signer {
|
||||
|
||||
self.determine_vote(&mut block_info, nonce_request);
|
||||
self.signer_db
|
||||
.insert_block(&block_info)
|
||||
.insert_block(self.reward_cycle, &block_info)
|
||||
.expect(&format!("{self}: Failed to insert block in DB"));
|
||||
true
|
||||
}
|
||||
@@ -967,22 +1029,20 @@ impl Signer {
|
||||
return;
|
||||
};
|
||||
|
||||
// TODO: proper garbage collection...This is currently our only cleanup of blocks
|
||||
self.signer_db
|
||||
.remove_block(&block_vote.signer_signature_hash)
|
||||
.expect(&format!("{self}: Failed to remove block from to signer DB"));
|
||||
|
||||
let block_submission = if block_vote.rejected {
|
||||
// We signed a rejection message. Return a rejection message
|
||||
BlockResponse::rejected(block_vote.signer_signature_hash, signature.clone()).into()
|
||||
BlockResponse::rejected(block_vote.signer_signature_hash, signature.clone())
|
||||
} else {
|
||||
// we agreed to sign the block hash. Return an approval message
|
||||
BlockResponse::accepted(block_vote.signer_signature_hash, signature.clone()).into()
|
||||
BlockResponse::accepted(block_vote.signer_signature_hash, signature.clone())
|
||||
};
|
||||
|
||||
// Submit signature result to miners to observe
|
||||
debug!("{self}: submit block response {block_submission:?}");
|
||||
if let Err(e) = self.stackerdb.send_message_with_retry(block_submission) {
|
||||
info!("{self}: Submit block response: {block_submission}");
|
||||
if let Err(e) = self
|
||||
.stackerdb
|
||||
.send_message_with_retry(block_submission.into())
|
||||
{
|
||||
warn!("{self}: Failed to send block submission to stacker-db: {e:?}");
|
||||
}
|
||||
}
|
||||
@@ -1005,7 +1065,7 @@ impl Signer {
|
||||
};
|
||||
let Some(block_info) = self
|
||||
.signer_db
|
||||
.block_lookup(&block_vote.signer_signature_hash)
|
||||
.block_lookup(self.reward_cycle, &block_vote.signer_signature_hash)
|
||||
.expect(&format!("{self}: Failed to connect to signer DB"))
|
||||
else {
|
||||
debug!(
|
||||
|
||||
@@ -18,6 +18,8 @@ use std::path::Path;
|
||||
|
||||
use blockstack_lib::util_lib::db::{query_row, sqlite_open, table_exists, Error as DBError};
|
||||
use rusqlite::{Connection, Error as SqliteError, OpenFlags, NO_PARAMS};
|
||||
use slog::slog_debug;
|
||||
use stacks_common::debug;
|
||||
use stacks_common::util::hash::Sha512Trunc256Sum;
|
||||
|
||||
use crate::signer::BlockInfo;
|
||||
@@ -32,8 +34,10 @@ pub struct SignerDb {
|
||||
|
||||
const CREATE_BLOCKS_TABLE: &'static str = "
|
||||
CREATE TABLE IF NOT EXISTS blocks (
|
||||
signer_signature_hash TEXT PRIMARY KEY,
|
||||
block_info TEXT NOT NULL
|
||||
reward_cycle INTEGER NOT NULL,
|
||||
signer_signature_hash TEXT NOT NULL,
|
||||
block_info TEXT NOT NULL,
|
||||
PRIMARY KEY (reward_cycle, signer_signature_hash)
|
||||
)";
|
||||
|
||||
impl SignerDb {
|
||||
@@ -68,11 +72,15 @@ impl SignerDb {
|
||||
|
||||
/// Fetch a block from the database using the block's
|
||||
/// `signer_signature_hash`
|
||||
pub fn block_lookup(&self, hash: &Sha512Trunc256Sum) -> Result<Option<BlockInfo>, DBError> {
|
||||
pub fn block_lookup(
|
||||
&self,
|
||||
reward_cycle: u64,
|
||||
hash: &Sha512Trunc256Sum,
|
||||
) -> Result<Option<BlockInfo>, DBError> {
|
||||
let result: Option<String> = query_row(
|
||||
&self.db,
|
||||
"SELECT block_info FROM blocks WHERE signer_signature_hash = ?",
|
||||
&[format!("{}", hash)],
|
||||
"SELECT block_info FROM blocks WHERE reward_cycle = ? AND signer_signature_hash = ?",
|
||||
&[&reward_cycle.to_string(), &format!("{}", hash)],
|
||||
)?;
|
||||
if let Some(block_info) = result {
|
||||
let block_info: BlockInfo =
|
||||
@@ -85,14 +93,30 @@ impl SignerDb {
|
||||
|
||||
/// Insert a block into the database.
|
||||
/// `hash` is the `signer_signature_hash` of the block.
|
||||
pub fn insert_block(&mut self, block_info: &BlockInfo) -> Result<(), DBError> {
|
||||
pub fn insert_block(
|
||||
&mut self,
|
||||
reward_cycle: u64,
|
||||
block_info: &BlockInfo,
|
||||
) -> Result<(), DBError> {
|
||||
let block_json =
|
||||
serde_json::to_string(&block_info).expect("Unable to serialize block info");
|
||||
let hash = &block_info.signer_signature_hash();
|
||||
let block_id = &block_info.block.block_id();
|
||||
let signed_over = &block_info.signed_over;
|
||||
debug!(
|
||||
"Inserting block_info: reward_cycle = {reward_cycle}, sighash = {hash}, block_id = {block_id}, signed = {signed_over} vote = {:?}",
|
||||
block_info.vote.as_ref().map(|v| {
|
||||
if v.rejected {
|
||||
"REJECT"
|
||||
} else {
|
||||
"ACCEPT"
|
||||
}
|
||||
})
|
||||
);
|
||||
self.db
|
||||
.execute(
|
||||
"INSERT OR REPLACE INTO blocks (signer_signature_hash, block_info) VALUES (?1, ?2)",
|
||||
&[format!("{}", hash), block_json],
|
||||
"INSERT OR REPLACE INTO blocks (reward_cycle, signer_signature_hash, block_info) VALUES (?1, ?2, ?3)",
|
||||
&[reward_cycle.to_string(), format!("{}", hash), block_json],
|
||||
)
|
||||
.map_err(|e| {
|
||||
return DBError::Other(format!(
|
||||
@@ -104,10 +128,15 @@ impl SignerDb {
|
||||
}
|
||||
|
||||
/// Remove a block
|
||||
pub fn remove_block(&mut self, hash: &Sha512Trunc256Sum) -> Result<(), DBError> {
|
||||
pub fn remove_block(
|
||||
&mut self,
|
||||
reward_cycle: u64,
|
||||
hash: &Sha512Trunc256Sum,
|
||||
) -> Result<(), DBError> {
|
||||
debug!("Deleting block_info: sighash = {hash}");
|
||||
self.db.execute(
|
||||
"DELETE FROM blocks WHERE signer_signature_hash = ?",
|
||||
&[format!("{}", hash)],
|
||||
"DELETE FROM blocks WHERE reward_cycle = ? AND signer_signature_hash = ?",
|
||||
&[reward_cycle.to_string(), format!("{}", hash)],
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
@@ -178,16 +207,23 @@ mod tests {
|
||||
|
||||
fn test_basic_signer_db_with_path(db_path: impl AsRef<Path>) {
|
||||
let mut db = SignerDb::new(db_path).expect("Failed to create signer db");
|
||||
let reward_cycle = 1;
|
||||
let (block_info, block) = create_block();
|
||||
db.insert_block(&block_info)
|
||||
db.insert_block(reward_cycle, &block_info)
|
||||
.expect("Unable to insert block into db");
|
||||
|
||||
let block_info = db
|
||||
.block_lookup(&block.header.signer_signature_hash())
|
||||
.block_lookup(reward_cycle, &block.header.signer_signature_hash())
|
||||
.unwrap()
|
||||
.expect("Unable to get block from db");
|
||||
|
||||
assert_eq!(BlockInfo::new(block.clone()), block_info);
|
||||
|
||||
// Test looking up a block from a different reward cycle
|
||||
let block_info = db
|
||||
.block_lookup(reward_cycle + 1, &block.header.signer_signature_hash())
|
||||
.unwrap();
|
||||
assert!(block_info.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -205,12 +241,13 @@ mod tests {
|
||||
fn test_update_block() {
|
||||
let db_path = tmp_db_path();
|
||||
let mut db = SignerDb::new(db_path).expect("Failed to create signer db");
|
||||
let reward_cycle = 42;
|
||||
let (block_info, block) = create_block();
|
||||
db.insert_block(&block_info)
|
||||
db.insert_block(reward_cycle, &block_info)
|
||||
.expect("Unable to insert block into db");
|
||||
|
||||
let block_info = db
|
||||
.block_lookup(&block.header.signer_signature_hash())
|
||||
.block_lookup(reward_cycle, &block.header.signer_signature_hash())
|
||||
.unwrap()
|
||||
.expect("Unable to get block from db");
|
||||
|
||||
@@ -231,11 +268,11 @@ mod tests {
|
||||
rejected: false,
|
||||
};
|
||||
block_info.vote = Some(vote.clone());
|
||||
db.insert_block(&block_info)
|
||||
db.insert_block(reward_cycle, &block_info)
|
||||
.expect("Unable to insert block into db");
|
||||
|
||||
let block_info = db
|
||||
.block_lookup(&block.header.signer_signature_hash())
|
||||
.block_lookup(reward_cycle, &block.header.signer_signature_hash())
|
||||
.unwrap()
|
||||
.expect("Unable to get block from db");
|
||||
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -693,6 +693,12 @@ impl FromSql for ThresholdSignature {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ThresholdSignature {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
to_hex(&self.serialize_to_vec()).fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToSql for ThresholdSignature {
|
||||
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput> {
|
||||
let bytes = self.serialize_to_vec();
|
||||
|
||||
@@ -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;
|
||||
@@ -39,7 +39,6 @@ use stacks::chainstate::stacks::{
|
||||
TenureChangeCause, TenureChangePayload, ThresholdSignature, TransactionAnchorMode,
|
||||
TransactionPayload, TransactionVersion,
|
||||
};
|
||||
use stacks::core::FIRST_BURNCHAIN_CONSENSUS_HASH;
|
||||
use stacks::net::stackerdb::StackerDBs;
|
||||
use stacks_common::codec::{read_next, StacksMessageCodec};
|
||||
use stacks_common::types::chainstate::{StacksAddress, StacksBlockId};
|
||||
@@ -83,8 +82,6 @@ struct ParentTenureInfo {
|
||||
struct ParentStacksBlockInfo {
|
||||
/// Header metadata for the Stacks block we're going to build on top of
|
||||
stacks_parent_header: StacksHeaderInfo,
|
||||
/// the total amount burned in the sortition that selected the Stacks block parent
|
||||
parent_block_total_burn: u64,
|
||||
/// nonce to use for this new block's coinbase transaction
|
||||
coinbase_nonce: u64,
|
||||
parent_tenure: Option<ParentTenureInfo>,
|
||||
@@ -193,37 +190,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) =
|
||||
@@ -231,6 +244,14 @@ impl BlockMinerThread {
|
||||
{
|
||||
warn!("Error broadcasting block: {e:?}");
|
||||
} else {
|
||||
info!(
|
||||
"Miner: Block signed by signer set and broadcasted";
|
||||
"signer_sighash" => %new_block.header.signer_signature_hash(),
|
||||
"block_hash" => %new_block.header.block_hash(),
|
||||
"stacks_block_id" => %new_block.header.block_id(),
|
||||
"block_height" => new_block.header.chain_length,
|
||||
"consensus_hash" => %new_block.header.consensus_hash,
|
||||
);
|
||||
self.globals.coord().announce_new_stacks_block();
|
||||
}
|
||||
|
||||
@@ -668,7 +689,6 @@ impl BlockMinerThread {
|
||||
parent_tenure_blocks: 0,
|
||||
}),
|
||||
stacks_parent_header: chain_tip.metadata,
|
||||
parent_block_total_burn: 0,
|
||||
coinbase_nonce: 0,
|
||||
});
|
||||
};
|
||||
@@ -839,15 +859,11 @@ impl BlockMinerThread {
|
||||
block.header.miner_signature = miner_signature;
|
||||
|
||||
info!(
|
||||
"Miner: Succeeded assembling {} block #{}: {}, with {} txs",
|
||||
if parent_block_info.parent_block_total_burn == 0 {
|
||||
"Genesis"
|
||||
} else {
|
||||
"Stacks"
|
||||
},
|
||||
"Miner: Assembled block #{} for signer set proposal: {}, with {} txs",
|
||||
block.header.chain_length,
|
||||
block.header.block_hash(),
|
||||
block.txs.len(),
|
||||
block.txs.len();
|
||||
"signer_sighash" => %block.header.signer_signature_hash(),
|
||||
);
|
||||
|
||||
// last chance -- confirm that the stacks tip is unchanged (since it could have taken long
|
||||
@@ -895,26 +911,6 @@ impl ParentStacksBlockInfo {
|
||||
.expect("Failed to look up block's parent snapshot")
|
||||
.expect("Failed to look up block's parent snapshot");
|
||||
|
||||
let parent_sortition_id = &parent_snapshot.sortition_id;
|
||||
|
||||
let parent_block_total_burn =
|
||||
if &stacks_tip_header.consensus_hash == &FIRST_BURNCHAIN_CONSENSUS_HASH {
|
||||
0
|
||||
} else {
|
||||
let parent_burn_block =
|
||||
SortitionDB::get_block_snapshot(burn_db.conn(), parent_sortition_id)
|
||||
.expect("SortitionDB failure.")
|
||||
.ok_or_else(|| {
|
||||
error!(
|
||||
"Failed to find block snapshot for the parent sortition";
|
||||
"parent_sortition_id" => %parent_sortition_id
|
||||
);
|
||||
NakamotoNodeError::SnapshotNotFoundForChainTip
|
||||
})?;
|
||||
|
||||
parent_burn_block.total_burn
|
||||
};
|
||||
|
||||
// don't mine off of an old burnchain block
|
||||
let burn_chain_tip = SortitionDB::get_canonical_burn_chain_tip(burn_db.conn())
|
||||
.expect("FATAL: failed to query sortition DB for canonical burn chain tip");
|
||||
@@ -1009,7 +1005,6 @@ impl ParentStacksBlockInfo {
|
||||
|
||||
Ok(ParentStacksBlockInfo {
|
||||
stacks_parent_header: stacks_tip_header,
|
||||
parent_block_total_burn,
|
||||
coinbase_nonce,
|
||||
parent_tenure: parent_tenure_info,
|
||||
})
|
||||
|
||||
@@ -447,7 +447,9 @@ impl SignerTest {
|
||||
panic!("Received SignError {}", sign_error);
|
||||
}
|
||||
OperationResult::Dkg(point) => {
|
||||
panic!("Received aggregate_group_key {point}");
|
||||
// should not panic, because DKG may have just run for the
|
||||
// next reward cycle.
|
||||
info!("Received aggregate_group_key {point}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -963,8 +965,8 @@ fn stackerdb_sign() {
|
||||
|
||||
info!("------------------------- Test Setup -------------------------");
|
||||
|
||||
info!("Creating an invalid block to sign...");
|
||||
let header = NakamotoBlockHeader {
|
||||
info!("Creating invalid blocks to sign...");
|
||||
let header1 = NakamotoBlockHeader {
|
||||
version: 1,
|
||||
chain_length: 2,
|
||||
burn_spent: 3,
|
||||
@@ -976,12 +978,12 @@ fn stackerdb_sign() {
|
||||
signer_signature: ThresholdSignature::empty(),
|
||||
signer_bitvec: BitVec::zeros(1).unwrap(),
|
||||
};
|
||||
let mut block = NakamotoBlock {
|
||||
header,
|
||||
let mut block1 = NakamotoBlock {
|
||||
header: header1,
|
||||
txs: vec![],
|
||||
};
|
||||
let tx_merkle_root = {
|
||||
let txid_vecs = block
|
||||
let tx_merkle_root1 = {
|
||||
let txid_vecs = block1
|
||||
.txs
|
||||
.iter()
|
||||
.map(|tx| tx.txid().as_bytes().to_vec())
|
||||
@@ -989,14 +991,46 @@ fn stackerdb_sign() {
|
||||
|
||||
MerkleTree::<Sha512Trunc256Sum>::new(&txid_vecs).root()
|
||||
};
|
||||
block.header.tx_merkle_root = tx_merkle_root;
|
||||
block1.header.tx_merkle_root = tx_merkle_root1;
|
||||
|
||||
let header2 = NakamotoBlockHeader {
|
||||
version: 1,
|
||||
chain_length: 3,
|
||||
burn_spent: 4,
|
||||
consensus_hash: ConsensusHash([0x05; 20]),
|
||||
parent_block_id: StacksBlockId([0x06; 32]),
|
||||
tx_merkle_root: Sha512Trunc256Sum([0x07; 32]),
|
||||
state_index_root: TrieHash([0x08; 32]),
|
||||
miner_signature: MessageSignature::empty(),
|
||||
signer_signature: ThresholdSignature::empty(),
|
||||
signer_bitvec: BitVec::zeros(1).unwrap(),
|
||||
};
|
||||
let mut block2 = NakamotoBlock {
|
||||
header: header2,
|
||||
txs: vec![],
|
||||
};
|
||||
let tx_merkle_root2 = {
|
||||
let txid_vecs = block2
|
||||
.txs
|
||||
.iter()
|
||||
.map(|tx| tx.txid().as_bytes().to_vec())
|
||||
.collect();
|
||||
|
||||
MerkleTree::<Sha512Trunc256Sum>::new(&txid_vecs).root()
|
||||
};
|
||||
block2.header.tx_merkle_root = tx_merkle_root2;
|
||||
|
||||
// The block is invalid so the signers should return a signature across a rejection
|
||||
let block_vote = NakamotoBlockVote {
|
||||
signer_signature_hash: block.header.signer_signature_hash(),
|
||||
let block1_vote = NakamotoBlockVote {
|
||||
signer_signature_hash: block1.header.signer_signature_hash(),
|
||||
rejected: true,
|
||||
};
|
||||
let msg = block_vote.serialize_to_vec();
|
||||
let msg1 = block1_vote.serialize_to_vec();
|
||||
let block2_vote = NakamotoBlockVote {
|
||||
signer_signature_hash: block2.header.signer_signature_hash(),
|
||||
rejected: true,
|
||||
};
|
||||
let msg2 = block2_vote.serialize_to_vec();
|
||||
|
||||
let timeout = Duration::from_secs(200);
|
||||
let mut signer_test = SignerTest::new(10);
|
||||
@@ -1010,7 +1044,7 @@ fn stackerdb_sign() {
|
||||
let sign_command = RunLoopCommand {
|
||||
reward_cycle,
|
||||
command: SignerCommand::Sign {
|
||||
block: block.clone(),
|
||||
block: block1,
|
||||
is_taproot: false,
|
||||
merkle_root: None,
|
||||
},
|
||||
@@ -1018,7 +1052,7 @@ fn stackerdb_sign() {
|
||||
let sign_taproot_command = RunLoopCommand {
|
||||
reward_cycle,
|
||||
command: SignerCommand::Sign {
|
||||
block: block.clone(),
|
||||
block: block2,
|
||||
is_taproot: true,
|
||||
merkle_root: None,
|
||||
},
|
||||
@@ -1035,12 +1069,12 @@ fn stackerdb_sign() {
|
||||
let schnorr_proofs = signer_test.wait_for_taproot_signatures(timeout);
|
||||
|
||||
for frost_signature in frost_signatures {
|
||||
assert!(frost_signature.verify(&key, &msg));
|
||||
assert!(frost_signature.verify(&key, &msg1));
|
||||
}
|
||||
for schnorr_proof in schnorr_proofs {
|
||||
let tweaked_key = tweaked_public_key(&key, None);
|
||||
assert!(
|
||||
schnorr_proof.verify(&tweaked_key.x(), &msg),
|
||||
schnorr_proof.verify(&tweaked_key.x(), &msg2),
|
||||
"Schnorr proof verification failed"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user