@@ -75,6 +75,22 @@ static GlobalMutex cs_blockchange;
7575static std::condition_variable cond_blockchange;
7676static CUpdatedBlock latestblock GUARDED_BY (cs_blockchange);
7777
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+
7894/* Calculate the difficulty for a given block index.
7995 */
8096double GetDifficulty (const CBlockIndex& blockindex)
@@ -2776,44 +2792,59 @@ static RPCHelpMan dumptxoutset()
27762792 // would be classified as a block connecting an invalid block.
27772793 disable_network = std::make_unique<NetworkDisable>(connman);
27782794
2779- // Note: Unlocking cs_main before CreateUTXOSnapshot might be racy
2780- // if the user interacts with any other *block RPCs.
27812795 invalidate_index = WITH_LOCK (::cs_main, return node.chainman ->ActiveChain ().Next (target_index));
27822796 InvalidateBlock (*node.chainman , invalidate_index->GetBlockHash ());
2783- const CBlockIndex* new_tip_index{ WITH_LOCK (::cs_main, return node. chainman -> ActiveChain (). Tip ())};
2797+ }
27842798
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 ();
27852813 // In case there is any issue with a block being read from disk we need
27862814 // to stop here, otherwise the dump could still be created for the wrong
27872815 // height.
27882816 // The new tip could also not be the target block if we have a stale
27892817 // sister block of invalidate_index. This block (or a descendant) would
27902818 // 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 );
27942824 }
27952825 }
27962826
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+ }
28012831 if (invalidate_index) {
28022832 ReconsiderBlock (*node.chainman , invalidate_index->GetBlockHash ());
28032833 }
2834+ if (!error.isNull ()) {
2835+ throw error;
2836+ }
28042837
28052838 result.pushKV (" path" , path.utf8string ());
28062839 return result;
28072840},
28082841 };
28092842}
28102843
2811- UniValue CreateUTXOSnapshot (
2812- NodeContext& node,
2844+ std::tuple<std::unique_ptr<CCoinsViewCursor>, CCoinsStats, const CBlockIndex*>
2845+ PrepareUTXOSnapshot (
28132846 Chainstate& chainstate,
2814- AutoFile& afile,
2815- const fs::path& path,
2816- const fs::path& temppath)
2847+ const std::function<void ()>& interruption_point)
28172848{
28182849 std::unique_ptr<CCoinsViewCursor> pcursor;
28192850 std::optional<CCoinsStats> maybe_stats;
@@ -2823,7 +2854,7 @@ UniValue CreateUTXOSnapshot(
28232854 // We need to lock cs_main to ensure that the coinsdb isn't written to
28242855 // between (i) flushing coins cache to disk (coinsdb), (ii) getting stats
28252856 // based upon the coinsdb, and (iii) constructing a cursor to the
2826- // coinsdb for use below this block .
2857+ // coinsdb for use in WriteUTXOSnapshot .
28272858 //
28282859 // Cursors returned by leveldb iterate over snapshots, so the contents
28292860 // of the pcursor will not be affected by simultaneous writes during
@@ -2832,11 +2863,11 @@ UniValue CreateUTXOSnapshot(
28322863 // See discussion here:
28332864 // https://github.com/bitcoin/bitcoin/pull/15606#discussion_r274479369
28342865 //
2835- LOCK (::cs_main);
2866+ AssertLockHeld (::cs_main);
28362867
28372868 chainstate.ForceFlushStateToDisk ();
28382869
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 );
28402871 if (!maybe_stats) {
28412872 throw JSONRPCError (RPC_INTERNAL_ERROR, " Unable to read UTXO set" );
28422873 }
@@ -2845,6 +2876,19 @@ UniValue CreateUTXOSnapshot(
28452876 tip = CHECK_NONFATAL (chainstate.m_blockman .LookupBlockIndex (maybe_stats->hashBlock ));
28462877 }
28472878
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+ {
28482892 LOG_TIME_SECONDS (strprintf (" writing UTXO snapshot at height %s (%s) to file %s (via %s)" ,
28492893 tip->nHeight , tip->GetBlockHash ().ToString (),
28502894 fs::PathToString (path), fs::PathToString (temppath)));
@@ -2880,7 +2924,7 @@ UniValue CreateUTXOSnapshot(
28802924 pcursor->GetKey (key);
28812925 last_hash = key.hash ;
28822926 while (pcursor->Valid ()) {
2883- if (iter % 5000 == 0 ) node. rpc_interruption_point ();
2927+ if (iter % 5000 == 0 ) interruption_point ();
28842928 ++iter;
28852929 if (pcursor->GetKey (key) && pcursor->GetValue (coin)) {
28862930 if (key.hash != last_hash) {
@@ -2911,6 +2955,17 @@ UniValue CreateUTXOSnapshot(
29112955 return result;
29122956}
29132957
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+
29142969static RPCHelpMan loadtxoutset ()
29152970{
29162971 return RPCHelpMan{
0 commit comments