Skip to content

Commit 8950112

Browse files
committed
refactor: replay state as enum, #5971
1 parent 79e690d commit 8950112

File tree

1 file changed

+104
-54
lines changed

1 file changed

+104
-54
lines changed

stacks-signer/src/v0/signer_state.rs

Lines changed: 104 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -103,22 +103,25 @@ pub type ReplayScopeOpt = Option<ReplayScope>;
103103
/// Represents the Tx Replay state
104104
pub enum ReplayState {
105105
/// No replay has started yet, or the previous replay was cleared.
106-
NotStartedOrCleared,
106+
Unset,
107107
/// A replay is currently in progress, with an associated transaction set and scope.
108108
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.
110111
Invalid,
111112
}
112113

113114
impl ReplayState {
114115
/// Constructs a `ReplayState` from a given replay transaction set and an optional scope.
115116
///
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 {
120123
if replay_set.is_empty() {
121-
return Self::NotStartedOrCleared;
124+
return Self::Unset;
122125
}
123126

124127
match scope_opt {
@@ -599,7 +602,7 @@ impl LocalStateMachine {
599602
return Err(ClientError::InvalidResponse(err_msg).into());
600603
}
601604

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);
603606
if let Some(new_replay_state) = self.handle_possible_bitcoin_fork(
604607
db,
605608
client,
@@ -608,15 +611,18 @@ impl LocalStateMachine {
608611
replay_state,
609612
)? {
610613
match new_replay_state {
611-
ReplayState::NotStartedOrCleared => {
614+
ReplayState::Unset => {
612615
tx_replay_set = ReplayTransactionSet::none();
613616
*tx_replay_scope = None;
614617
}
615618
ReplayState::InProgress(new_txs_set, new_scope) => {
616619
tx_replay_set = new_txs_set;
617620
*tx_replay_scope = Some(new_scope);
618621
}
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+
}
620626
}
621627
}
622628
}
@@ -954,8 +960,13 @@ impl LocalStateMachine {
954960
state.tx_replay_set.clone_as_optional()
955961
}
956962

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
959970
pub fn handle_possible_bitcoin_fork(
960971
&self,
961972
db: &SignerDb,
@@ -973,51 +984,37 @@ impl LocalStateMachine {
973984
return Ok(None);
974985
}
975986

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(
999989
db,
1000990
client,
1001991
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)),
10191002
}
1003+
}
10201004

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> {
10211018
info!("Signer State: fork detected";
10221019
"expected_burn_block.height" => expected_burn_block.burn_block_height,
10231020
"expected_burn_block.hash" => %expected_burn_block.consensus_hash,
@@ -1061,8 +1058,8 @@ impl LocalStateMachine {
10611058
past_tip: potential_replay_tip,
10621059
};
10631060
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);
10661063
let replay_state =
10671064
ReplayState::InProgress(ReplayTransactionSet::new(replay_set), scope);
10681065
Ok(Some(replay_state))
@@ -1071,6 +1068,59 @@ impl LocalStateMachine {
10711068
}
10721069
}
10731070

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+
10741124
/// Retrieves the set of transactions that were part of a Bitcoin fork within the same reward cycle.
10751125
///
10761126
/// This method identifies the range of Tenures affected by a fork, from the `fork_tip` down to the `fork_origin`

0 commit comments

Comments
 (0)