Skip to content

Commit 79e690d

Browse files
committed
refactor: created ReplayState struct, #6184
1 parent 5c6780c commit 79e690d

File tree

2 files changed

+74
-40
lines changed

2 files changed

+74
-40
lines changed

stacks-signer/src/v0/signer.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ use crate::client::{ClientError, SignerSlotID, StackerDB, StacksClient};
5252
use crate::config::{SignerConfig, SignerConfigMode};
5353
use crate::runloop::SignerResult;
5454
use crate::signerdb::{BlockInfo, BlockState, SignerDb};
55-
use crate::v0::signer_state::{NewBurnBlock, TxReplayScopeOpt};
55+
use crate::v0::signer_state::{NewBurnBlock, ReplayScopeOpt};
5656
use crate::Signer as SignerTrait;
5757

5858
/// A global variable that can be used to make signers repeat their proposal
@@ -126,7 +126,7 @@ pub struct Signer {
126126
/// Whether to validate blocks with replay transactions
127127
pub validate_with_replay_tx: bool,
128128
/// Scope of Tx Replay in terms of Burn block boundaries
129-
pub tx_replay_scope: TxReplayScopeOpt,
129+
pub tx_replay_scope: ReplayScopeOpt,
130130
}
131131

132132
impl std::fmt::Display for SignerMode {

stacks-signer/src/v0/signer_state.rs

Lines changed: 72 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -89,16 +89,44 @@ pub struct NewBurnBlock {
8989
}
9090

9191
/// 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 {
9494
/// The burn block where the fork that originated the transaction replay began.
9595
pub fork_origin: NewBurnBlock,
9696
/// The canonical burn chain tip at the time the transaction replay started.
9797
pub past_tip: NewBurnBlock,
9898
}
9999

100100
/// 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+
}
102130

103131
impl LocalStateMachine {
104132
/// Initialize a local state machine by querying the local stacks-node
@@ -204,7 +232,7 @@ impl LocalStateMachine {
204232
db: &SignerDb,
205233
client: &StacksClient,
206234
proposal_config: &ProposalEvalConfig,
207-
tx_replay_scope: &mut TxReplayScopeOpt,
235+
tx_replay_scope: &mut ReplayScopeOpt,
208236
) -> Result<(), SignerChainstateError> {
209237
let LocalStateMachine::Pending { update, .. } = self else {
210238
return self.check_miner_inactivity(db, client, proposal_config);
@@ -514,7 +542,7 @@ impl LocalStateMachine {
514542
client: &StacksClient,
515543
proposal_config: &ProposalEvalConfig,
516544
mut expected_burn_block: Option<NewBurnBlock>,
517-
tx_replay_scope: &mut TxReplayScopeOpt,
545+
tx_replay_scope: &mut ReplayScopeOpt,
518546
) -> Result<(), SignerChainstateError> {
519547
// set self to uninitialized so that if this function errors,
520548
// self is left as uninitialized.
@@ -570,16 +598,26 @@ impl LocalStateMachine {
570598
};
571599
return Err(ClientError::InvalidResponse(err_msg).into());
572600
}
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(
574604
db,
575605
client,
576606
&expected_burn_block,
577607
&prior_state_machine,
578-
tx_replay_set.is_some(),
579-
tx_replay_scope,
608+
replay_state,
580609
)? {
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+
}
583621
}
584622
}
585623

@@ -924,9 +962,8 @@ impl LocalStateMachine {
924962
client: &StacksClient,
925963
expected_burn_block: &NewBurnBlock,
926964
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> {
930967
if expected_burn_block.burn_block_height > prior_state_machine.burn_block_height {
931968
// no bitcoin fork, because we're advancing the burn block height
932969
return Ok(None);
@@ -935,22 +972,20 @@ impl LocalStateMachine {
935972
// no bitcoin fork, because we're at the same burn block hash as before
936973
return Ok(None);
937974
}
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 {
939982
info!("Tx Replay: detected bitcoin fork while in replay mode. Tryng to handle the fork";
940983
"expected_burn_block.height" => expected_burn_block.burn_block_height,
941984
"expected_burn_block.hash" => %expected_burn_block.consensus_hash,
942985
"prior_state_machine.burn_block_height" => prior_state_machine.burn_block_height,
943986
"prior_state_machine.burn_block" => %prior_state_machine.burn_block,
944987
);
945988

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-
954989
let is_deepest_fork =
955990
expected_burn_block.burn_block_height < scope.fork_origin.burn_block_height;
956991
if !is_deepest_fork {
@@ -959,30 +994,28 @@ impl LocalStateMachine {
959994
return Ok(None);
960995
}
961996

962-
let updated_replay_set;
963-
let updated_scope_opt;
997+
let replay_state;
964998
if let Some(replay_set) = self.compute_forked_txs_set_in_same_cycle(
965999
db,
9661000
client,
9671001
expected_burn_block,
9681002
&scope.past_tip,
9691003
)? {
970-
let scope = TxReplayScope {
1004+
let scope = ReplayScope {
9711005
fork_origin: expected_burn_block.clone(),
9721006
past_tip: scope.past_tip.clone(),
9731007
};
9741008

9751009
info!("Tx Replay: replay set updated with {} tx(s)", replay_set.len();
9761010
"tx_replay_set" => ?replay_set,
9771011
"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);
9801014
} else {
9811015
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;
9841017
}
985-
return Ok(Some((updated_replay_set, updated_scope_opt)));
1018+
return Ok(Some(replay_state));
9861019
}
9871020

9881021
info!("Signer State: fork detected";
@@ -1019,20 +1052,21 @@ impl LocalStateMachine {
10191052
Ok(None)
10201053
}
10211054
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 {
10241060
fork_origin: expected_burn_block.clone(),
10251061
past_tip: potential_replay_tip,
10261062
};
10271063
info!("Tx Replay: replay set updated with {} tx(s)", replay_set.len();
10281064
"tx_replay_set" => ?replay_set,
10291065
"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+
}
10361070
}
10371071
}
10381072
}

0 commit comments

Comments
 (0)