@@ -89,16 +89,44 @@ pub struct NewBurnBlock {
89
89
}
90
90
91
91
/// Represents the scope of Tx Replay in terms of burn block boundaries.
92
- #[ derive( Debug ) ]
93
- pub struct TxReplayScope {
92
+ #[ derive( Debug , Clone ) ]
93
+ pub struct ReplayScope {
94
94
/// The burn block where the fork that originated the transaction replay began.
95
95
pub fork_origin : NewBurnBlock ,
96
96
/// The canonical burn chain tip at the time the transaction replay started.
97
97
pub past_tip : NewBurnBlock ,
98
98
}
99
99
100
100
/// Optional `TxReplayScope`, representing the potential absence of a replay scope.
101
- pub type TxReplayScopeOpt = Option < TxReplayScope > ;
101
+ pub type ReplayScopeOpt = Option < ReplayScope > ;
102
+
103
+ /// Represents the Tx Replay state
104
+ pub enum ReplayState {
105
+ /// No replay has started yet, or the previous replay was cleared.
106
+ NotStartedOrCleared ,
107
+ /// A replay is currently in progress, with an associated transaction set and scope.
108
+ InProgress ( ReplayTransactionSet , ReplayScope ) ,
109
+ /// An invalid state — a transaction set was provided without a valid scope.
110
+ Invalid ,
111
+ }
112
+
113
+ impl ReplayState {
114
+ /// Constructs a `ReplayState` from a given replay transaction set and an optional scope.
115
+ ///
116
+ /// - Returns `NotStartedOrCleared` if the replay set is empty.
117
+ /// - Returns `Invalid` if the replay set exists but the scope is missing.
118
+ /// - Returns `InProgress` if both a non-empty replay set and a valid scope are provided.
119
+ fn from ( replay_set : & ReplayTransactionSet , scope_opt : & ReplayScopeOpt ) -> Self {
120
+ if replay_set. is_empty ( ) {
121
+ return Self :: NotStartedOrCleared ;
122
+ }
123
+
124
+ match scope_opt {
125
+ None => Self :: Invalid ,
126
+ Some ( scope) => Self :: InProgress ( replay_set. clone ( ) , scope. clone ( ) ) ,
127
+ }
128
+ }
129
+ }
102
130
103
131
impl LocalStateMachine {
104
132
/// Initialize a local state machine by querying the local stacks-node
@@ -204,7 +232,7 @@ impl LocalStateMachine {
204
232
db : & SignerDb ,
205
233
client : & StacksClient ,
206
234
proposal_config : & ProposalEvalConfig ,
207
- tx_replay_scope : & mut TxReplayScopeOpt ,
235
+ tx_replay_scope : & mut ReplayScopeOpt ,
208
236
) -> Result < ( ) , SignerChainstateError > {
209
237
let LocalStateMachine :: Pending { update, .. } = self else {
210
238
return self . check_miner_inactivity ( db, client, proposal_config) ;
@@ -514,7 +542,7 @@ impl LocalStateMachine {
514
542
client : & StacksClient ,
515
543
proposal_config : & ProposalEvalConfig ,
516
544
mut expected_burn_block : Option < NewBurnBlock > ,
517
- tx_replay_scope : & mut TxReplayScopeOpt ,
545
+ tx_replay_scope : & mut ReplayScopeOpt ,
518
546
) -> Result < ( ) , SignerChainstateError > {
519
547
// set self to uninitialized so that if this function errors,
520
548
// self is left as uninitialized.
@@ -570,16 +598,26 @@ impl LocalStateMachine {
570
598
} ;
571
599
return Err ( ClientError :: InvalidResponse ( err_msg) . into ( ) ) ;
572
600
}
573
- if let Some ( ( new_replay_set, new_replay_scope) ) = self . handle_possible_bitcoin_fork (
601
+
602
+ let replay_state = ReplayState :: from ( & tx_replay_set, & tx_replay_scope) ;
603
+ if let Some ( new_replay_state) = self . handle_possible_bitcoin_fork (
574
604
db,
575
605
client,
576
606
& expected_burn_block,
577
607
& prior_state_machine,
578
- tx_replay_set. is_some ( ) ,
579
- tx_replay_scope,
608
+ replay_state,
580
609
) ? {
581
- tx_replay_set = ReplayTransactionSet :: new ( new_replay_set) ;
582
- * tx_replay_scope = new_replay_scope;
610
+ match new_replay_state {
611
+ ReplayState :: NotStartedOrCleared => {
612
+ tx_replay_set = ReplayTransactionSet :: none ( ) ;
613
+ * tx_replay_scope = None ;
614
+ }
615
+ ReplayState :: InProgress ( new_txs_set, new_scope) => {
616
+ tx_replay_set = new_txs_set;
617
+ * tx_replay_scope = Some ( new_scope) ;
618
+ }
619
+ ReplayState :: Invalid => warn ! ( "Tx Replay: Invalid is not possible here!" ) ,
620
+ }
583
621
}
584
622
}
585
623
@@ -924,9 +962,8 @@ impl LocalStateMachine {
924
962
client : & StacksClient ,
925
963
expected_burn_block : & NewBurnBlock ,
926
964
prior_state_machine : & SignerStateMachine ,
927
- is_in_tx_replay_mode : bool ,
928
- tx_replay_scope : & TxReplayScopeOpt ,
929
- ) -> Result < Option < ( Vec < StacksTransaction > , TxReplayScopeOpt ) > , SignerChainstateError > {
965
+ replay_state : ReplayState ,
966
+ ) -> Result < Option < ReplayState > , SignerChainstateError > {
930
967
if expected_burn_block. burn_block_height > prior_state_machine. burn_block_height {
931
968
// no bitcoin fork, because we're advancing the burn block height
932
969
return Ok ( None ) ;
@@ -935,22 +972,20 @@ impl LocalStateMachine {
935
972
// no bitcoin fork, because we're at the same burn block hash as before
936
973
return Ok ( None ) ;
937
974
}
938
- if is_in_tx_replay_mode {
975
+
976
+ if matches ! ( replay_state, ReplayState :: Invalid ) {
977
+ warn ! ( "Tx Replay: Invalid state due to scope being not set while in replay mode!" ) ;
978
+ return Err ( SignerChainstateError :: LocalStateMachineNotReady ) ;
979
+ }
980
+
981
+ if let ReplayState :: InProgress ( _txs_set, scope) = replay_state {
939
982
info ! ( "Tx Replay: detected bitcoin fork while in replay mode. Tryng to handle the fork" ;
940
983
"expected_burn_block.height" => expected_burn_block. burn_block_height,
941
984
"expected_burn_block.hash" => %expected_burn_block. consensus_hash,
942
985
"prior_state_machine.burn_block_height" => prior_state_machine. burn_block_height,
943
986
"prior_state_machine.burn_block" => %prior_state_machine. burn_block,
944
987
) ;
945
988
946
- let scope = match tx_replay_scope {
947
- Some ( scope) => scope,
948
- None => {
949
- warn ! ( "Tx Replay: BUG! Scope cannot be None while in replay mode!" ) ;
950
- return Err ( SignerChainstateError :: LocalStateMachineNotReady ) ;
951
- }
952
- } ;
953
-
954
989
let is_deepest_fork =
955
990
expected_burn_block. burn_block_height < scope. fork_origin . burn_block_height ;
956
991
if !is_deepest_fork {
@@ -959,30 +994,28 @@ impl LocalStateMachine {
959
994
return Ok ( None ) ;
960
995
}
961
996
962
- let updated_replay_set;
963
- let updated_scope_opt;
997
+ let replay_state;
964
998
if let Some ( replay_set) = self . compute_forked_txs_set_in_same_cycle (
965
999
db,
966
1000
client,
967
1001
expected_burn_block,
968
1002
& scope. past_tip ,
969
1003
) ? {
970
- let scope = TxReplayScope {
1004
+ let scope = ReplayScope {
971
1005
fork_origin : expected_burn_block. clone ( ) ,
972
1006
past_tip : scope. past_tip . clone ( ) ,
973
1007
} ;
974
1008
975
1009
info ! ( "Tx Replay: replay set updated with {} tx(s)" , replay_set. len( ) ;
976
1010
"tx_replay_set" => ?replay_set,
977
1011
"tx_replay_scope" => ?scope) ;
978
- updated_replay_set = replay_set ;
979
- updated_scope_opt = Some ( scope) ;
1012
+ replay_state =
1013
+ ReplayState :: InProgress ( ReplayTransactionSet :: new ( replay_set ) , scope) ;
980
1014
} else {
981
1015
info ! ( "Tx Replay: replay set will be cleared, because the fork involves the previous reward cycle." ) ;
982
- updated_replay_set = vec ! [ ] ;
983
- updated_scope_opt = None ;
1016
+ replay_state = ReplayState :: NotStartedOrCleared ;
984
1017
}
985
- return Ok ( Some ( ( updated_replay_set , updated_scope_opt ) ) ) ;
1018
+ return Ok ( Some ( replay_state ) ) ;
986
1019
}
987
1020
988
1021
info ! ( "Signer State: fork detected" ;
@@ -1019,20 +1052,21 @@ impl LocalStateMachine {
1019
1052
Ok ( None )
1020
1053
}
1021
1054
Some ( replay_set) => {
1022
- let scope_opt = if !replay_set. is_empty ( ) {
1023
- let scope = TxReplayScope {
1055
+ if replay_set. is_empty ( ) {
1056
+ info ! ( "Tx Replay: no transactions to be replayed." ) ;
1057
+ Ok ( None )
1058
+ } else {
1059
+ let scope = ReplayScope {
1024
1060
fork_origin : expected_burn_block. clone ( ) ,
1025
1061
past_tip : potential_replay_tip,
1026
1062
} ;
1027
1063
info ! ( "Tx Replay: replay set updated with {} tx(s)" , replay_set. len( ) ;
1028
1064
"tx_replay_set" => ?replay_set,
1029
1065
"tx_replay_scope" => ?scope) ;
1030
- Some ( scope)
1031
- } else {
1032
- info ! ( "Tx Replay: no transactions to be replayed." ) ;
1033
- None
1034
- } ;
1035
- Ok ( Some ( ( replay_set, scope_opt) ) )
1066
+ let replay_state =
1067
+ ReplayState :: InProgress ( ReplayTransactionSet :: new ( replay_set) , scope) ;
1068
+ Ok ( Some ( replay_state) )
1069
+ }
1036
1070
}
1037
1071
}
1038
1072
}
0 commit comments