@@ -18,6 +18,7 @@ use std::time::{Duration, UNIX_EPOCH};
18
18
19
19
use blockstack_lib:: chainstate:: burn:: ConsensusHashExtensions ;
20
20
use blockstack_lib:: chainstate:: nakamoto:: { NakamotoBlock , NakamotoBlockHeader } ;
21
+ use blockstack_lib:: chainstate:: stacks:: { StacksTransaction , TransactionPayload } ;
21
22
use clarity:: types:: chainstate:: StacksAddress ;
22
23
use libsigner:: v0:: messages:: {
23
24
MessageSlotID , SignerMessage , StateMachineUpdate as StateMachineUpdateMessage ,
@@ -151,6 +152,7 @@ impl GlobalStateEvaluator {
151
152
current_miner : current_miner. into ( ) ,
152
153
active_signer_protocol_version,
153
154
tx_replay_state : false ,
155
+ tx_replay_set : None ,
154
156
} ;
155
157
let entry = state_views
156
158
. entry ( state_machine. clone ( ) )
@@ -250,6 +252,8 @@ pub struct SignerStateMachine {
250
252
/// Whether or not we're in a tx replay state
251
253
/// TODO: just a placeholder for now
252
254
pub tx_replay_state : bool ,
255
+ /// Transaction replay set
256
+ pub tx_replay_set : Option < Vec < StacksTransaction > > ,
253
257
}
254
258
255
259
#[ derive( Debug , Clone , PartialEq , Serialize , Deserialize , Eq , Hash ) ]
@@ -324,7 +328,7 @@ pub enum StateMachineUpdate {
324
328
#[ derive( Debug , Clone , PartialEq , Serialize , Deserialize ) ]
325
329
pub struct NewBurnBlock {
326
330
/// The height of the new burn block
327
- pub height : u64 ,
331
+ pub burn_block_height : u64 ,
328
332
/// The hash of the new burn block
329
333
pub consensus_hash : ConsensusHash ,
330
334
}
@@ -389,6 +393,7 @@ impl LocalStateMachine {
389
393
current_miner : MinerState :: NoValidMiner ,
390
394
active_signer_protocol_version : SUPPORTED_SIGNER_PROTOCOL_VERSION ,
391
395
tx_replay_state : false ,
396
+ tx_replay_set : None ,
392
397
}
393
398
}
394
399
@@ -687,10 +692,10 @@ impl LocalStateMachine {
687
692
// TODO: test only, remove
688
693
match expected_burn_block. clone ( ) {
689
694
Some ( expected_burn_block) => {
690
- if expected_burn_block. height > 230 {
695
+ if expected_burn_block. burn_block_height > 230 {
691
696
info ! (
692
697
"---- bitcoin_block_arrival {} {} ----" ,
693
- expected_burn_block. height , expected_burn_block. consensus_hash
698
+ expected_burn_block. burn_block_height , expected_burn_block. consensus_hash
694
699
) ;
695
700
}
696
701
}
@@ -712,7 +717,8 @@ impl LocalStateMachine {
712
717
match expected_burn_block {
713
718
None => expected_burn_block = Some ( pending_burn_block) ,
714
719
Some ( ref expected) => {
715
- if pending_burn_block. height > expected. height {
720
+ if pending_burn_block. burn_block_height > expected. burn_block_height
721
+ {
716
722
expected_burn_block = Some ( pending_burn_block) ;
717
723
}
718
724
}
@@ -728,36 +734,80 @@ impl LocalStateMachine {
728
734
let next_burn_block_height = peer_info. burn_block_height ;
729
735
let next_burn_block_hash = peer_info. pox_consensus ;
730
736
let mut fork_detected = prior_state_machine. tx_replay_state ;
737
+ let mut tx_replay_set = prior_state_machine. tx_replay_set . clone ( ) ;
731
738
732
739
if let Some ( expected_burn_block) = expected_burn_block {
733
740
// If the next height is less than the expected height, we need to wait.
734
741
// OR if the next height is the same, but with a different hash, we need to wait.
735
- if next_burn_block_height < expected_burn_block. height || {
736
- next_burn_block_height == expected_burn_block. height
742
+ if next_burn_block_height < expected_burn_block. burn_block_height || {
743
+ next_burn_block_height == expected_burn_block. burn_block_height
737
744
&& next_burn_block_hash != expected_burn_block. consensus_hash
738
745
} {
739
746
let err_msg = format ! (
740
747
"Node has not processed the next burn block ({}) yet" ,
741
- expected_burn_block. height
748
+ expected_burn_block. burn_block_height
742
749
) ;
743
750
* self = Self :: Pending {
744
751
update : StateMachineUpdate :: BurnBlock ( expected_burn_block) ,
745
752
prior : prior_state_machine,
746
753
} ;
747
754
return Err ( ClientError :: InvalidResponse ( err_msg) . into ( ) ) ;
748
755
}
749
- if expected_burn_block. height <= prior_state_machine. burn_block_height
756
+ if expected_burn_block. burn_block_height <= prior_state_machine. burn_block_height
750
757
&& expected_burn_block. consensus_hash != prior_state_machine. burn_block
758
+ // TODO: handle fork while still in replay
759
+ && tx_replay_set. is_none ( )
751
760
{
752
761
fork_detected = true ;
753
762
info ! ( "---- Signer State: Possible fork! ----" ;
754
- "expected_burn_block.height" => expected_burn_block. height ,
763
+ "expected_burn_block.height" => expected_burn_block. burn_block_height ,
755
764
"expected_burn_block.hash" => %expected_burn_block. consensus_hash,
756
765
"next_burn_block_height" => next_burn_block_height,
757
766
"next_burn_block_hash" => %next_burn_block_hash,
758
767
"prior_state_machine.burn_block_height" => prior_state_machine. burn_block_height,
759
768
"prior_state_machine.burn_block" => %prior_state_machine. burn_block,
760
769
) ;
770
+ // Determine the tenures that were forked
771
+ let mut sortition_info =
772
+ client. get_sortition_by_consensus_hash ( & prior_state_machine. burn_block ) ?;
773
+ let last_forked_tenure = prior_state_machine. burn_block ;
774
+ let mut first_forked_tenure = prior_state_machine. burn_block ;
775
+ let mut forked_tenures = vec ! [ (
776
+ prior_state_machine. burn_block,
777
+ prior_state_machine. burn_block_height,
778
+ ) ] ;
779
+ while sortition_info. burn_block_height > expected_burn_block. burn_block_height {
780
+ let Some ( stacks_parent_ch) = sortition_info. stacks_parent_ch else {
781
+ info ! ( "No stacks parent ch found for sortition info" ;
782
+ "sortition_info" => ?sortition_info,
783
+ ) ;
784
+ break ;
785
+ } ;
786
+ sortition_info = client. get_sortition_by_consensus_hash ( & stacks_parent_ch) ?;
787
+ first_forked_tenure = sortition_info. consensus_hash ;
788
+ forked_tenures. push ( ( stacks_parent_ch, sortition_info. burn_block_height ) ) ;
789
+ }
790
+ let fork_info =
791
+ client. get_tenure_forking_info ( & first_forked_tenure, & last_forked_tenure) ?;
792
+ let forked_txs = fork_info
793
+ . iter ( )
794
+ . flat_map ( |fork_info| {
795
+ fork_info
796
+ . nakamoto_blocks
797
+ . iter ( )
798
+ . flat_map ( |blocks| blocks. iter ( ) )
799
+ . flat_map ( |block| block. txs . iter ( ) )
800
+ } )
801
+ . cloned ( )
802
+ . filter ( |tx| match tx. payload {
803
+ // Don't include Coinbase, TenureChange, or PoisonMicroblock transactions
804
+ TransactionPayload :: TenureChange ( ..)
805
+ | TransactionPayload :: Coinbase ( ..)
806
+ | TransactionPayload :: PoisonMicroblock ( ..) => false ,
807
+ _ => true ,
808
+ } )
809
+ . collect :: < Vec < _ > > ( ) ;
810
+ tx_replay_set = Some ( forked_txs) ;
761
811
}
762
812
}
763
813
@@ -802,6 +852,7 @@ impl LocalStateMachine {
802
852
current_miner : miner_state,
803
853
active_signer_protocol_version : prior_state_machine. active_signer_protocol_version ,
804
854
tx_replay_state : fork_detected,
855
+ tx_replay_set,
805
856
} ) ;
806
857
807
858
if prior_state != * self {
@@ -847,6 +898,7 @@ impl LocalStateMachine {
847
898
current_miner : current_miner. into ( ) ,
848
899
active_signer_protocol_version,
849
900
tx_replay_state : false ,
901
+ tx_replay_set : None ,
850
902
} ) ;
851
903
// Because we updated our active signer protocol version, update local_update so its included in the subsequent evaluations
852
904
let update: Result < StateMachineUpdateMessage , _ > = ( & * self ) . try_into ( ) ;
@@ -880,6 +932,7 @@ impl LocalStateMachine {
880
932
current_miner : ( & new_miner) . into ( ) ,
881
933
active_signer_protocol_version,
882
934
tx_replay_state : false ,
935
+ tx_replay_set : None ,
883
936
} ) ;
884
937
}
885
938
}
0 commit comments