@@ -22,14 +22,19 @@ use std::sync::{Arc, Condvar, Mutex};
22
22
use std:: time:: Duration ;
23
23
24
24
use hashbrown:: { HashMap , HashSet } ;
25
- use libsigner:: v0:: messages:: { BlockAccepted , BlockResponse , SignerMessage as SignerMessageV0 } ;
26
- use libsigner:: SignerEvent ;
25
+ use libsigner:: v0:: messages:: {
26
+ BlockAccepted , BlockResponse , MessageSlotID , SignerMessage as SignerMessageV0 ,
27
+ StateMachineUpdate , StateMachineUpdateContent ,
28
+ } ;
29
+ use libsigner:: { SignerEvent , SignerSession , StackerDBSession } ;
27
30
use stacks:: burnchains:: Burnchain ;
28
31
use stacks:: chainstate:: burn:: BlockSnapshot ;
29
32
use stacks:: chainstate:: nakamoto:: NakamotoBlockHeader ;
30
33
use stacks:: chainstate:: stacks:: boot:: { NakamotoSignerEntry , RewardSet , SIGNERS_NAME } ;
31
34
use stacks:: chainstate:: stacks:: events:: StackerDBChunksEvent ;
32
- use stacks:: chainstate:: stacks:: Error as ChainstateError ;
35
+ use stacks:: chainstate:: stacks:: { Error as ChainstateError , StacksTransaction } ;
36
+ use stacks:: codec:: StacksMessageCodec ;
37
+ use stacks:: net:: stackerdb:: StackerDBs ;
33
38
use stacks:: types:: chainstate:: StacksPublicKey ;
34
39
use stacks:: types:: PublicKey ;
35
40
use stacks:: util:: get_epoch_time_secs;
@@ -40,6 +45,7 @@ use stacks_common::util::tests::TestFlag;
40
45
41
46
use super :: Error as NakamotoNodeError ;
42
47
use crate :: event_dispatcher:: StackerDBChannel ;
48
+ use crate :: Config ;
43
49
44
50
#[ cfg( test) ]
45
51
/// Fault injection flag to prevent the miner from seeing enough signer signatures.
@@ -68,6 +74,12 @@ pub(crate) struct TimestampInfo {
68
74
pub weight : u32 ,
69
75
}
70
76
77
+ #[ derive( Debug , Clone ) ]
78
+ pub ( crate ) struct ReplayInfo {
79
+ pub transactions : Vec < StacksTransaction > ,
80
+ pub weight : u32 ,
81
+ }
82
+
71
83
/// The listener for the StackerDB, which listens for messages from the
72
84
/// signers and tracks the state of block signatures and idle timestamps.
73
85
pub struct StackerDBListener {
@@ -96,6 +108,11 @@ pub struct StackerDBListener {
96
108
/// - key: StacksPublicKey
97
109
/// - value: TimestampInfo
98
110
pub ( crate ) signer_idle_timestamps : Arc < Mutex < HashMap < StacksPublicKey , TimestampInfo > > > ,
111
+ /// Tracks any replay transactions from signers to decide when the miner should
112
+ /// attempt to replay reorged blocks
113
+ /// - key: StacksPublicKey
114
+ /// - value: Vec<StacksTransaction>
115
+ pub ( crate ) replay_info : Arc < Mutex < HashMap < StacksPublicKey , ReplayInfo > > > ,
99
116
}
100
117
101
118
/// Interface for other threads to retrieve info from the StackerDBListener
@@ -109,6 +126,11 @@ pub struct StackerDBListenerComms {
109
126
/// - key: StacksPublicKey
110
127
/// - value: TimestampInfo
111
128
signer_idle_timestamps : Arc < Mutex < HashMap < StacksPublicKey , TimestampInfo > > > ,
129
+ /// Tracks any replay transactions from signers to decide when the miner should
130
+ /// attempt to replay reorged blocks
131
+ /// - key: StacksPublicKey
132
+ /// - value: ReplayInfo
133
+ replay_info : Arc < Mutex < HashMap < StacksPublicKey , ReplayInfo > > > ,
112
134
}
113
135
114
136
impl StackerDBListener {
@@ -119,6 +141,7 @@ impl StackerDBListener {
119
141
reward_set : & RewardSet ,
120
142
burn_tip : & BlockSnapshot ,
121
143
burnchain : & Burnchain ,
144
+ config : & Config ,
122
145
) -> Result < Self , ChainstateError > {
123
146
let ( receiver, replaced_other) = stackerdb_channel
124
147
. lock ( )
@@ -161,6 +184,60 @@ impl StackerDBListener {
161
184
} )
162
185
. collect :: < Result < HashMap < _ , _ > , ChainstateError > > ( ) ?;
163
186
187
+ let reward_cycle = burnchain
188
+ . block_height_to_reward_cycle ( burn_tip. block_height )
189
+ . expect ( "BUG: unknown reward cycle" ) ;
190
+ let signers_contract_id = MessageSlotID :: StateMachineUpdate
191
+ . stacker_db_contract ( config. is_mainnet ( ) , reward_cycle) ;
192
+ let rpc_socket = config
193
+ . node
194
+ . get_rpc_loopback ( )
195
+ . ok_or_else ( || ChainstateError :: MinerAborted ) ?;
196
+ let mut signers_session =
197
+ StackerDBSession :: new ( & rpc_socket. to_string ( ) , signers_contract_id. clone ( ) ) ;
198
+ let stackerdbs = StackerDBs :: connect ( & config. get_stacker_db_file_path ( ) , false ) ?;
199
+ let slot_ids: Vec < _ > = stackerdbs
200
+ . get_signers ( & signers_contract_id)
201
+ . expect ( "FATAL: could not get signers from stacker DB" )
202
+ . into_iter ( )
203
+ . enumerate ( )
204
+ . map ( |( slot_id, _) | {
205
+ u32:: try_from ( slot_id) . expect ( "FATAL: too many signers to fit into u32 range" )
206
+ } )
207
+ . collect ( ) ;
208
+ let chunks = signers_session
209
+ . get_latest_chunks ( & slot_ids)
210
+ . inspect_err ( |e| warn ! ( "Unable to read the latest signer state from signer db: {e}." ) )
211
+ . unwrap_or_default ( ) ;
212
+ let mut replay_infos = HashMap :: new ( ) ;
213
+ for ( chunk, slot_id) in chunks. into_iter ( ) . zip ( slot_ids) {
214
+ let Some ( chunk) = chunk else {
215
+ continue ;
216
+ } ;
217
+ let Some ( signer_entry) = & signer_entries. get ( & slot_id) else {
218
+ continue ;
219
+ } ;
220
+ let Ok ( signer_pubkey) = StacksPublicKey :: from_slice ( & signer_entry. signing_key ) else {
221
+ continue ;
222
+ } ;
223
+ if let Ok ( SignerMessageV0 :: StateMachineUpdate ( update) ) =
224
+ SignerMessageV0 :: consensus_deserialize ( & mut chunk. as_slice ( ) )
225
+ {
226
+ let transactions = match update. content {
227
+ StateMachineUpdateContent :: V0 { .. } => vec ! [ ] ,
228
+ StateMachineUpdateContent :: V1 {
229
+ replay_transactions,
230
+ ..
231
+ } => replay_transactions,
232
+ } ;
233
+ let replay_info = ReplayInfo {
234
+ transactions,
235
+ weight : signer_entry. weight ,
236
+ } ;
237
+ replay_infos. insert ( signer_pubkey, replay_info) ;
238
+ }
239
+ }
240
+
164
241
Ok ( Self {
165
242
stackerdb_channel,
166
243
receiver : Some ( receiver) ,
@@ -172,13 +249,15 @@ impl StackerDBListener {
172
249
signer_entries,
173
250
blocks : Arc :: new ( ( Mutex :: new ( HashMap :: new ( ) ) , Condvar :: new ( ) ) ) ,
174
251
signer_idle_timestamps : Arc :: new ( Mutex :: new ( HashMap :: new ( ) ) ) ,
252
+ replay_info : Arc :: new ( Mutex :: new ( replay_infos) ) ,
175
253
} )
176
254
}
177
255
178
256
pub fn get_comms ( & self ) -> StackerDBListenerComms {
179
257
StackerDBListenerComms {
180
258
blocks : self . blocks . clone ( ) ,
181
259
signer_idle_timestamps : self . signer_idle_timestamps . clone ( ) ,
260
+ replay_info : self . replay_info . clone ( ) ,
182
261
}
183
262
}
184
263
@@ -445,8 +524,8 @@ impl StackerDBListener {
445
524
| SignerMessageV0 :: MockBlock ( _) => {
446
525
debug ! ( "Received mock message. Ignoring." ) ;
447
526
}
448
- SignerMessageV0 :: StateMachineUpdate ( _ ) => {
449
- debug ! ( "Received state machine update message. Ignoring." ) ;
527
+ SignerMessageV0 :: StateMachineUpdate ( update ) => {
528
+ self . update_replay_info ( signer_pubkey , signer_entry . weight , update ) ;
450
529
}
451
530
} ;
452
531
}
@@ -472,6 +551,32 @@ impl StackerDBListener {
472
551
idle_timestamps. insert ( signer_pubkey, timestamp_info) ;
473
552
}
474
553
554
+ fn update_replay_info (
555
+ & self ,
556
+ signer_pubkey : StacksPublicKey ,
557
+ weight : u32 ,
558
+ update : StateMachineUpdate ,
559
+ ) {
560
+ let transactions = match update. content {
561
+ StateMachineUpdateContent :: V0 { .. } => vec ! [ ] ,
562
+ StateMachineUpdateContent :: V1 {
563
+ replay_transactions,
564
+ ..
565
+ } => replay_transactions,
566
+ } ;
567
+ let mut replay_infos = self
568
+ . replay_info
569
+ . lock ( )
570
+ . expect ( "FATAL: failed to lock idle timestamps" ) ;
571
+
572
+ // Update the map with the replay info and weight
573
+ let replay_info = ReplayInfo {
574
+ transactions,
575
+ weight,
576
+ } ;
577
+ replay_infos. insert ( signer_pubkey, replay_info) ;
578
+ }
579
+
475
580
/// Do we ignore signer signatures?
476
581
#[ cfg( test) ]
477
582
fn fault_injection_ignore_signatures ( ) -> bool {
@@ -597,4 +702,31 @@ impl StackerDBListenerComms {
597
702
// tenure.
598
703
u64:: MAX
599
704
}
705
+
706
+ /// Get the transactions that at least 70% of the signing power expect to be replayed in
707
+ /// the next stacks block
708
+ pub fn get_replay_transactions ( & self , weight_threshold : u32 ) -> Vec < StacksTransaction > {
709
+ let replay_info = self
710
+ . replay_info
711
+ . lock ( )
712
+ . expect ( "FATAL: failed to lock replay transactions" ) ;
713
+
714
+ let replay_info = replay_info. values ( ) . collect :: < Vec < _ > > ( ) ;
715
+ let mut weights: HashMap < & Vec < StacksTransaction > , u32 > = HashMap :: new ( ) ;
716
+ for info in replay_info {
717
+ // We only care about signers voting for us to replay a specific set of transactions
718
+ if info. transactions . is_empty ( ) {
719
+ continue ;
720
+ }
721
+ let entry = weights. entry ( & info. transactions ) . or_default ( ) ;
722
+ * entry += info. weight ;
723
+ if * entry >= weight_threshold {
724
+ debug ! ( "SignerCoordinator: 70% threshold reached to attempt replay transactions" ;
725
+ "replay_transactions" => ?info. transactions,
726
+ ) ;
727
+ return info. transactions . clone ( ) ;
728
+ }
729
+ }
730
+ vec ! [ ]
731
+ }
600
732
}
0 commit comments