@@ -97,7 +97,7 @@ impl LocalStateMachine {
97
97
proposal_config : & ProposalEvalConfig ,
98
98
) -> Result < Self , SignerChainstateError > {
99
99
let mut instance = Self :: Uninitialized ;
100
- instance. bitcoin_block_arrival ( db, client, proposal_config, None ) ?;
100
+ instance. bitcoin_block_arrival ( db, client, proposal_config, None , & mut None ) ?;
101
101
102
102
Ok ( instance)
103
103
}
@@ -192,14 +192,19 @@ impl LocalStateMachine {
192
192
db : & SignerDb ,
193
193
client : & StacksClient ,
194
194
proposal_config : & ProposalEvalConfig ,
195
+ tx_replay_scope : & mut Option < ( NewBurnBlock , NewBurnBlock ) > ,
195
196
) -> Result < ( ) , SignerChainstateError > {
196
197
let LocalStateMachine :: Pending { update, .. } = self else {
197
198
return self . check_miner_inactivity ( db, client, proposal_config) ;
198
199
} ;
199
200
match update. clone ( ) {
200
- StateMachineUpdate :: BurnBlock ( expected_burn_height) => {
201
- self . bitcoin_block_arrival ( db, client, proposal_config, Some ( expected_burn_height) )
202
- }
201
+ StateMachineUpdate :: BurnBlock ( expected_burn_height) => self . bitcoin_block_arrival (
202
+ db,
203
+ client,
204
+ proposal_config,
205
+ Some ( expected_burn_height) ,
206
+ tx_replay_scope,
207
+ ) ,
203
208
}
204
209
}
205
210
@@ -497,6 +502,7 @@ impl LocalStateMachine {
497
502
client : & StacksClient ,
498
503
proposal_config : & ProposalEvalConfig ,
499
504
mut expected_burn_block : Option < NewBurnBlock > ,
505
+ tx_replay_scope : & mut Option < ( NewBurnBlock , NewBurnBlock ) > ,
500
506
) -> Result < ( ) , SignerChainstateError > {
501
507
// set self to uninitialized so that if this function errors,
502
508
// self is left as uninitialized.
@@ -558,6 +564,7 @@ impl LocalStateMachine {
558
564
& expected_burn_block,
559
565
& prior_state_machine,
560
566
tx_replay_set. is_some ( ) ,
567
+ tx_replay_scope,
561
568
) ? {
562
569
tx_replay_set = ReplayTransactionSet :: new ( new_replay_set) ;
563
570
}
@@ -889,6 +896,7 @@ impl LocalStateMachine {
889
896
expected_burn_block : & NewBurnBlock ,
890
897
prior_state_machine : & SignerStateMachine ,
891
898
is_in_tx_replay_mode : bool ,
899
+ tx_replay_scope : & mut Option < ( NewBurnBlock , NewBurnBlock ) > ,
892
900
) -> Result < Option < Vec < StacksTransaction > > , SignerChainstateError > {
893
901
if expected_burn_block. burn_block_height > prior_state_machine. burn_block_height {
894
902
// no bitcoin fork, because we're advancing the burn block height
@@ -899,10 +907,45 @@ impl LocalStateMachine {
899
907
return Ok ( None ) ;
900
908
}
901
909
if is_in_tx_replay_mode {
902
- // TODO: handle fork while still in replay
903
- info ! ( "Detected bitcoin fork while in replay mode, will not try to handle the fork" ) ;
904
- return Ok ( None ) ;
910
+ info ! ( "Tx Replay: detected bitcoin fork while in replay mode. Tryng to handle the fork" ;
911
+ "expected_burn_block.height" => expected_burn_block. burn_block_height,
912
+ "expected_burn_block.hash" => %expected_burn_block. consensus_hash,
913
+ "prior_state_machine.burn_block_height" => prior_state_machine. burn_block_height,
914
+ "prior_state_machine.burn_block" => %prior_state_machine. burn_block,
915
+ ) ;
916
+
917
+ //TODO: Remove unwrap once decided the final tx_replay_scope structure format
918
+ // and if to handle them as part of State machine update
919
+ let curr_scope = tx_replay_scope. clone ( ) . unwrap ( ) ;
920
+
921
+ let ( fork_origin, past_tip) = & curr_scope;
922
+ let is_deepest_fork =
923
+ expected_burn_block. burn_block_height < fork_origin. burn_block_height ;
924
+ if !is_deepest_fork {
925
+ //if it is within the scope or after - this is not a new fork, but the continue of a reorg
926
+ info ! ( "Tx Replay: nothing todo. Reorg in progress!" ) ;
927
+ return Ok ( None ) ;
928
+ }
929
+
930
+ let updated_replay_set;
931
+ if let Some ( replay_set) =
932
+ self . compute_forked_txs_set ( db, client, expected_burn_block, & past_tip) ?
933
+ {
934
+ let updated_scope = ( expected_burn_block. clone ( ) , past_tip. clone ( ) ) ;
935
+
936
+ info ! ( "Tx Replay: replay set updated with {} tx(s)" , replay_set. len( ) ;
937
+ "tx_replay_set" => ?replay_set,
938
+ "tx_replay_scope" => ?updated_scope) ;
939
+ updated_replay_set = replay_set;
940
+ * tx_replay_scope = Some ( updated_scope) ;
941
+ } else {
942
+ info ! ( "Tx Replay: replay set will be cleared, because the fork involves the previous reward cycle." ) ;
943
+ updated_replay_set = vec ! [ ] ;
944
+ * tx_replay_scope = None ;
945
+ }
946
+ return Ok ( Some ( updated_replay_set) ) ;
905
947
}
948
+
906
949
info ! ( "Signer State: fork detected" ;
907
950
"expected_burn_block.height" => expected_burn_block. burn_block_height,
908
951
"expected_burn_block.hash" => %expected_burn_block. consensus_hash,
@@ -920,11 +963,19 @@ impl LocalStateMachine {
920
963
return Ok ( None ) ;
921
964
}
922
965
}
966
+
923
967
// Determine the tenures that were forked
924
968
let mut parent_burn_block_info =
925
969
db. get_burn_block_by_ch ( & prior_state_machine. burn_block ) ?;
970
+
971
+ let potential_replay_tip = NewBurnBlock {
972
+ burn_block_height : parent_burn_block_info. block_height ,
973
+ consensus_hash : parent_burn_block_info. consensus_hash ,
974
+ } ;
975
+
926
976
let last_forked_tenure = prior_state_machine. burn_block ;
927
977
let mut first_forked_tenure = prior_state_machine. burn_block ;
978
+
928
979
let mut forked_tenures = vec ! [ (
929
980
prior_state_machine. burn_block,
930
981
prior_state_machine. burn_block_height,
@@ -954,6 +1005,81 @@ impl LocalStateMachine {
954
1005
return Ok ( None ) ;
955
1006
}
956
1007
1008
+ // Collect transactions to be replayed across the forked blocks
1009
+ let mut forked_blocks = fork_info
1010
+ . iter ( )
1011
+ . flat_map ( |fork_info| fork_info. nakamoto_blocks . iter ( ) . flatten ( ) )
1012
+ . collect :: < Vec < _ > > ( ) ;
1013
+ forked_blocks. sort_by_key ( |block| block. header . chain_length ) ;
1014
+ let forked_txs = forked_blocks
1015
+ . iter ( )
1016
+ . flat_map ( |block| block. txs . iter ( ) )
1017
+ . filter ( |tx|
1018
+ // Don't include Coinbase, TenureChange, or PoisonMicroblock transactions
1019
+ !matches ! (
1020
+ tx. payload,
1021
+ TransactionPayload :: TenureChange ( ..)
1022
+ | TransactionPayload :: Coinbase ( ..)
1023
+ | TransactionPayload :: PoisonMicroblock ( ..)
1024
+ ) )
1025
+ . cloned ( )
1026
+ . collect :: < Vec < _ > > ( ) ;
1027
+ if forked_txs. len ( ) > 0 {
1028
+ let updated_scope = ( expected_burn_block. clone ( ) , potential_replay_tip) ;
1029
+ info ! ( "Tx Replay: replay set updated with {} tx(s)" , forked_txs. len( ) ;
1030
+ "tx_replay_set" => ?forked_txs,
1031
+ "tx_replay_scope" => ?updated_scope) ;
1032
+ * tx_replay_scope = Some ( updated_scope) ;
1033
+ } else {
1034
+ info ! ( "Tx Replay: no transactions to be replayed." ) ;
1035
+ * tx_replay_scope = None ;
1036
+ }
1037
+ Ok ( Some ( forked_txs) )
1038
+ }
1039
+
1040
+ ///TODO: This method can be used to remove dublication in 'handle_possible_bitcoin_fork'
1041
+ /// Just waiting to avoid potential merge conflict with PR #6109
1042
+ /// Retrieve all the transactions that are involved by the fork
1043
+ /// from the start block (highest height) back to the end block (lowest height)
1044
+ fn compute_forked_txs_set (
1045
+ & self ,
1046
+ db : & SignerDb ,
1047
+ client : & StacksClient ,
1048
+ end_block : & NewBurnBlock ,
1049
+ start_block : & NewBurnBlock ,
1050
+ ) -> Result < Option < Vec < StacksTransaction > > , SignerChainstateError > {
1051
+ // Determine the tenures that were forked
1052
+ let mut parent_burn_block_info = db. get_burn_block_by_ch ( & start_block. consensus_hash ) ?;
1053
+ let last_forked_tenure = start_block. consensus_hash ;
1054
+ let mut first_forked_tenure = start_block. consensus_hash ;
1055
+ let mut forked_tenures = vec ! [ ( start_block. consensus_hash, start_block. burn_block_height) ] ;
1056
+ while parent_burn_block_info. block_height > end_block. burn_block_height {
1057
+ parent_burn_block_info =
1058
+ db. get_burn_block_by_hash ( & parent_burn_block_info. parent_burn_block_hash ) ?;
1059
+ first_forked_tenure = parent_burn_block_info. consensus_hash ;
1060
+ forked_tenures. push ( (
1061
+ parent_burn_block_info. consensus_hash ,
1062
+ parent_burn_block_info. block_height ,
1063
+ ) ) ;
1064
+ }
1065
+ let fork_info =
1066
+ client. get_tenure_forking_info ( & first_forked_tenure, & last_forked_tenure) ?;
1067
+
1068
+ // Check if fork occurred within current reward cycle. Reject tx replay otherwise.
1069
+ let reward_cycle_info = client. get_current_reward_cycle_info ( ) ?;
1070
+
1071
+ let current_reward_cycle = reward_cycle_info. reward_cycle ;
1072
+ let is_fork_in_current_reward_cycle = fork_info. iter ( ) . all ( |fork_info| {
1073
+ let block_height = fork_info. burn_block_height ;
1074
+ let block_rc = reward_cycle_info. get_reward_cycle ( block_height) ;
1075
+ block_rc == current_reward_cycle
1076
+ } ) ;
1077
+
1078
+ if !is_fork_in_current_reward_cycle {
1079
+ info ! ( "Detected bitcoin fork occurred in previous reward cycle. Tx replay won't be executed" ) ;
1080
+ return Ok ( None ) ;
1081
+ }
1082
+
957
1083
// Collect transactions to be replayed across the forked blocks
958
1084
let mut forked_blocks = fork_info
959
1085
. iter ( )
0 commit comments