@@ -145,6 +145,8 @@ pub enum Operation {
145
145
/// The Signer state
146
146
#[ derive( PartialEq , Eq , Debug , Clone ) ]
147
147
pub enum State {
148
+ /// The signer is uninitialized and should read stackerdb to restore state
149
+ Uninitialized ,
148
150
/// The signer is idle, waiting for messages and commands
149
151
Idle ,
150
152
/// The signer is executing a DKG or Sign round
@@ -234,6 +236,43 @@ impl Signer {
234
236
fn get_coordinator_dkg ( & self ) -> ( u32 , PublicKey ) {
235
237
self . coordinator_selector . get_coordinator ( )
236
238
}
239
+
240
+ /// Read stackerdb messages in case the signer was started late or restarted and missed incoming DKG messages
241
+ pub fn read_dkg_stackerdb_messages (
242
+ & mut self ,
243
+ stacks_client : & StacksClient ,
244
+ res : Sender < Vec < OperationResult > > ,
245
+ current_reward_cycle : u64 ,
246
+ ) -> Result < ( ) , ClientError > {
247
+ if self . state != State :: Uninitialized {
248
+ // We should only read stackerdb if we are uninitialized
249
+ return Ok ( ( ) ) ;
250
+ }
251
+ let ordered_packets = self
252
+ . stackerdb
253
+ . get_dkg_packets ( & self . signer_slot_ids ) ?
254
+ . iter ( )
255
+ . filter_map ( |packet| {
256
+ let coordinator_pubkey = if Self :: is_dkg_message ( & packet. msg ) {
257
+ self . get_coordinator_dkg ( ) . 1
258
+ } else {
259
+ debug ! (
260
+ "{self}: Received a non-DKG message in the DKG message queue. Ignoring it."
261
+ ) ;
262
+ return None ;
263
+ } ;
264
+ self . verify_packet ( stacks_client, packet. clone ( ) , & coordinator_pubkey)
265
+ } )
266
+ . collect :: < Vec < _ > > ( ) ;
267
+ // We successfully read stackerdb so we are no longer uninitialized
268
+ self . state = State :: Idle ;
269
+ debug ! (
270
+ "{self}: Processing {} DKG messages from stackerdb: {ordered_packets:?}" ,
271
+ ordered_packets. len( )
272
+ ) ;
273
+ self . handle_packets ( stacks_client, res, & ordered_packets, current_reward_cycle) ;
274
+ Ok ( ( ) )
275
+ }
237
276
}
238
277
239
278
impl From < SignerConfig > for Signer {
@@ -297,7 +336,7 @@ impl From<SignerConfig> for Signer {
297
336
298
337
if let Some ( state) = load_encrypted_signer_state (
299
338
& mut stackerdb,
300
- signer_config. signer_slot_id . into ( ) ,
339
+ signer_config. signer_slot_id ,
301
340
& state_machine. network_private_key ,
302
341
) . or_else ( |err| {
303
342
warn ! ( "Failed to load encrypted signer state from StackerDB, falling back to SignerDB: {err}" ) ;
@@ -312,7 +351,7 @@ impl From<SignerConfig> for Signer {
312
351
Self {
313
352
coordinator,
314
353
state_machine,
315
- state : State :: Idle ,
354
+ state : State :: Uninitialized ,
316
355
commands : VecDeque :: new ( ) ,
317
356
stackerdb,
318
357
mainnet : signer_config. mainnet ,
@@ -403,6 +442,7 @@ impl Signer {
403
442
return ;
404
443
}
405
444
}
445
+ self . update_operation ( Operation :: Dkg ) ;
406
446
}
407
447
Command :: Sign {
408
448
block_proposal,
@@ -449,6 +489,7 @@ impl Signer {
449
489
return ;
450
490
}
451
491
}
492
+ self . update_operation ( Operation :: Sign ) ;
452
493
}
453
494
}
454
495
}
@@ -460,6 +501,10 @@ impl Signer {
460
501
current_reward_cycle : u64 ,
461
502
) {
462
503
match & self . state {
504
+ State :: Uninitialized => {
505
+ // We cannot process any commands until we have restored our state
506
+ warn ! ( "{self}: Cannot process commands until state is restored. Waiting..." ) ;
507
+ }
463
508
State :: Idle => {
464
509
let Some ( command) = self . commands . front ( ) else {
465
510
debug ! ( "{self}: Nothing to process. Waiting for command..." ) ;
@@ -685,13 +730,13 @@ impl Signer {
685
730
}
686
731
}
687
732
688
- if packets. iter ( ) . any ( |packet| match packet . msg {
689
- Message :: DkgEnd ( _ ) => true ,
690
- _ => false ,
691
- } ) {
733
+ if packets
734
+ . iter ( )
735
+ . any ( |packet| matches ! ( packet . msg , Message :: DkgEnd ( _ ) ) )
736
+ {
692
737
debug ! ( "{self}: Saving signer state" ) ;
693
738
self . save_signer_state ( )
694
- . expect ( & format ! ( "{self}: Failed to save signer state" ) ) ;
739
+ . unwrap_or_else ( |_| panic ! ( "{self}: Failed to save signer state" ) ) ;
695
740
}
696
741
self . send_outbound_messages ( signer_outbound_messages) ;
697
742
self . send_outbound_messages ( coordinator_outbound_messages) ;
@@ -1316,42 +1361,78 @@ impl Signer {
1316
1361
}
1317
1362
}
1318
1363
1319
- /// Refresh DKG value and queue DKG command if necessary
1320
- pub fn refresh_dkg ( & mut self , stacks_client : & StacksClient ) -> Result < ( ) , ClientError > {
1321
- // First check if we should queue DKG based on contract vote state and stackerdb transactions
1322
- let should_queue = self . should_queue_dkg ( stacks_client) ?;
1323
- // Before queueing the command, check one last time if DKG has been
1324
- // approved. It could have happened after the last call to
1325
- // `get_approved_aggregate_key` but before the theshold check in
1326
- // `should_queue_dkg`.
1364
+ /// Refresh DKG and queue it if required
1365
+ pub fn refresh_dkg (
1366
+ & mut self ,
1367
+ stacks_client : & StacksClient ,
1368
+ res : Sender < Vec < OperationResult > > ,
1369
+ current_reward_cycle : u64 ,
1370
+ ) -> Result < ( ) , ClientError > {
1371
+ // First attempt to retrieve the aggregate key from the contract.
1372
+ self . update_approved_aggregate_key ( stacks_client) ?;
1373
+ if self . approved_aggregate_public_key . is_some ( ) {
1374
+ return Ok ( ( ) ) ;
1375
+ }
1376
+ // Check stackerdb for any missed DKG messages to catch up our state.
1377
+ self . read_dkg_stackerdb_messages ( & stacks_client, res, current_reward_cycle) ?;
1378
+ // Check if we should still queue DKG
1379
+ if !self . should_queue_dkg ( stacks_client) ? {
1380
+ return Ok ( ( ) ) ;
1381
+ }
1382
+ // Because there could be a slight delay in reading pending transactions and a key being approved by the contract,
1383
+ // check one last time if the approved key was set since we finished the should queue dkg call
1384
+ self . update_approved_aggregate_key ( stacks_client) ?;
1385
+ if self . approved_aggregate_public_key . is_some ( ) {
1386
+ return Ok ( ( ) ) ;
1387
+ }
1388
+ if self . commands . front ( ) != Some ( & Command :: Dkg ) {
1389
+ info ! ( "{self} is the current coordinator and must trigger DKG. Queuing DKG command..." ) ;
1390
+ self . commands . push_front ( Command :: Dkg ) ;
1391
+ } else {
1392
+ debug ! ( "{self}: DKG command already queued..." ) ;
1393
+ }
1394
+ Ok ( ( ) )
1395
+ }
1396
+
1397
+ /// Overwrites the approved aggregate key to the value in the contract, updating state accordingly
1398
+ pub fn update_approved_aggregate_key (
1399
+ & mut self ,
1400
+ stacks_client : & StacksClient ,
1401
+ ) -> Result < ( ) , ClientError > {
1327
1402
let old_dkg = self . approved_aggregate_public_key ;
1328
1403
self . approved_aggregate_public_key =
1329
1404
stacks_client. get_approved_aggregate_key ( self . reward_cycle ) ?;
1330
1405
if self . approved_aggregate_public_key . is_some ( ) {
1331
1406
// TODO: this will never work as is. We need to have stored our party shares on the side etc for this particular aggregate key.
1332
1407
// Need to update state to store the necessary info, check against it to see if we have participated in the winning round and
1333
1408
// then overwrite our value accordingly. Otherwise, we will be locked out of the round and should not participate.
1409
+ let internal_dkg = self . coordinator . aggregate_public_key ;
1410
+ if internal_dkg != self . approved_aggregate_public_key {
1411
+ warn ! ( "{self}: we do not support changing the internal DKG key yet. Expected {internal_dkg:?} got {:?}" , self . approved_aggregate_public_key) ;
1412
+ }
1334
1413
self . coordinator
1335
1414
. set_aggregate_public_key ( self . approved_aggregate_public_key ) ;
1336
1415
if old_dkg != self . approved_aggregate_public_key {
1337
1416
warn ! (
1338
- "{self}: updated DKG value to {:?}." ,
1417
+ "{self}: updated DKG value from {old_dkg:?} to {:?}." ,
1339
1418
self . approved_aggregate_public_key
1340
1419
) ;
1341
1420
}
1342
- if let State :: OperationInProgress ( Operation :: Dkg ) = self . state {
1343
- debug ! (
1344
- "{self}: DKG has already been set. Aborting DKG operation {}." ,
1345
- self . coordinator. current_dkg_id
1346
- ) ;
1347
- self . finish_operation ( ) ;
1348
- }
1349
- } else if should_queue {
1350
- if self . commands . front ( ) != Some ( & Command :: Dkg ) {
1351
- info ! ( "{self} is the current coordinator and must trigger DKG. Queuing DKG command..." ) ;
1352
- self . commands . push_front ( Command :: Dkg ) ;
1353
- } else {
1354
- debug ! ( "{self}: DKG command already queued..." ) ;
1421
+ match self . state {
1422
+ State :: OperationInProgress ( Operation :: Dkg ) => {
1423
+ debug ! (
1424
+ "{self}: DKG has already been set. Aborting DKG operation {}." ,
1425
+ self . coordinator. current_dkg_id
1426
+ ) ;
1427
+ self . finish_operation ( ) ;
1428
+ }
1429
+ State :: Uninitialized => {
1430
+ // If we successfully load the DKG value, we are fully initialized
1431
+ self . state = State :: Idle ;
1432
+ }
1433
+ _ => {
1434
+ // do nothing
1435
+ }
1355
1436
}
1356
1437
}
1357
1438
Ok ( ( ) )
@@ -1433,7 +1514,7 @@ impl Signer {
1433
1514
else {
1434
1515
continue ;
1435
1516
} ;
1436
- let Some ( dkg_public_key) = self . coordinator . aggregate_public_key . clone ( ) else {
1517
+ let Some ( dkg_public_key) = self . coordinator . aggregate_public_key else {
1437
1518
break ;
1438
1519
} ;
1439
1520
if params. aggregate_key == dkg_public_key
0 commit comments