Skip to content

Commit

Permalink
feat: update sign_coordinator and blind_signer for new message types
Browse files Browse the repository at this point in the history
  • Loading branch information
hstove committed May 15, 2024
1 parent 4532b97 commit d14e5b4
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 29 deletions.
52 changes: 46 additions & 6 deletions testnet/stacks-node/src/nakamoto_node/miner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ impl BlockMinerThread {
})?;

*attempts += 1;
let signature = coordinator.begin_sign(
let signature = coordinator.begin_sign_v1(
new_block,
burn_block_height,
*attempts,
Expand All @@ -339,10 +339,15 @@ impl BlockMinerThread {
fn gather_signatures(
&mut self,
new_block: &mut NakamotoBlock,
_burn_block_height: u64,
_stackerdbs: &mut StackerDBs,
_attempts: &mut u64,
burn_block_height: u64,
stackerdbs: &mut StackerDBs,
attempts: &mut u64,
) -> Result<(RewardSet, Vec<MessageSignature>), NakamotoNodeError> {
let Some(miner_privkey) = self.config.miner.mining_key else {
return Err(NakamotoNodeError::MinerConfigurationFailed(
"No mining key configured, cannot mine",
));
};
let sort_db = SortitionDB::open(
&self.config.get_burn_db_file_path(),
true,
Expand All @@ -356,6 +361,15 @@ impl BlockMinerThread {
.expect("FATAL: could not retrieve chain tip")
.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");

let reward_info = match sort_db.get_preprocessed_reward_set_of(&tip.sortition_id) {
Ok(Some(x)) => x,
Ok(None) => {
Expand All @@ -376,8 +390,34 @@ impl BlockMinerThread {
));
};

// TODO: collect signatures from signers
return Ok((reward_set, vec![]));
let miner_privkey_as_scalar = Scalar::from(miner_privkey.as_slice().clone());
let mut coordinator = SignCoordinator::new(
&reward_set,
reward_cycle,
miner_privkey_as_scalar,
Point::new(),
&stackerdbs,
&self.config,
)
.map_err(|e| {
NakamotoNodeError::SigningCoordinatorFailure(format!(
"Failed to initialize the signing coordinator. Cannot mine! {e:?}"
))
})?;

*attempts += 1;
let signature = coordinator.begin_sign_v0(
new_block,
burn_block_height,
*attempts,
&tip,
&self.burnchain,
&sort_db,
&stackerdbs,
&self.globals.counters,
)?;

return Ok((reward_set, signature));
}

fn get_stackerdb_contract_and_slots(
Expand Down
113 changes: 100 additions & 13 deletions testnet/stacks-node/src/nakamoto_node/sign_coordinator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ use std::sync::mpsc::Receiver;
use std::time::{Duration, Instant};

use hashbrown::{HashMap, HashSet};
use libsigner::v1::messages::{MessageSlotID, SignerMessage};
use libsigner::v0::messages::SignerMessage as SignerMessageV0;
use libsigner::v1::messages::{MessageSlotID, SignerMessage as SignerMessageV1};
use libsigner::{BlockProposal, SignerEntries, SignerEvent, SignerSession, StackerDBSession};
use stacks::burnchains::Burnchain;
use stacks::chainstate::burn::db::sortdb::SortitionDB;
Expand All @@ -28,6 +29,7 @@ use stacks::chainstate::stacks::events::StackerDBChunksEvent;
use stacks::chainstate::stacks::{Error as ChainstateError, ThresholdSignature};
use stacks::libstackerdb::StackerDBChunkData;
use stacks::net::stackerdb::StackerDBs;
use stacks::util::secp256k1::MessageSignature;
use stacks::util_lib::boot::boot_code_id;
use stacks_common::bitvec::BitVec;
use stacks_common::codec::StacksMessageCodec;
Expand Down Expand Up @@ -140,10 +142,10 @@ fn get_signer_commitments(
);
continue;
};
let Ok(SignerMessage::DkgResults {
let Ok(SignerMessageV1::DkgResults {
aggregate_key,
party_polynomials,
}) = SignerMessage::consensus_deserialize(&mut signer_data.as_slice())
}) = SignerMessageV1::consensus_deserialize(&mut signer_data.as_slice())
else {
warn!(
"Failed to parse DKG result, will look for results from other signers.";
Expand Down Expand Up @@ -314,12 +316,12 @@ impl SignCoordinator {
.expect("FATAL: tried to initialize WSTS coordinator before first burn block height")
}

fn send_signers_message(
fn send_signers_message<M: StacksMessageCodec>(
message_key: &Scalar,
sortdb: &SortitionDB,
tip: &BlockSnapshot,
stackerdbs: &StackerDBs,
message: SignerMessage,
message: M,
is_mainnet: bool,
miners_session: &mut StackerDBSession,
) -> Result<(), String> {
Expand Down Expand Up @@ -363,7 +365,7 @@ impl SignCoordinator {
}

#[cfg_attr(test, mutants::skip)]
pub fn begin_sign(
pub fn begin_sign_v1(
&mut self,
block: &NakamotoBlock,
burn_block_height: u64,
Expand Down Expand Up @@ -397,7 +399,7 @@ impl SignCoordinator {
"Failed to start signing round in FIRE coordinator: {e:?}"
))
})?;
Self::send_signers_message(
Self::send_signers_message::<SignerMessageV1>(
&self.message_key,
sortdb,
burn_tip,
Expand Down Expand Up @@ -483,11 +485,11 @@ impl SignCoordinator {
let packets: Vec<_> = messages
.into_iter()
.filter_map(|msg| match msg {
SignerMessage::DkgResults { .. }
| SignerMessage::BlockResponse(_)
| SignerMessage::EncryptedSignerState(_)
| SignerMessage::Transactions(_) => None,
SignerMessage::Packet(packet) => {
SignerMessageV1::DkgResults { .. }
| SignerMessageV1::BlockResponse(_)
| SignerMessageV1::EncryptedSignerState(_)
| SignerMessageV1::Transactions(_) => None,
SignerMessageV1::Packet(packet) => {
debug!("Received signers packet: {packet:?}");
if !packet.verify(&self.wsts_public_keys, &coordinator_pk) {
warn!("Failed to verify StackerDB packet: {packet:?}");
Expand Down Expand Up @@ -548,7 +550,7 @@ impl SignCoordinator {
}
}
for msg in outbound_msgs {
match Self::send_signers_message(
match Self::send_signers_message::<SignerMessageV1>(
&self.message_key,
sortdb,
burn_tip,
Expand All @@ -573,4 +575,89 @@ impl SignCoordinator {
"Timed out waiting for group signature".into(),
))
}

pub fn begin_sign_v0(
&mut self,
block: &NakamotoBlock,
burn_block_height: u64,
block_attempt: u64,
burn_tip: &BlockSnapshot,
burnchain: &Burnchain,
sortdb: &SortitionDB,
stackerdbs: &StackerDBs,
counters: &Counters,
) -> Result<Vec<MessageSignature>, NakamotoNodeError> {
let sign_id = Self::get_sign_id(burn_tip.block_height, burnchain);
let sign_iter_id = block_attempt;
let reward_cycle_id = burnchain
.block_height_to_reward_cycle(burn_tip.block_height)
.expect("FATAL: tried to initialize coordinator before first burn block height");
self.coordinator.current_sign_id = sign_id;
self.coordinator.current_sign_iter_id = sign_iter_id;

let block_proposal = BlockProposal {
block: block.clone(),
burn_height: burn_block_height,
reward_cycle: reward_cycle_id,
};

let block_proposal_message = SignerMessageV0::BlockProposal(block_proposal);
Self::send_signers_message::<SignerMessageV0>(
&self.message_key,
sortdb,
burn_tip,
&stackerdbs,
block_proposal_message,
self.is_mainnet,
&mut self.miners_session,
)
.map_err(NakamotoNodeError::SigningCoordinatorFailure)?;
counters.bump_naka_proposed_blocks();
#[cfg(test)]
{
// In test mode, short-circuit waiting for the signers if the TEST_SIGNING
// channel has been created. This allows integration tests for the stacks-node
// independent of the stacks-signer.
if let Some(signatures) =
crate::tests::nakamoto_integrations::TestSigningChannel::get_signature()
{
debug!("Short-circuiting waiting for signers, using test signature");
return Ok(signatures);
}
}

let Some(ref mut receiver) = self.receiver else {
return Err(NakamotoNodeError::SigningCoordinatorFailure(
"Failed to obtain the StackerDB event receiver".into(),
));
};

let start_ts = Instant::now();
while start_ts.elapsed() <= self.signing_round_timeout {
let event = match receiver.recv_timeout(EVENT_RECEIVER_POLL) {
Ok(event) => event,
Err(std::sync::mpsc::RecvTimeoutError::Timeout) => {
continue;
}
Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => {
return Err(NakamotoNodeError::SigningCoordinatorFailure(
"StackerDB event receiver disconnected".into(),
))
}
};

let is_signer_event =
event.contract_id.name.starts_with(SIGNERS_NAME) && event.contract_id.is_boot();
if !is_signer_event {
debug!("Ignoring StackerDB event for non-signer contract"; "contract" => %event.contract_id);
continue;
}

// TODO: get messages from signers
}

Err(NakamotoNodeError::SignerSignatureError(
"Timed out waiting for group signature".into(),
))
}
}
38 changes: 28 additions & 10 deletions testnet/stacks-node/src/tests/nakamoto_integrations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ use clarity::vm::costs::ExecutionCost;
use clarity::vm::types::{PrincipalData, QualifiedContractIdentifier};
use http_types::headers::AUTHORIZATION;
use lazy_static::lazy_static;
use libsigner::v1::messages::SignerMessage;
use libsigner::v0::messages::SignerMessage as SignerMessageV0;
use libsigner::v1::messages::SignerMessage as SignerMessageV1;
use libsigner::{BlockProposal, SignerSession, StackerDBSession};
use rand::RngCore;
use stacks::burnchains::{MagicBytes, Txid};
Expand Down Expand Up @@ -314,24 +315,41 @@ pub fn get_latest_block_proposal(
let proposed_block = {
let miner_contract_id = boot_code_id(MINERS_NAME, false);
let mut miners_stackerdb = StackerDBSession::new(&conf.node.rpc_bind, miner_contract_id);
let message: SignerMessage = miners_stackerdb
let message: SignerMessageV0 = miners_stackerdb
.get_latest(miner_slot_id.start)
.expect("Failed to get latest chunk from the miner slot ID")
.expect("No chunk found");
let SignerMessage::Packet(packet) = message else {
panic!("Expected a signer message packet. Got {message:?}");
let SignerMessageV0::BlockProposal(block_proposal) = message else {
panic!("Expected a signer message block proposal. Got {message:?}");
};
let Message::NonceRequest(nonce_request) = packet.msg else {
panic!("Expected a nonce request. Got {:?}", packet.msg);
};
let block_proposal =
BlockProposal::consensus_deserialize(&mut nonce_request.message.as_slice())
.expect("Failed to deserialize block proposal");
// TODO: use v1 message types behind epoch gate
// get_block_proposal_msg_v1(&mut miners_stackerdb, miner_slot_id.start);
block_proposal.block
};
Ok(proposed_block)
}

#[allow(dead_code)]
fn get_block_proposal_msg_v1(
miners_stackerdb: &mut StackerDBSession,
slot_id: u32,
) -> NakamotoBlock {
let message: SignerMessageV1 = miners_stackerdb
.get_latest(slot_id)
.expect("Failed to get latest chunk from the miner slot ID")
.expect("No chunk found");
let SignerMessageV1::Packet(packet) = message else {
panic!("Expected a signer message packet. Got {message:?}");
};
let Message::NonceRequest(nonce_request) = packet.msg else {
panic!("Expected a nonce request. Got {:?}", packet.msg);
};
let block_proposal =
BlockProposal::consensus_deserialize(&mut nonce_request.message.as_slice())
.expect("Failed to deserialize block proposal");
block_proposal.block
}

pub fn read_and_sign_block_proposal(
conf: &Config,
signers: &TestSigners,
Expand Down

0 comments on commit d14e5b4

Please sign in to comment.