@@ -21,6 +21,7 @@ use std::time::Instant;
21
21
use blockstack_lib:: chainstate:: burn:: ConsensusHashExtensions ;
22
22
use blockstack_lib:: chainstate:: nakamoto:: signer_set:: NakamotoSigners ;
23
23
use blockstack_lib:: chainstate:: nakamoto:: { NakamotoBlock , NakamotoBlockVote } ;
24
+ use blockstack_lib:: chainstate:: stacks:: boot:: SIGNERS_VOTING_FUNCTION_NAME ;
24
25
use blockstack_lib:: chainstate:: stacks:: StacksTransaction ;
25
26
use blockstack_lib:: net:: api:: postblock_proposal:: BlockValidateResponse ;
26
27
use hashbrown:: HashSet ;
@@ -123,13 +124,22 @@ pub enum Command {
123
124
} ,
124
125
}
125
126
127
+ /// The specific operations that a signer can perform
128
+ #[ derive( PartialEq , Eq , Debug , Clone ) ]
129
+ pub enum Operation {
130
+ /// A DKG operation
131
+ Dkg ,
132
+ /// A Sign operation
133
+ Sign ,
134
+ }
135
+
126
136
/// The Signer state
127
137
#[ derive( PartialEq , Eq , Debug , Clone ) ]
128
138
pub enum State {
129
139
/// The signer is idle, waiting for messages and commands
130
140
Idle ,
131
141
/// The signer is executing a DKG or Sign round
132
- OperationInProgress ,
142
+ OperationInProgress ( Operation ) ,
133
143
}
134
144
135
145
/// The stacks signer registered for the reward cycle
@@ -343,8 +353,8 @@ impl Signer {
343
353
}
344
354
345
355
/// Update operation
346
- fn update_operation ( & mut self ) {
347
- self . state = State :: OperationInProgress ;
356
+ fn update_operation ( & mut self , operation : Operation ) {
357
+ self . state = State :: OperationInProgress ( operation ) ;
348
358
self . coordinator_selector . last_message_time = Some ( Instant :: now ( ) ) ;
349
359
}
350
360
@@ -374,6 +384,7 @@ impl Signer {
374
384
Ok ( msg) => {
375
385
let ack = self . stackerdb . send_message_with_retry ( msg. into ( ) ) ;
376
386
debug ! ( "{self}: ACK: {ack:?}" , ) ;
387
+ self . update_operation ( Operation :: Dkg ) ;
377
388
}
378
389
Err ( e) => {
379
390
error ! ( "{self}: Failed to start DKG: {e:?}" , ) ;
@@ -419,6 +430,7 @@ impl Signer {
419
430
. unwrap_or_else ( |e| {
420
431
error ! ( "{self}: Failed to insert block in DB: {e:?}" ) ;
421
432
} ) ;
433
+ self . update_operation ( Operation :: Sign ) ;
422
434
}
423
435
Err ( e) => {
424
436
error ! ( "{self}: Failed to start signing block: {e:?}" , ) ;
@@ -427,7 +439,6 @@ impl Signer {
427
439
}
428
440
}
429
441
}
430
- self . update_operation ( ) ;
431
442
}
432
443
433
444
/// Attempt to process the next command in the queue, and update state accordingly
@@ -460,10 +471,10 @@ impl Signer {
460
471
. expect ( "BUG: Already asserted that the command queue was not empty" ) ;
461
472
self . execute_command ( stacks_client, & command) ;
462
473
}
463
- State :: OperationInProgress => {
474
+ State :: OperationInProgress ( op ) => {
464
475
// We cannot execute the next command until the current one is finished...
465
476
debug ! (
466
- "{self}: Waiting for operation to finish. Coordinator state = {:?}" ,
477
+ "{self}: Waiting for {op:?} operation to finish. Coordinator state = {:?}" ,
467
478
self . coordinator. state
468
479
) ;
469
480
}
@@ -696,9 +707,26 @@ impl Signer {
696
707
self . process_operation_results ( stacks_client, & operation_results) ;
697
708
self . send_operation_results ( res, operation_results) ;
698
709
self . finish_operation ( ) ;
699
- } else if !packets. is_empty ( ) && self . coordinator . state != CoordinatorState :: Idle {
700
- // We have received a message and are in the middle of an operation. Update our state accordingly
701
- self . update_operation ( ) ;
710
+ } else if !packets. is_empty ( ) {
711
+ // We have received a message. Update our state accordingly
712
+ // Let us be extra explicit in case a new state type gets added to wsts' state machine
713
+ match & self . coordinator . state {
714
+ CoordinatorState :: Idle => { }
715
+ CoordinatorState :: DkgPublicDistribute
716
+ | CoordinatorState :: DkgPublicGather
717
+ | CoordinatorState :: DkgPrivateDistribute
718
+ | CoordinatorState :: DkgPrivateGather
719
+ | CoordinatorState :: DkgEndDistribute
720
+ | CoordinatorState :: DkgEndGather => {
721
+ self . update_operation ( Operation :: Dkg ) ;
722
+ }
723
+ CoordinatorState :: NonceRequest ( _, _)
724
+ | CoordinatorState :: NonceGather ( _, _)
725
+ | CoordinatorState :: SigShareRequest ( _, _)
726
+ | CoordinatorState :: SigShareGather ( _, _) => {
727
+ self . update_operation ( Operation :: Sign ) ;
728
+ }
729
+ }
702
730
}
703
731
704
732
debug ! ( "{self}: Saving signer state" ) ;
@@ -1016,6 +1044,10 @@ impl Signer {
1016
1044
/// Process a dkg result by broadcasting a vote to the stacks node
1017
1045
fn process_dkg ( & mut self , stacks_client : & StacksClient , dkg_public_key : & Point ) {
1018
1046
let mut dkg_results_bytes = vec ! [ ] ;
1047
+ debug ! (
1048
+ "{self}: Received DKG result. Broadcasting vote to the stacks node..." ;
1049
+ "dkg_public_key" => %dkg_public_key
1050
+ ) ;
1019
1051
if let Err ( e) = SignerMessage :: serialize_dkg_result (
1020
1052
& mut dkg_results_bytes,
1021
1053
dkg_public_key,
@@ -1273,7 +1305,49 @@ impl Signer {
1273
1305
}
1274
1306
}
1275
1307
1308
+ /// Refresh DKG value and queue DKG command if necessary
1309
+ pub fn refresh_dkg ( & mut self , stacks_client : & StacksClient ) -> Result < ( ) , ClientError > {
1310
+ // First check if we should queue DKG based on contract vote state and stackerdb transactions
1311
+ let should_queue = self . should_queue_dkg ( stacks_client) ?;
1312
+ // Before queueing the command, check one last time if DKG has been
1313
+ // approved. It could have happened after the last call to
1314
+ // `get_approved_aggregate_key` but before the theshold check in
1315
+ // `should_queue_dkg`.
1316
+ let old_dkg = self . approved_aggregate_public_key ;
1317
+ self . approved_aggregate_public_key =
1318
+ stacks_client. get_approved_aggregate_key ( self . reward_cycle ) ?;
1319
+ if self . approved_aggregate_public_key . is_some ( ) {
1320
+ // TODO: this will never work as is. We need to have stored our party shares on the side etc for this particular aggregate key.
1321
+ // Need to update state to store the necessary info, check against it to see if we have participated in the winning round and
1322
+ // then overwrite our value accordingly. Otherwise, we will be locked out of the round and should not participate.
1323
+ self . coordinator
1324
+ . set_aggregate_public_key ( self . approved_aggregate_public_key ) ;
1325
+ if old_dkg != self . approved_aggregate_public_key {
1326
+ warn ! (
1327
+ "{self}: updated DKG value to {:?}." ,
1328
+ self . approved_aggregate_public_key
1329
+ ) ;
1330
+ }
1331
+ if let State :: OperationInProgress ( Operation :: Dkg ) = self . state {
1332
+ debug ! (
1333
+ "{self}: DKG has already been set. Aborting DKG operation {}." ,
1334
+ self . coordinator. current_dkg_id
1335
+ ) ;
1336
+ self . finish_operation ( ) ;
1337
+ }
1338
+ } else if should_queue {
1339
+ if self . commands . front ( ) != Some ( & Command :: Dkg ) {
1340
+ info ! ( "{self} is the current coordinator and must trigger DKG. Queuing DKG command..." ) ;
1341
+ self . commands . push_front ( Command :: Dkg ) ;
1342
+ } else {
1343
+ debug ! ( "{self}: DKG command already queued..." ) ;
1344
+ }
1345
+ }
1346
+ Ok ( ( ) )
1347
+ }
1348
+
1276
1349
/// Should DKG be queued to the current signer's command queue
1350
+ /// This assumes that no key has been approved by the contract yet
1277
1351
pub fn should_queue_dkg ( & mut self , stacks_client : & StacksClient ) -> Result < bool , ClientError > {
1278
1352
if self . state != State :: Idle
1279
1353
|| self . signer_id != self . get_coordinator_dkg ( ) . 0
@@ -1283,6 +1357,25 @@ impl Signer {
1283
1357
return Ok ( false ) ;
1284
1358
}
1285
1359
let signer_address = stacks_client. get_signer_address ( ) ;
1360
+ let account_nonces = self . get_account_nonces ( stacks_client, & [ * signer_address] ) ;
1361
+ let old_transactions = self . get_signer_transactions ( & account_nonces) . map_err ( |e| {
1362
+ warn ! ( "{self}: Failed to get old signer transactions: {e:?}. May trigger DKG unnecessarily" ) ;
1363
+ } ) . unwrap_or_default ( ) ;
1364
+ // Check if we have an existing vote transaction for the same round and reward cycle
1365
+ for transaction in old_transactions. iter ( ) {
1366
+ let params =
1367
+ 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:?}" ) ) ;
1368
+ if Some ( params. aggregate_key ) == self . coordinator . aggregate_public_key
1369
+ && params. voting_round == self . coordinator . current_dkg_id
1370
+ {
1371
+ debug ! ( "{self}: Not triggering a DKG round. Already have a pending vote transaction." ;
1372
+ "txid" => %transaction. txid( ) ,
1373
+ "aggregate_key" => %params. aggregate_key,
1374
+ "voting_round" => params. voting_round
1375
+ ) ;
1376
+ return Ok ( false ) ;
1377
+ }
1378
+ }
1286
1379
if let Some ( aggregate_key) = stacks_client. get_vote_for_aggregate_public_key (
1287
1380
self . coordinator . current_dkg_id ,
1288
1381
self . reward_cycle ,
@@ -1311,12 +1404,6 @@ impl Signer {
1311
1404
) ;
1312
1405
return Ok ( false ) ;
1313
1406
}
1314
- warn ! ( "{self}: Vote for DKG failed." ;
1315
- "voting_round" => self . coordinator. current_dkg_id,
1316
- "aggregate_key" => %aggregate_key,
1317
- "round_weight" => round_weight,
1318
- "threshold_weight" => threshold_weight
1319
- ) ;
1320
1407
} else {
1321
1408
// Have I already voted, but the vote is still pending in StackerDB? Check stackerdb for the same round number and reward cycle vote transaction
1322
1409
// Only get the account nonce of THIS signer as we only care about our own votes, not other signer votes
@@ -1363,32 +1450,6 @@ impl Signer {
1363
1450
Ok ( true )
1364
1451
}
1365
1452
1366
- /// Update the DKG for the provided signer info, triggering it if required
1367
- pub fn update_dkg ( & mut self , stacks_client : & StacksClient ) -> Result < ( ) , ClientError > {
1368
- let old_dkg = self . approved_aggregate_public_key ;
1369
- self . approved_aggregate_public_key =
1370
- stacks_client. get_approved_aggregate_key ( self . reward_cycle ) ?;
1371
- if self . approved_aggregate_public_key . is_some ( ) {
1372
- // TODO: this will never work as is. We need to have stored our party shares on the side etc for this particular aggregate key.
1373
- // Need to update state to store the necessary info, check against it to see if we have participated in the winning round and
1374
- // then overwrite our value accordingly. Otherwise, we will be locked out of the round and should not participate.
1375
- self . coordinator
1376
- . set_aggregate_public_key ( self . approved_aggregate_public_key ) ;
1377
- if old_dkg != self . approved_aggregate_public_key {
1378
- warn ! (
1379
- "{self}: updated DKG value to {:?}." ,
1380
- self . approved_aggregate_public_key
1381
- ) ;
1382
- }
1383
- return Ok ( ( ) ) ;
1384
- } ;
1385
- if self . should_queue_dkg ( stacks_client) ? {
1386
- info ! ( "{self} is the current coordinator and must trigger DKG. Queuing DKG command..." ) ;
1387
- self . commands . push_front ( Command :: Dkg ) ;
1388
- }
1389
- Ok ( ( ) )
1390
- }
1391
-
1392
1453
/// Process the event
1393
1454
pub fn process_event (
1394
1455
& mut self ,
0 commit comments