@@ -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 blockstack_lib:: util_lib:: db:: Error as DBError ;
@@ -126,13 +127,22 @@ pub enum Command {
126
127
} ,
127
128
}
128
129
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
+
129
139
/// The Signer state
130
140
#[ derive( PartialEq , Eq , Debug , Clone ) ]
131
141
pub enum State {
132
142
/// The signer is idle, waiting for messages and commands
133
143
Idle ,
134
144
/// The signer is executing a DKG or Sign round
135
- OperationInProgress ,
145
+ OperationInProgress ( Operation ) ,
136
146
}
137
147
138
148
/// The stacks signer registered for the reward cycle
@@ -349,8 +359,8 @@ impl Signer {
349
359
}
350
360
351
361
/// 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 ) ;
354
364
self . coordinator_selector . last_message_time = Some ( Instant :: now ( ) ) ;
355
365
}
356
366
@@ -380,6 +390,7 @@ impl Signer {
380
390
Ok ( msg) => {
381
391
let ack = self . stackerdb . send_message_with_retry ( msg. into ( ) ) ;
382
392
debug ! ( "{self}: ACK: {ack:?}" , ) ;
393
+ self . update_operation ( Operation :: Dkg ) ;
383
394
}
384
395
Err ( e) => {
385
396
error ! ( "{self}: Failed to start DKG: {e:?}" , ) ;
@@ -425,6 +436,7 @@ impl Signer {
425
436
. unwrap_or_else ( |e| {
426
437
error ! ( "{self}: Failed to insert block in DB: {e:?}" ) ;
427
438
} ) ;
439
+ self . update_operation ( Operation :: Sign ) ;
428
440
}
429
441
Err ( e) => {
430
442
error ! ( "{self}: Failed to start signing block: {e:?}" , ) ;
@@ -433,7 +445,6 @@ impl Signer {
433
445
}
434
446
}
435
447
}
436
- self . update_operation ( ) ;
437
448
}
438
449
439
450
/// Attempt to process the next command in the queue, and update state accordingly
@@ -466,10 +477,10 @@ impl Signer {
466
477
. expect ( "BUG: Already asserted that the command queue was not empty" ) ;
467
478
self . execute_command ( stacks_client, & command) ;
468
479
}
469
- State :: OperationInProgress => {
480
+ State :: OperationInProgress ( op ) => {
470
481
// We cannot execute the next command until the current one is finished...
471
482
debug ! (
472
- "{self}: Waiting for operation to finish. Coordinator state = {:?}" ,
483
+ "{self}: Waiting for {op:?} operation to finish. Coordinator state = {:?}" ,
473
484
self . coordinator. state
474
485
) ;
475
486
}
@@ -703,9 +714,26 @@ impl Signer {
703
714
self . process_operation_results ( stacks_client, & operation_results) ;
704
715
self . send_operation_results ( res, operation_results) ;
705
716
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
+ }
709
737
}
710
738
711
739
if packets. iter ( ) . any ( |packet| match packet. msg {
@@ -1029,6 +1057,10 @@ impl Signer {
1029
1057
/// Process a dkg result by broadcasting a vote to the stacks node
1030
1058
fn process_dkg ( & mut self , stacks_client : & StacksClient , dkg_public_key : & Point ) {
1031
1059
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
+ ) ;
1032
1064
if let Err ( e) = SignerMessage :: serialize_dkg_result (
1033
1065
& mut dkg_results_bytes,
1034
1066
dkg_public_key,
@@ -1326,7 +1358,49 @@ impl Signer {
1326
1358
}
1327
1359
}
1328
1360
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
+
1329
1402
/// Should DKG be queued to the current signer's command queue
1403
+ /// This assumes that no key has been approved by the contract yet
1330
1404
pub fn should_queue_dkg ( & mut self , stacks_client : & StacksClient ) -> Result < bool , ClientError > {
1331
1405
if self . state != State :: Idle
1332
1406
|| self . signer_id != self . get_coordinator_dkg ( ) . 0
@@ -1336,6 +1410,25 @@ impl Signer {
1336
1410
return Ok ( false ) ;
1337
1411
}
1338
1412
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
+ }
1339
1432
if let Some ( aggregate_key) = stacks_client. get_vote_for_aggregate_public_key (
1340
1433
self . coordinator . current_dkg_id ,
1341
1434
self . reward_cycle ,
@@ -1364,12 +1457,6 @@ impl Signer {
1364
1457
) ;
1365
1458
return Ok ( false ) ;
1366
1459
}
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
- ) ;
1373
1460
} else {
1374
1461
// Have I already voted, but the vote is still pending in StackerDB? Check stackerdb for the same round number and reward cycle vote transaction
1375
1462
// 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 {
1416
1503
Ok ( true )
1417
1504
}
1418
1505
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
-
1445
1506
/// Process the event
1446
1507
pub fn process_event (
1447
1508
& mut self ,
0 commit comments