Skip to content

Commit 53cf49d

Browse files
committed
Merge branch 'develop' into 4595-nakamoto-stacks-signer-should-store-its-dkg-shares-in-stackerdb-to-enable-disaster-recovery
2 parents 23a06a3 + 71abebc commit 53cf49d

File tree

4 files changed

+115
-51
lines changed

4 files changed

+115
-51
lines changed

stacks-signer/src/cli.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,11 @@ pub struct Cli {
4444
/// Subcommands for the stacks signer binary
4545
#[derive(clap::Subcommand, Debug)]
4646
pub enum Command {
47-
/// Get a chunk from the stacker-db instance
47+
/// Get a chunk from the stacker-db instance in hex encoding
4848
GetChunk(GetChunkArgs),
49-
/// Get the latest chunk from the stacker-db instance
49+
/// Get the latest chunk from the stacker-db instance in hex encoding
5050
GetLatestChunk(GetLatestChunkArgs),
51-
/// List chunks from the stacker-db instance
51+
/// List chunks from the stacker-db instance in hex encoding
5252
ListChunks(StackerDBArgs),
5353
/// Upload a chunk to the stacker-db instance
5454
PutChunk(PutChunkArgs),

stacks-signer/src/main.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,13 @@ fn stackerdb_session(host: &str, contract: QualifiedContractIdentifier) -> Stack
7171
/// Write the chunk to stdout
7272
fn write_chunk_to_stdout(chunk_opt: Option<Vec<u8>>) {
7373
if let Some(chunk) = chunk_opt.as_ref() {
74-
let bytes = io::stdout().write(chunk).unwrap();
75-
if bytes < chunk.len() {
74+
let hexed_string = to_hex(chunk);
75+
let hexed_chunk = hexed_string.as_bytes();
76+
let bytes = io::stdout().write(&hexed_chunk).unwrap();
77+
if bytes < hexed_chunk.len() {
7678
print!(
7779
"Failed to write complete chunk to stdout. Missing {} bytes",
78-
chunk.len() - bytes
80+
hexed_chunk.len() - bytes
7981
);
8082
}
8183
}
@@ -176,7 +178,9 @@ fn handle_list_chunks(args: StackerDBArgs) {
176178
debug!("Listing chunks...");
177179
let mut session = stackerdb_session(&args.host, args.contract);
178180
let chunk_list = session.list_chunks().unwrap();
179-
println!("{}", serde_json::to_string(&chunk_list).unwrap());
181+
let chunk_list_json = serde_json::to_string(&chunk_list).unwrap();
182+
let hexed_json = to_hex(chunk_list_json.as_bytes());
183+
println!("{}", hexed_json);
180184
}
181185

182186
fn handle_put_chunk(args: PutChunkArgs) {

stacks-signer/src/runloop.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -383,10 +383,9 @@ impl SignerRunLoop<Vec<OperationResult>, RunLoopCommand> for RunLoop {
383383
if event_parity == Some(other_signer_parity) {
384384
continue;
385385
}
386-
387386
if signer.approved_aggregate_public_key.is_none() {
388-
if let Err(e) = signer.update_dkg(&self.stacks_client) {
389-
error!("{signer}: failed to update DKG: {e}");
387+
if let Err(e) = signer.refresh_dkg(&self.stacks_client) {
388+
error!("{signer}: failed to refresh DKG: {e}");
390389
}
391390
}
392391
signer.refresh_coordinator();

stacks-signer/src/signer.rs

Lines changed: 102 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use std::time::Instant;
2121
use blockstack_lib::chainstate::burn::ConsensusHashExtensions;
2222
use blockstack_lib::chainstate::nakamoto::signer_set::NakamotoSigners;
2323
use blockstack_lib::chainstate::nakamoto::{NakamotoBlock, NakamotoBlockVote};
24+
use blockstack_lib::chainstate::stacks::boot::SIGNERS_VOTING_FUNCTION_NAME;
2425
use blockstack_lib::chainstate::stacks::StacksTransaction;
2526
use blockstack_lib::net::api::postblock_proposal::BlockValidateResponse;
2627
use blockstack_lib::util_lib::db::Error as DBError;
@@ -126,13 +127,22 @@ pub enum Command {
126127
},
127128
}
128129

130+
/// The specific operations that a signer can perform
131+
#[derive(PartialEq, Eq, Debug, Clone)]
132+
pub enum Operation {
133+
/// A DKG operation
134+
Dkg,
135+
/// A Sign operation
136+
Sign,
137+
}
138+
129139
/// The Signer state
130140
#[derive(PartialEq, Eq, Debug, Clone)]
131141
pub enum State {
132142
/// The signer is idle, waiting for messages and commands
133143
Idle,
134144
/// The signer is executing a DKG or Sign round
135-
OperationInProgress,
145+
OperationInProgress(Operation),
136146
}
137147

138148
/// The stacks signer registered for the reward cycle
@@ -349,8 +359,8 @@ impl Signer {
349359
}
350360

351361
/// Update operation
352-
fn update_operation(&mut self) {
353-
self.state = State::OperationInProgress;
362+
fn update_operation(&mut self, operation: Operation) {
363+
self.state = State::OperationInProgress(operation);
354364
self.coordinator_selector.last_message_time = Some(Instant::now());
355365
}
356366

@@ -380,6 +390,7 @@ impl Signer {
380390
Ok(msg) => {
381391
let ack = self.stackerdb.send_message_with_retry(msg.into());
382392
debug!("{self}: ACK: {ack:?}",);
393+
self.update_operation(Operation::Dkg);
383394
}
384395
Err(e) => {
385396
error!("{self}: Failed to start DKG: {e:?}",);
@@ -425,6 +436,7 @@ impl Signer {
425436
.unwrap_or_else(|e| {
426437
error!("{self}: Failed to insert block in DB: {e:?}");
427438
});
439+
self.update_operation(Operation::Sign);
428440
}
429441
Err(e) => {
430442
error!("{self}: Failed to start signing block: {e:?}",);
@@ -433,7 +445,6 @@ impl Signer {
433445
}
434446
}
435447
}
436-
self.update_operation();
437448
}
438449

439450
/// Attempt to process the next command in the queue, and update state accordingly
@@ -466,10 +477,10 @@ impl Signer {
466477
.expect("BUG: Already asserted that the command queue was not empty");
467478
self.execute_command(stacks_client, &command);
468479
}
469-
State::OperationInProgress => {
480+
State::OperationInProgress(op) => {
470481
// We cannot execute the next command until the current one is finished...
471482
debug!(
472-
"{self}: Waiting for operation to finish. Coordinator state = {:?}",
483+
"{self}: Waiting for {op:?} operation to finish. Coordinator state = {:?}",
473484
self.coordinator.state
474485
);
475486
}
@@ -703,9 +714,26 @@ impl Signer {
703714
self.process_operation_results(stacks_client, &operation_results);
704715
self.send_operation_results(res, operation_results);
705716
self.finish_operation();
706-
} else if !packets.is_empty() && self.coordinator.state != CoordinatorState::Idle {
707-
// We have received a message and are in the middle of an operation. Update our state accordingly
708-
self.update_operation();
717+
} else if !packets.is_empty() {
718+
// We have received a message. Update our state accordingly
719+
// Let us be extra explicit in case a new state type gets added to wsts' state machine
720+
match &self.coordinator.state {
721+
CoordinatorState::Idle => {}
722+
CoordinatorState::DkgPublicDistribute
723+
| CoordinatorState::DkgPublicGather
724+
| CoordinatorState::DkgPrivateDistribute
725+
| CoordinatorState::DkgPrivateGather
726+
| CoordinatorState::DkgEndDistribute
727+
| CoordinatorState::DkgEndGather => {
728+
self.update_operation(Operation::Dkg);
729+
}
730+
CoordinatorState::NonceRequest(_, _)
731+
| CoordinatorState::NonceGather(_, _)
732+
| CoordinatorState::SigShareRequest(_, _)
733+
| CoordinatorState::SigShareGather(_, _) => {
734+
self.update_operation(Operation::Sign);
735+
}
736+
}
709737
}
710738

711739
if packets.iter().any(|packet| match packet.msg {
@@ -1029,6 +1057,10 @@ impl Signer {
10291057
/// Process a dkg result by broadcasting a vote to the stacks node
10301058
fn process_dkg(&mut self, stacks_client: &StacksClient, dkg_public_key: &Point) {
10311059
let mut dkg_results_bytes = vec![];
1060+
debug!(
1061+
"{self}: Received DKG result. Broadcasting vote to the stacks node...";
1062+
"dkg_public_key" => %dkg_public_key
1063+
);
10321064
if let Err(e) = SignerMessage::serialize_dkg_result(
10331065
&mut dkg_results_bytes,
10341066
dkg_public_key,
@@ -1326,7 +1358,49 @@ impl Signer {
13261358
}
13271359
}
13281360

1361+
/// Refresh DKG value and queue DKG command if necessary
1362+
pub fn refresh_dkg(&mut self, stacks_client: &StacksClient) -> Result<(), ClientError> {
1363+
// First check if we should queue DKG based on contract vote state and stackerdb transactions
1364+
let should_queue = self.should_queue_dkg(stacks_client)?;
1365+
// Before queueing the command, check one last time if DKG has been
1366+
// approved. It could have happened after the last call to
1367+
// `get_approved_aggregate_key` but before the theshold check in
1368+
// `should_queue_dkg`.
1369+
let old_dkg = self.approved_aggregate_public_key;
1370+
self.approved_aggregate_public_key =
1371+
stacks_client.get_approved_aggregate_key(self.reward_cycle)?;
1372+
if self.approved_aggregate_public_key.is_some() {
1373+
// TODO: this will never work as is. We need to have stored our party shares on the side etc for this particular aggregate key.
1374+
// Need to update state to store the necessary info, check against it to see if we have participated in the winning round and
1375+
// then overwrite our value accordingly. Otherwise, we will be locked out of the round and should not participate.
1376+
self.coordinator
1377+
.set_aggregate_public_key(self.approved_aggregate_public_key);
1378+
if old_dkg != self.approved_aggregate_public_key {
1379+
warn!(
1380+
"{self}: updated DKG value to {:?}.",
1381+
self.approved_aggregate_public_key
1382+
);
1383+
}
1384+
if let State::OperationInProgress(Operation::Dkg) = self.state {
1385+
debug!(
1386+
"{self}: DKG has already been set. Aborting DKG operation {}.",
1387+
self.coordinator.current_dkg_id
1388+
);
1389+
self.finish_operation();
1390+
}
1391+
} else if should_queue {
1392+
if self.commands.front() != Some(&Command::Dkg) {
1393+
info!("{self} is the current coordinator and must trigger DKG. Queuing DKG command...");
1394+
self.commands.push_front(Command::Dkg);
1395+
} else {
1396+
debug!("{self}: DKG command already queued...");
1397+
}
1398+
}
1399+
Ok(())
1400+
}
1401+
13291402
/// Should DKG be queued to the current signer's command queue
1403+
/// This assumes that no key has been approved by the contract yet
13301404
pub fn should_queue_dkg(&mut self, stacks_client: &StacksClient) -> Result<bool, ClientError> {
13311405
if self.state != State::Idle
13321406
|| self.signer_id != self.get_coordinator_dkg().0
@@ -1336,6 +1410,25 @@ impl Signer {
13361410
return Ok(false);
13371411
}
13381412
let signer_address = stacks_client.get_signer_address();
1413+
let account_nonces = self.get_account_nonces(stacks_client, &[*signer_address]);
1414+
let old_transactions = self.get_signer_transactions(&account_nonces).map_err(|e| {
1415+
warn!("{self}: Failed to get old signer transactions: {e:?}. May trigger DKG unnecessarily");
1416+
}).unwrap_or_default();
1417+
// Check if we have an existing vote transaction for the same round and reward cycle
1418+
for transaction in old_transactions.iter() {
1419+
let params =
1420+
NakamotoSigners::parse_vote_for_aggregate_public_key(transaction).unwrap_or_else(|| panic!("BUG: {self}: Received an invalid {SIGNERS_VOTING_FUNCTION_NAME} transaction in an already filtered list: {transaction:?}"));
1421+
if Some(params.aggregate_key) == self.coordinator.aggregate_public_key
1422+
&& params.voting_round == self.coordinator.current_dkg_id
1423+
{
1424+
debug!("{self}: Not triggering a DKG round. Already have a pending vote transaction.";
1425+
"txid" => %transaction.txid(),
1426+
"aggregate_key" => %params.aggregate_key,
1427+
"voting_round" => params.voting_round
1428+
);
1429+
return Ok(false);
1430+
}
1431+
}
13391432
if let Some(aggregate_key) = stacks_client.get_vote_for_aggregate_public_key(
13401433
self.coordinator.current_dkg_id,
13411434
self.reward_cycle,
@@ -1364,12 +1457,6 @@ impl Signer {
13641457
);
13651458
return Ok(false);
13661459
}
1367-
warn!("{self}: Vote for DKG failed.";
1368-
"voting_round" => self.coordinator.current_dkg_id,
1369-
"aggregate_key" => %aggregate_key,
1370-
"round_weight" => round_weight,
1371-
"threshold_weight" => threshold_weight
1372-
);
13731460
} else {
13741461
// Have I already voted, but the vote is still pending in StackerDB? Check stackerdb for the same round number and reward cycle vote transaction
13751462
// Only get the account nonce of THIS signer as we only care about our own votes, not other signer votes
@@ -1416,32 +1503,6 @@ impl Signer {
14161503
Ok(true)
14171504
}
14181505

1419-
/// Update the DKG for the provided signer info, triggering it if required
1420-
pub fn update_dkg(&mut self, stacks_client: &StacksClient) -> Result<(), ClientError> {
1421-
let old_dkg = self.approved_aggregate_public_key;
1422-
self.approved_aggregate_public_key =
1423-
stacks_client.get_approved_aggregate_key(self.reward_cycle)?;
1424-
if self.approved_aggregate_public_key.is_some() {
1425-
// TODO: this will never work as is. We need to have stored our party shares on the side etc for this particular aggregate key.
1426-
// Need to update state to store the necessary info, check against it to see if we have participated in the winning round and
1427-
// then overwrite our value accordingly. Otherwise, we will be locked out of the round and should not participate.
1428-
self.coordinator
1429-
.set_aggregate_public_key(self.approved_aggregate_public_key);
1430-
if old_dkg != self.approved_aggregate_public_key {
1431-
warn!(
1432-
"{self}: updated DKG value to {:?}.",
1433-
self.approved_aggregate_public_key
1434-
);
1435-
}
1436-
return Ok(());
1437-
};
1438-
if self.should_queue_dkg(stacks_client)? {
1439-
info!("{self} is the current coordinator and must trigger DKG. Queuing DKG command...");
1440-
self.commands.push_front(Command::Dkg);
1441-
}
1442-
Ok(())
1443-
}
1444-
14451506
/// Process the event
14461507
pub fn process_event(
14471508
&mut self,

0 commit comments

Comments
 (0)