@@ -103,22 +103,25 @@ pub type ReplayScopeOpt = Option<ReplayScope>;
103
103
/// Represents the Tx Replay state
104
104
pub enum ReplayState {
105
105
/// No replay has started yet, or the previous replay was cleared.
106
- NotStartedOrCleared ,
106
+ Unset ,
107
107
/// A replay is currently in progress, with an associated transaction set and scope.
108
108
InProgress ( ReplayTransactionSet , ReplayScope ) ,
109
- /// An invalid state — a transaction set was provided without a valid scope.
109
+ /// An invalid state where a replay set was provided without a valid scope.
110
+ /// Possibly caused by the scope being a local state in the `Signer` struct.
110
111
Invalid ,
111
112
}
112
113
113
114
impl ReplayState {
114
115
/// Constructs a `ReplayState` from a given replay transaction set and an optional scope.
115
116
///
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 {
117
+ /// # Returns
118
+ ///
119
+ /// - `Unset` if the replay set is empty.
120
+ /// - `Invalid` if the replay set exists but the scope is missing.
121
+ /// - `InProgress` if both a non-empty replay set and a valid scope are provided.
122
+ fn infer_state ( replay_set : & ReplayTransactionSet , scope_opt : & ReplayScopeOpt ) -> Self {
120
123
if replay_set. is_empty ( ) {
121
- return Self :: NotStartedOrCleared ;
124
+ return Self :: Unset ;
122
125
}
123
126
124
127
match scope_opt {
@@ -599,7 +602,7 @@ impl LocalStateMachine {
599
602
return Err ( ClientError :: InvalidResponse ( err_msg) . into ( ) ) ;
600
603
}
601
604
602
- let replay_state = ReplayState :: from ( & tx_replay_set, & tx_replay_scope) ;
605
+ let replay_state = ReplayState :: infer_state ( & tx_replay_set, & tx_replay_scope) ;
603
606
if let Some ( new_replay_state) = self . handle_possible_bitcoin_fork (
604
607
db,
605
608
client,
@@ -608,15 +611,18 @@ impl LocalStateMachine {
608
611
replay_state,
609
612
) ? {
610
613
match new_replay_state {
611
- ReplayState :: NotStartedOrCleared => {
614
+ ReplayState :: Unset => {
612
615
tx_replay_set = ReplayTransactionSet :: none ( ) ;
613
616
* tx_replay_scope = None ;
614
617
}
615
618
ReplayState :: InProgress ( new_txs_set, new_scope) => {
616
619
tx_replay_set = new_txs_set;
617
620
* tx_replay_scope = Some ( new_scope) ;
618
621
}
619
- ReplayState :: Invalid => warn ! ( "Tx Replay: Invalid is not possible here!" ) ,
622
+ ReplayState :: Invalid => {
623
+ warn ! ( "Tx Replay: Invalid state due to scope being not set while in replay mode!" ) ;
624
+ return Err ( SignerChainstateError :: LocalStateMachineNotReady ) ;
625
+ }
620
626
}
621
627
}
622
628
}
@@ -954,8 +960,13 @@ impl LocalStateMachine {
954
960
state. tx_replay_set . clone_as_optional ( )
955
961
}
956
962
957
- /// Handle a possible bitcoin fork. If a fork is detetected,
958
- /// return the transactions that should be replayed.
963
+ /// Handle a possible bitcoin fork. If a fork is detected,
964
+ /// try to handle the possible replay state.
965
+ ///
966
+ /// # Returns
967
+ /// - `Ok(None)` if nothing need to be done about replay
968
+ /// - `Ok(Some(ReplayState))` if a change (new or update) to the replay state is required
969
+ /// - `Err(SignerChainstateError)` in case of chain state errors
959
970
pub fn handle_possible_bitcoin_fork (
960
971
& self ,
961
972
db : & SignerDb ,
@@ -973,51 +984,37 @@ impl LocalStateMachine {
973
984
return Ok ( None ) ;
974
985
}
975
986
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 {
982
- info ! ( "Tx Replay: detected bitcoin fork while in replay mode. Tryng to handle the fork" ;
983
- "expected_burn_block.height" => expected_burn_block. burn_block_height,
984
- "expected_burn_block.hash" => %expected_burn_block. consensus_hash,
985
- "prior_state_machine.burn_block_height" => prior_state_machine. burn_block_height,
986
- "prior_state_machine.burn_block" => %prior_state_machine. burn_block,
987
- ) ;
988
-
989
- let is_deepest_fork =
990
- expected_burn_block. burn_block_height < scope. fork_origin . burn_block_height ;
991
- if !is_deepest_fork {
992
- //if it is within the scope or after - this is not a new fork, but the continue of a reorg
993
- info ! ( "Tx Replay: nothing todo. Reorg in progress!" ) ;
994
- return Ok ( None ) ;
995
- }
996
-
997
- let replay_state;
998
- if let Some ( replay_set) = self . compute_forked_txs_set_in_same_cycle (
987
+ match replay_state {
988
+ ReplayState :: Unset => self . handle_fork_for_new_replay (
999
989
db,
1000
990
client,
1001
991
expected_burn_block,
1002
- & scope. past_tip ,
1003
- ) ? {
1004
- let scope = ReplayScope {
1005
- fork_origin : expected_burn_block. clone ( ) ,
1006
- past_tip : scope. past_tip . clone ( ) ,
1007
- } ;
1008
-
1009
- info ! ( "Tx Replay: replay set updated with {} tx(s)" , replay_set. len( ) ;
1010
- "tx_replay_set" => ?replay_set,
1011
- "tx_replay_scope" => ?scope) ;
1012
- replay_state =
1013
- ReplayState :: InProgress ( ReplayTransactionSet :: new ( replay_set) , scope) ;
1014
- } else {
1015
- info ! ( "Tx Replay: replay set will be cleared, because the fork involves the previous reward cycle." ) ;
1016
- replay_state = ReplayState :: NotStartedOrCleared ;
1017
- }
1018
- return Ok ( Some ( replay_state) ) ;
992
+ prior_state_machine,
993
+ ) ,
994
+ ReplayState :: InProgress ( _, scope) => self . handle_fork_on_in_progress_replay (
995
+ db,
996
+ client,
997
+ expected_burn_block,
998
+ prior_state_machine,
999
+ scope,
1000
+ ) ,
1001
+ ReplayState :: Invalid => Ok ( Some ( ReplayState :: Invalid ) ) ,
1019
1002
}
1003
+ }
1020
1004
1005
+ /// Understand if the fork produces a replay set to be managed
1006
+ ///
1007
+ /// # Returns
1008
+ ///
1009
+ /// - `Ok(None)` if nothing need to be done
1010
+ /// - `Ok(Some(ReplayState::InProgress(..)))` in case a replay need to be started
1011
+ fn handle_fork_for_new_replay (
1012
+ & self ,
1013
+ db : & SignerDb ,
1014
+ client : & StacksClient ,
1015
+ expected_burn_block : & NewBurnBlock ,
1016
+ prior_state_machine : & SignerStateMachine ,
1017
+ ) -> Result < Option < ReplayState > , SignerChainstateError > {
1021
1018
info ! ( "Signer State: fork detected" ;
1022
1019
"expected_burn_block.height" => expected_burn_block. burn_block_height,
1023
1020
"expected_burn_block.hash" => %expected_burn_block. consensus_hash,
@@ -1061,8 +1058,8 @@ impl LocalStateMachine {
1061
1058
past_tip : potential_replay_tip,
1062
1059
} ;
1063
1060
info ! ( "Tx Replay: replay set updated with {} tx(s)" , replay_set. len( ) ;
1064
- "tx_replay_set" => ?replay_set,
1065
- "tx_replay_scope" => ?scope) ;
1061
+ "tx_replay_set" => ?replay_set,
1062
+ "tx_replay_scope" => ?scope) ;
1066
1063
let replay_state =
1067
1064
ReplayState :: InProgress ( ReplayTransactionSet :: new ( replay_set) , scope) ;
1068
1065
Ok ( Some ( replay_state) )
@@ -1071,6 +1068,59 @@ impl LocalStateMachine {
1071
1068
}
1072
1069
}
1073
1070
1071
+ /// Understand if the fork produces changes over an in-progress replay
1072
+ ///
1073
+ /// # Returns
1074
+ ///
1075
+ /// - `Ok(None)` if nothing need to be done
1076
+ /// - `Ok(Some(ReplayState::Unset))` in case a replay set need to be cleared
1077
+ /// - `Ok(Some(ReplayState::InProgress(..)))` in case a replay set need to be updated
1078
+ fn handle_fork_on_in_progress_replay (
1079
+ & self ,
1080
+ db : & SignerDb ,
1081
+ client : & StacksClient ,
1082
+ expected_burn_block : & NewBurnBlock ,
1083
+ prior_state_machine : & SignerStateMachine ,
1084
+ scope : ReplayScope ,
1085
+ ) -> Result < Option < ReplayState > , SignerChainstateError > {
1086
+ info ! ( "Tx Replay: detected bitcoin fork while in replay mode. Tryng to handle the fork" ;
1087
+ "expected_burn_block.height" => expected_burn_block. burn_block_height,
1088
+ "expected_burn_block.hash" => %expected_burn_block. consensus_hash,
1089
+ "prior_state_machine.burn_block_height" => prior_state_machine. burn_block_height,
1090
+ "prior_state_machine.burn_block" => %prior_state_machine. burn_block,
1091
+ ) ;
1092
+
1093
+ let is_deepest_fork =
1094
+ expected_burn_block. burn_block_height < scope. fork_origin . burn_block_height ;
1095
+ if !is_deepest_fork {
1096
+ //if it is within the scope or after - this is not a new fork, but the continue of a reorg
1097
+ info ! ( "Tx Replay: nothing todo. Reorg in progress!" ) ;
1098
+ return Ok ( None ) ;
1099
+ }
1100
+
1101
+ let replay_state;
1102
+ if let Some ( replay_set) = self . compute_forked_txs_set_in_same_cycle (
1103
+ db,
1104
+ client,
1105
+ expected_burn_block,
1106
+ & scope. past_tip ,
1107
+ ) ? {
1108
+ let scope = ReplayScope {
1109
+ fork_origin : expected_burn_block. clone ( ) ,
1110
+ past_tip : scope. past_tip . clone ( ) ,
1111
+ } ;
1112
+
1113
+ info ! ( "Tx Replay: replay set updated with {} tx(s)" , replay_set. len( ) ;
1114
+ "tx_replay_set" => ?replay_set,
1115
+ "tx_replay_scope" => ?scope) ;
1116
+ replay_state = ReplayState :: InProgress ( ReplayTransactionSet :: new ( replay_set) , scope) ;
1117
+ } else {
1118
+ info ! ( "Tx Replay: replay set will be cleared, because the fork involves the previous reward cycle." ) ;
1119
+ replay_state = ReplayState :: Unset ;
1120
+ }
1121
+ return Ok ( Some ( replay_state) ) ;
1122
+ }
1123
+
1074
1124
/// Retrieves the set of transactions that were part of a Bitcoin fork within the same reward cycle.
1075
1125
///
1076
1126
/// This method identifies the range of Tenures affected by a fork, from the `fork_tip` down to the `fork_origin`
0 commit comments