@@ -55,6 +55,7 @@ use crate::chainstate::nakamoto::{
55
55
SortitionHandleConn , StacksDBIndexed ,
56
56
} ;
57
57
use crate :: chainstate:: stacks:: boot:: RewardSet ;
58
+ use crate :: chainstate:: stacks:: db:: blocks:: DummyEventDispatcher ;
58
59
use crate :: chainstate:: stacks:: db:: {
59
60
ChainstateTx , ClarityTx , StacksAccount , StacksChainState , StacksHeaderInfo ,
60
61
} ;
@@ -70,6 +71,7 @@ use crate::chainstate::stacks::{
70
71
use crate :: clarity:: vm:: types:: StacksAddressExtensions ;
71
72
use crate :: clarity_vm:: clarity:: ClarityInstance ;
72
73
use crate :: clarity_vm:: database:: SortitionDBRef ;
74
+ use crate :: net:: Error as NetError ;
73
75
use crate :: util_lib:: db:: { query_row, u64_to_sql, Error as DBError } ;
74
76
75
77
impl NakamotoBlockHeader {
@@ -461,7 +463,7 @@ impl NakamotoBlockBuilder {
461
463
}
462
464
463
465
/// Get an address's account
464
- fn get_account (
466
+ pub fn get_account (
465
467
chainstate : & mut StacksChainState ,
466
468
sortdb : & SortitionDB ,
467
469
addr : & StacksAddress ,
@@ -561,13 +563,17 @@ impl NakamotoBlockBuilder {
561
563
}
562
564
let block = builder. mine_nakamoto_block ( & mut tenure_tx) ;
563
565
let size = builder. bytes_so_far ;
564
- let cost = builder. tenure_finish ( tenure_tx) . unwrap ( ) ;
566
+ let cost = builder. tenure_finish ( tenure_tx) ? ;
565
567
Ok ( ( block, size, cost) )
566
568
}
567
569
568
570
/// Produce a single-block shadow tenure.
569
571
/// Used by tooling to synthesize shadow blocks in case of an emergency.
570
- /// The details and circumstances will be recorded in an accompanying SIP.
572
+ /// The details and circumatances will be recorded in an accompanying SIP.
573
+ ///
574
+ /// `naka_tip_id` is the Stacks chain tip on top of which the shadow block will be built.
575
+ /// `tenure_id_consensus_hash` is the sortition in which the shadow block will be built.
576
+ /// `txs` are transactions to include, beyond a coinbase and tenure-change
571
577
pub fn make_shadow_tenure (
572
578
chainstate : & mut StacksChainState ,
573
579
sortdb : & SortitionDB ,
@@ -705,8 +711,7 @@ impl NakamotoBlockBuilder {
705
711
Some ( & coinbase_tx) ,
706
712
1 ,
707
713
None ,
708
- )
709
- . unwrap ( ) ;
714
+ ) ?;
710
715
711
716
let mut block_txs = vec ! [ tenure_change_tx, coinbase_tx] ;
712
717
block_txs. append ( & mut txs) ;
@@ -853,3 +858,134 @@ impl<'a> NakamotoStagingBlocksTx<'a> {
853
858
Ok ( ( ) )
854
859
}
855
860
}
861
+
862
+ /// DO NOT RUN ON A RUNNING NODE (unless you're testing).
863
+ ///
864
+ /// Insert and process a shadow block into the Stacks chainstate.
865
+ pub fn process_shadow_block (
866
+ chain_state : & mut StacksChainState ,
867
+ sort_db : & mut SortitionDB ,
868
+ shadow_block : NakamotoBlock ,
869
+ ) -> Result < ( ) , ChainstateError > {
870
+ let tx = chain_state. staging_db_tx_begin ( ) ?;
871
+ tx. add_shadow_block ( & shadow_block) ?;
872
+ tx. commit ( ) ?;
873
+
874
+ let no_dispatch: Option < DummyEventDispatcher > = None ;
875
+ loop {
876
+ let sort_tip = SortitionDB :: get_canonical_burn_chain_tip ( sort_db. conn ( ) ) ?;
877
+
878
+ // process at most one block per loop pass
879
+ let processed_block_receipt = match NakamotoChainState :: process_next_nakamoto_block (
880
+ chain_state,
881
+ sort_db,
882
+ & sort_tip. sortition_id ,
883
+ no_dispatch. as_ref ( ) ,
884
+ ) {
885
+ Ok ( receipt_opt) => receipt_opt,
886
+ Err ( ChainstateError :: InvalidStacksBlock ( msg) ) => {
887
+ warn ! ( "Encountered invalid block: {}" , & msg) ;
888
+ continue ;
889
+ }
890
+ Err ( ChainstateError :: NetError ( NetError :: DeserializeError ( msg) ) ) => {
891
+ // happens if we load a zero-sized block (i.e. an invalid block)
892
+ warn ! ( "Encountered invalid block (codec error): {}" , & msg) ;
893
+ continue ;
894
+ }
895
+ Err ( e) => {
896
+ // something else happened
897
+ return Err ( e. into ( ) ) ;
898
+ }
899
+ } ;
900
+
901
+ if processed_block_receipt. is_none ( ) {
902
+ // out of blocks
903
+ info ! ( "No more blocks to process (no receipts)" ) ;
904
+ break ;
905
+ } ;
906
+
907
+ let Some ( ( _, processed, orphaned, _) ) = chain_state
908
+ . nakamoto_blocks_db ( )
909
+ . get_block_processed_and_signed_weight (
910
+ & shadow_block. header . consensus_hash ,
911
+ & shadow_block. header . block_hash ( ) ,
912
+ ) ?
913
+ else {
914
+ return Err ( ChainstateError :: InvalidStacksBlock ( format ! (
915
+ "Shadow block {} for tenure {} not store" ,
916
+ & shadow_block. block_id( ) ,
917
+ & shadow_block. header. consensus_hash
918
+ ) ) ) ;
919
+ } ;
920
+
921
+ if orphaned {
922
+ return Err ( ChainstateError :: InvalidStacksBlock ( format ! (
923
+ "Shadow block {} for tenure {} was orphaned" ,
924
+ & shadow_block. block_id( ) ,
925
+ & shadow_block. header. consensus_hash
926
+ ) ) ) ;
927
+ }
928
+
929
+ if processed {
930
+ break ;
931
+ }
932
+ }
933
+ Ok ( ( ) )
934
+ }
935
+
936
+ /// DO NOT RUN ON A RUNNING NODE (unless you're testing).
937
+ ///
938
+ /// Automatically repair a node that has been stalled due to an empty prepare phase.
939
+ /// Works by synthesizing, inserting, and processing shadow tenures in-between the last sortition
940
+ /// with a winner and the burnchain tip.
941
+ ///
942
+ /// This is meant to be accessed by the tooling. Once the blocks are synthesized, they would be
943
+ /// added into other broken nodes' chainstates by the same tooling. Ultimately, a patched node
944
+ /// would be released with these shadow blocks added in as part of the chainstate schema.
945
+ ///
946
+ /// Returns the syntheisized shadow blocks on success.
947
+ /// Returns error on failure.
948
+ pub fn shadow_chainstate_repair (
949
+ chain_state : & mut StacksChainState ,
950
+ sort_db : & mut SortitionDB ,
951
+ ) -> Result < Vec < NakamotoBlock > , ChainstateError > {
952
+ let sort_tip = SortitionDB :: get_canonical_burn_chain_tip ( sort_db. conn ( ) ) ?;
953
+
954
+ let header = NakamotoChainState :: get_canonical_block_header ( chain_state. db ( ) , & sort_db) ?
955
+ . ok_or_else ( || ChainstateError :: NoSuchBlockError ) ?;
956
+
957
+ let header_sn =
958
+ SortitionDB :: get_block_snapshot_consensus ( sort_db. conn ( ) , & header. consensus_hash ) ?
959
+ . ok_or_else ( || {
960
+ ChainstateError :: InvalidStacksBlock (
961
+ "Canonical stacks header does not have a sortition" . into ( ) ,
962
+ )
963
+ } ) ?;
964
+
965
+ let mut shadow_blocks = vec ! [ ] ;
966
+ for burn_height in ( header_sn. block_height + 1 ) ..sort_tip. block_height {
967
+ let sort_tip = SortitionDB :: get_canonical_burn_chain_tip ( sort_db. conn ( ) ) ?;
968
+ let sort_handle = sort_db. index_handle ( & sort_tip. sortition_id ) ;
969
+ let sn = sort_handle
970
+ . get_block_snapshot_by_height ( burn_height) ?
971
+ . ok_or_else ( || ChainstateError :: InvalidStacksBlock ( "No sortition at height" . into ( ) ) ) ?;
972
+
973
+ let header = NakamotoChainState :: get_canonical_block_header ( chain_state. db ( ) , & sort_db) ?
974
+ . ok_or_else ( || ChainstateError :: NoSuchBlockError ) ?;
975
+
976
+ let chain_tip = header. index_block_hash ( ) ;
977
+ let shadow_block = NakamotoBlockBuilder :: make_shadow_tenure (
978
+ chain_state,
979
+ sort_db,
980
+ chain_tip. clone ( ) ,
981
+ sn. consensus_hash ,
982
+ vec ! [ ] ,
983
+ ) ?;
984
+
985
+ shadow_blocks. push ( shadow_block. clone ( ) ) ;
986
+
987
+ process_shadow_block ( chain_state, sort_db, shadow_block) ?;
988
+ }
989
+
990
+ Ok ( shadow_blocks)
991
+ }
0 commit comments