@@ -75,6 +75,22 @@ static GlobalMutex cs_blockchange;
75
75
static std::condition_variable cond_blockchange;
76
76
static CUpdatedBlock latestblock GUARDED_BY (cs_blockchange);
77
77
78
+ std::tuple<std::unique_ptr<CCoinsViewCursor>, CCoinsStats, const CBlockIndex*>
79
+ PrepareUTXOSnapshot (
80
+ Chainstate& chainstate,
81
+ const std::function<void ()>& interruption_point = {})
82
+ EXCLUSIVE_LOCKS_REQUIRED (::cs_main);
83
+
84
+ UniValue WriteUTXOSnapshot (
85
+ Chainstate& chainstate,
86
+ CCoinsViewCursor* pcursor,
87
+ CCoinsStats* maybe_stats,
88
+ const CBlockIndex* tip,
89
+ AutoFile& afile,
90
+ const fs::path& path,
91
+ const fs::path& temppath,
92
+ const std::function<void ()>& interruption_point = {});
93
+
78
94
/* Calculate the difficulty for a given block index.
79
95
*/
80
96
double GetDifficulty (const CBlockIndex& blockindex)
@@ -2776,44 +2792,59 @@ static RPCHelpMan dumptxoutset()
2776
2792
// would be classified as a block connecting an invalid block.
2777
2793
disable_network = std::make_unique<NetworkDisable>(connman);
2778
2794
2779
- // Note: Unlocking cs_main before CreateUTXOSnapshot might be racy
2780
- // if the user interacts with any other *block RPCs.
2781
2795
invalidate_index = WITH_LOCK (::cs_main, return node.chainman ->ActiveChain ().Next (target_index));
2782
2796
InvalidateBlock (*node.chainman , invalidate_index->GetBlockHash ());
2783
- const CBlockIndex* new_tip_index{ WITH_LOCK (::cs_main, return node. chainman -> ActiveChain (). Tip ())};
2797
+ }
2784
2798
2799
+ Chainstate* chainstate;
2800
+ std::unique_ptr<CCoinsViewCursor> cursor;
2801
+ CCoinsStats stats;
2802
+ UniValue result;
2803
+ UniValue error;
2804
+ {
2805
+ // Lock the chainstate before calling PrepareUtxoSnapshot, to be able
2806
+ // to get a UTXO database cursor while the chain is pointing at the
2807
+ // target block. After that, release the lock while calling
2808
+ // WriteUTXOSnapshot. The cursor will remain valid and be used by
2809
+ // WriteUTXOSnapshot to write a consistent snapshot even if the
2810
+ // chainstate changes.
2811
+ LOCK (node.chainman ->GetMutex ());
2812
+ chainstate = &node.chainman ->ActiveChainstate ();
2785
2813
// In case there is any issue with a block being read from disk we need
2786
2814
// to stop here, otherwise the dump could still be created for the wrong
2787
2815
// height.
2788
2816
// The new tip could also not be the target block if we have a stale
2789
2817
// sister block of invalidate_index. This block (or a descendant) would
2790
2818
// be activated as the new tip and we would not get to new_tip_index.
2791
- if (new_tip_index != target_index) {
2792
- ReconsiderBlock (*node.chainman , invalidate_index->GetBlockHash ());
2793
- throw JSONRPCError (RPC_MISC_ERROR, " Could not roll back to requested height, reverting to tip." );
2819
+ if (target_index != chainstate->m_chain .Tip ()) {
2820
+ LogInfo (" Failed to roll back to requested height, reverting to tip.\n " );
2821
+ error = JSONRPCError (RPC_MISC_ERROR, " Could not roll back to requested height." );
2822
+ } else {
2823
+ std::tie (cursor, stats, tip) = PrepareUTXOSnapshot (*chainstate, node.rpc_interruption_point );
2794
2824
}
2795
2825
}
2796
2826
2797
- UniValue result = CreateUTXOSnapshot (
2798
- node, node. chainman -> ActiveChainstate (), afile, path, temppath);
2799
- fs::rename (temppath, path);
2800
-
2827
+ if (error. isNull ()) {
2828
+ result = WriteUTXOSnapshot (*chainstate, cursor. get (), &stats, tip, afile, path, temppath, node. rpc_interruption_point );
2829
+ fs::rename (temppath, path);
2830
+ }
2801
2831
if (invalidate_index) {
2802
2832
ReconsiderBlock (*node.chainman , invalidate_index->GetBlockHash ());
2803
2833
}
2834
+ if (!error.isNull ()) {
2835
+ throw error;
2836
+ }
2804
2837
2805
2838
result.pushKV (" path" , path.utf8string ());
2806
2839
return result;
2807
2840
},
2808
2841
};
2809
2842
}
2810
2843
2811
- UniValue CreateUTXOSnapshot (
2812
- NodeContext& node,
2844
+ std::tuple<std::unique_ptr<CCoinsViewCursor>, CCoinsStats, const CBlockIndex*>
2845
+ PrepareUTXOSnapshot (
2813
2846
Chainstate& chainstate,
2814
- AutoFile& afile,
2815
- const fs::path& path,
2816
- const fs::path& temppath)
2847
+ const std::function<void ()>& interruption_point)
2817
2848
{
2818
2849
std::unique_ptr<CCoinsViewCursor> pcursor;
2819
2850
std::optional<CCoinsStats> maybe_stats;
@@ -2823,7 +2854,7 @@ UniValue CreateUTXOSnapshot(
2823
2854
// We need to lock cs_main to ensure that the coinsdb isn't written to
2824
2855
// between (i) flushing coins cache to disk (coinsdb), (ii) getting stats
2825
2856
// based upon the coinsdb, and (iii) constructing a cursor to the
2826
- // coinsdb for use below this block .
2857
+ // coinsdb for use in WriteUTXOSnapshot .
2827
2858
//
2828
2859
// Cursors returned by leveldb iterate over snapshots, so the contents
2829
2860
// of the pcursor will not be affected by simultaneous writes during
@@ -2832,11 +2863,11 @@ UniValue CreateUTXOSnapshot(
2832
2863
// See discussion here:
2833
2864
// https://github.com/bitcoin/bitcoin/pull/15606#discussion_r274479369
2834
2865
//
2835
- LOCK (::cs_main);
2866
+ AssertLockHeld (::cs_main);
2836
2867
2837
2868
chainstate.ForceFlushStateToDisk ();
2838
2869
2839
- maybe_stats = GetUTXOStats (&chainstate.CoinsDB (), chainstate.m_blockman , CoinStatsHashType::HASH_SERIALIZED, node. rpc_interruption_point );
2870
+ maybe_stats = GetUTXOStats (&chainstate.CoinsDB (), chainstate.m_blockman , CoinStatsHashType::HASH_SERIALIZED, interruption_point );
2840
2871
if (!maybe_stats) {
2841
2872
throw JSONRPCError (RPC_INTERNAL_ERROR, " Unable to read UTXO set" );
2842
2873
}
@@ -2845,6 +2876,19 @@ UniValue CreateUTXOSnapshot(
2845
2876
tip = CHECK_NONFATAL (chainstate.m_blockman .LookupBlockIndex (maybe_stats->hashBlock ));
2846
2877
}
2847
2878
2879
+ return {std::move (pcursor), *CHECK_NONFATAL (maybe_stats), tip};
2880
+ }
2881
+
2882
+ UniValue WriteUTXOSnapshot (
2883
+ Chainstate& chainstate,
2884
+ CCoinsViewCursor* pcursor,
2885
+ CCoinsStats* maybe_stats,
2886
+ const CBlockIndex* tip,
2887
+ AutoFile& afile,
2888
+ const fs::path& path,
2889
+ const fs::path& temppath,
2890
+ const std::function<void ()>& interruption_point)
2891
+ {
2848
2892
LOG_TIME_SECONDS (strprintf (" writing UTXO snapshot at height %s (%s) to file %s (via %s)" ,
2849
2893
tip->nHeight , tip->GetBlockHash ().ToString (),
2850
2894
fs::PathToString (path), fs::PathToString (temppath)));
@@ -2880,7 +2924,7 @@ UniValue CreateUTXOSnapshot(
2880
2924
pcursor->GetKey (key);
2881
2925
last_hash = key.hash ;
2882
2926
while (pcursor->Valid ()) {
2883
- if (iter % 5000 == 0 ) node. rpc_interruption_point ();
2927
+ if (iter % 5000 == 0 ) interruption_point ();
2884
2928
++iter;
2885
2929
if (pcursor->GetKey (key) && pcursor->GetValue (coin)) {
2886
2930
if (key.hash != last_hash) {
@@ -2911,6 +2955,17 @@ UniValue CreateUTXOSnapshot(
2911
2955
return result;
2912
2956
}
2913
2957
2958
+ UniValue CreateUTXOSnapshot (
2959
+ node::NodeContext& node,
2960
+ Chainstate& chainstate,
2961
+ AutoFile& afile,
2962
+ const fs::path& path,
2963
+ const fs::path& tmppath)
2964
+ {
2965
+ auto [cursor, stats, tip]{WITH_LOCK (::cs_main, return PrepareUTXOSnapshot (chainstate, node.rpc_interruption_point ))};
2966
+ return WriteUTXOSnapshot (chainstate, cursor.get (), &stats, tip, afile, path, tmppath, node.rpc_interruption_point );
2967
+ }
2968
+
2914
2969
static RPCHelpMan loadtxoutset ()
2915
2970
{
2916
2971
return RPCHelpMan{
0 commit comments