@@ -2648,6 +2648,25 @@ static RPCHelpMan getblockfilter()
2648
2648
};
2649
2649
}
2650
2650
2651
+ /* *
2652
+ * RAII class that disables the network in its constructor and enables it in its
2653
+ * destructor.
2654
+ */
2655
+ class NetworkDisable
2656
+ {
2657
+ CConnman& m_connman;
2658
+ public:
2659
+ NetworkDisable (CConnman& connman) : m_connman(connman) {
2660
+ m_connman.SetNetworkActive (false );
2661
+ if (m_connman.GetNetworkActive ()) {
2662
+ throw JSONRPCError (RPC_MISC_ERROR, " Network activity could not be suspended." );
2663
+ }
2664
+ };
2665
+ ~NetworkDisable () {
2666
+ m_connman.SetNetworkActive (true );
2667
+ };
2668
+ };
2669
+
2651
2670
/* *
2652
2671
* Serialize the UTXO set to a file for loading elsewhere.
2653
2672
*
@@ -2660,6 +2679,14 @@ static RPCHelpMan dumptxoutset()
2660
2679
" Write the serialized UTXO set to a file." ,
2661
2680
{
2662
2681
{" path" , RPCArg::Type::STR, RPCArg::Optional::NO, " Path to the output file. If relative, will be prefixed by datadir." },
2682
+ {" type" , RPCArg::Type::STR, RPCArg::Default (" " ), " The type of snapshot to create. Can be \" latest\" to create a snapshot of the current UTXO set or \" rollback\" to temporarily roll back the state of the node to a historical block before creating the snapshot of a historical UTXO set. This parameter can be omitted if a separate \" rollback\" named parameter is specified indicating the height or hash of a specific historical block. If \" rollback\" is specified and separate \" rollback\" named parameter is not specified, this will roll back to the latest valid snapshot block that currently be loaded with loadtxoutset." },
2683
+ {" options" , RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, " " ,
2684
+ {
2685
+ {" rollback" , RPCArg::Type::NUM, RPCArg::Optional::OMITTED,
2686
+ " Height or hash of the block to roll back to before creating the snapshot. Note: The further this number is from the tip, the longer this process will take. Consider setting a higher -rpcclienttimeout value in this case." ,
2687
+ RPCArgOptions{.skip_type_check = true , .type_str = {" " , " string or numeric" }}},
2688
+ },
2689
+ },
2663
2690
},
2664
2691
RPCResult{
2665
2692
RPCResult::Type::OBJ, " " , " " ,
@@ -2673,10 +2700,33 @@ static RPCHelpMan dumptxoutset()
2673
2700
}
2674
2701
},
2675
2702
RPCExamples{
2676
- HelpExampleCli (" dumptxoutset" , " utxo.dat" )
2703
+ HelpExampleCli (" -rpcclienttimeout=0 dumptxoutset" , " utxo.dat latest" ) +
2704
+ HelpExampleCli (" -rpcclienttimeout=0 dumptxoutset" , " utxo.dat rollback" ) +
2705
+ HelpExampleCli (" -rpcclienttimeout=0 -named dumptxoutset" , R"( utxo.dat rollback=853456)" )
2677
2706
},
2678
2707
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
2679
2708
{
2709
+ NodeContext& node = EnsureAnyNodeContext (request.context );
2710
+ const CBlockIndex* tip{WITH_LOCK (::cs_main, return node.chainman ->ActiveChain ().Tip ())};
2711
+ const CBlockIndex* target_index{nullptr };
2712
+ const std::string snapshot_type{self.Arg <std::string>(" type" )};
2713
+ const UniValue options{request.params [2 ].isNull () ? UniValue::VOBJ : request.params [2 ]};
2714
+ if (options.exists (" rollback" )) {
2715
+ if (!snapshot_type.empty () && snapshot_type != " rollback" ) {
2716
+ throw JSONRPCError (RPC_INVALID_PARAMETER, strprintf (" Invalid snapshot type \" %s\" specified with rollback option" , snapshot_type));
2717
+ }
2718
+ target_index = ParseHashOrHeight (options[" rollback" ], *node.chainman );
2719
+ } else if (snapshot_type == " rollback" ) {
2720
+ auto snapshot_heights = node.chainman ->GetParams ().GetAvailableSnapshotHeights ();
2721
+ CHECK_NONFATAL (snapshot_heights.size () > 0 );
2722
+ auto max_height = std::max_element (snapshot_heights.begin (), snapshot_heights.end ());
2723
+ target_index = ParseHashOrHeight (*max_height, *node.chainman );
2724
+ } else if (snapshot_type == " latest" ) {
2725
+ target_index = tip;
2726
+ } else {
2727
+ throw JSONRPCError (RPC_INVALID_PARAMETER, strprintf (" Invalid snapshot type \" %s\" specified. Please specify \" rollback\" or \" latest\" " , snapshot_type));
2728
+ }
2729
+
2680
2730
const ArgsManager& args{EnsureAnyArgsman (request.context )};
2681
2731
const fs::path path = fsbridge::AbsPathJoin (args.GetDataDirNet (), fs::u8path (request.params [0 ].get_str ()));
2682
2732
// Write to a temporary path and then move into `path` on completion
@@ -2698,11 +2748,55 @@ static RPCHelpMan dumptxoutset()
2698
2748
" Couldn't open file " + temppath.utf8string () + " for writing." );
2699
2749
}
2700
2750
2701
- NodeContext& node = EnsureAnyNodeContext (request.context );
2751
+ CConnman& connman = EnsureConnman (node);
2752
+ const CBlockIndex* invalidate_index{nullptr };
2753
+ std::unique_ptr<NetworkDisable> disable_network;
2754
+
2755
+ // If the user wants to dump the txoutset of the current tip, we don't have
2756
+ // to roll back at all
2757
+ if (target_index != tip) {
2758
+ // If the node is running in pruned mode we ensure all necessary block
2759
+ // data is available before starting to roll back.
2760
+ if (node.chainman ->m_blockman .IsPruneMode ()) {
2761
+ LOCK (node.chainman ->GetMutex ());
2762
+ const CBlockIndex* current_tip{node.chainman ->ActiveChain ().Tip ()};
2763
+ const CBlockIndex* first_block{node.chainman ->m_blockman .GetFirstBlock (*current_tip, /* status_mask=*/ BLOCK_HAVE_MASK)};
2764
+ if (first_block->nHeight > target_index->nHeight ) {
2765
+ throw JSONRPCError (RPC_MISC_ERROR, " Could not roll back to requested height since necessary block data is already pruned." );
2766
+ }
2767
+ }
2768
+
2769
+ // Suspend network activity for the duration of the process when we are
2770
+ // rolling back the chain to get a utxo set from a past height. We do
2771
+ // this so we don't punish peers that send us that send us data that
2772
+ // seems wrong in this temporary state. For example a normal new block
2773
+ // would be classified as a block connecting an invalid block.
2774
+ disable_network = std::make_unique<NetworkDisable>(connman);
2775
+
2776
+ invalidate_index = WITH_LOCK (::cs_main, return node.chainman ->ActiveChain ().Next (target_index));
2777
+ InvalidateBlock (*node.chainman , invalidate_index->GetBlockHash ());
2778
+ const CBlockIndex* new_tip_index{WITH_LOCK (::cs_main, return node.chainman ->ActiveChain ().Tip ())};
2779
+
2780
+ // In case there is any issue with a block being read from disk we need
2781
+ // to stop here, otherwise the dump could still be created for the wrong
2782
+ // height.
2783
+ // The new tip could also not be the target block if we have a stale
2784
+ // sister block of invalidate_index. This block (or a descendant) would
2785
+ // be activated as the new tip and we would not get to new_tip_index.
2786
+ if (new_tip_index != target_index) {
2787
+ ReconsiderBlock (*node.chainman , invalidate_index->GetBlockHash ());
2788
+ throw JSONRPCError (RPC_MISC_ERROR, " Could not roll back to requested height, reverting to tip." );
2789
+ }
2790
+ }
2791
+
2702
2792
UniValue result = CreateUTXOSnapshot (
2703
2793
node, node.chainman ->ActiveChainstate (), afile, path, temppath);
2704
2794
fs::rename (temppath, path);
2705
2795
2796
+ if (invalidate_index) {
2797
+ ReconsiderBlock (*node.chainman , invalidate_index->GetBlockHash ());
2798
+ }
2799
+
2706
2800
result.pushKV (" path" , path.utf8string ());
2707
2801
return result;
2708
2802
},
0 commit comments