Skip to content

Commit 80315c0

Browse files
committed
refactor: Move early loadtxoutset checks into ActiveSnapshot
Also changes the return type of ActiveSnapshot to allow returning the error message to the user of the loadtxoutset RPC.
1 parent 9c5cdf0 commit 80315c0

File tree

6 files changed

+47
-53
lines changed

6 files changed

+47
-53
lines changed

src/rpc/blockchain.cpp

Lines changed: 5 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,7 @@ using kernel::CoinStatsHashType;
6262
using node::BlockManager;
6363
using node::NodeContext;
6464
using node::SnapshotMetadata;
65-
using util::Join;
6665
using util::MakeUnorderedList;
67-
using util::ToString;
6866

6967
struct CUpdatedBlock
7068
{
@@ -2821,34 +2819,15 @@ static RPCHelpMan loadtxoutset()
28212819
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("Unable to parse metadata: %s", e.what()));
28222820
}
28232821

2824-
uint256 base_blockhash = metadata.m_base_blockhash;
2825-
int base_blockheight = metadata.m_base_blockheight;
2826-
if (!chainman.GetParams().AssumeutxoForBlockhash(base_blockhash).has_value()) {
2827-
auto available_heights = chainman.GetParams().GetAvailableSnapshotHeights();
2828-
std::string heights_formatted = Join(available_heights, ", ", [&](const auto& i) { return ToString(i); });
2829-
throw JSONRPCError(RPC_INTERNAL_ERROR, strprintf("Unable to load UTXO snapshot, "
2830-
"assumeutxo block hash in snapshot metadata not recognized (hash: %s, height: %s). The following snapshot heights are available: %s.",
2831-
base_blockhash.ToString(),
2832-
base_blockheight,
2833-
heights_formatted));
2834-
}
2835-
CBlockIndex* snapshot_start_block = WITH_LOCK(::cs_main,
2836-
return chainman.m_blockman.LookupBlockIndex(base_blockhash));
2837-
2838-
if (!snapshot_start_block) {
2839-
throw JSONRPCError(
2840-
RPC_INTERNAL_ERROR,
2841-
strprintf("The base block header (%s) must appear in the headers chain. Make sure all headers are syncing, and call this RPC again.",
2842-
base_blockhash.ToString()));
2843-
}
2844-
if (!chainman.ActivateSnapshot(afile, metadata, false)) {
2845-
throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to load UTXO snapshot " + fs::PathToString(path));
2822+
auto activation_result{chainman.ActivateSnapshot(afile, metadata, false)};
2823+
if (!activation_result) {
2824+
throw JSONRPCError(RPC_INTERNAL_ERROR, strprintf(_("Unable to load UTXO snapshot: %s\n"), util::ErrorString(activation_result)).original);
28462825
}
28472826

28482827
UniValue result(UniValue::VOBJ);
28492828
result.pushKV("coins_loaded", metadata.m_coins_count);
2850-
result.pushKV("tip_hash", snapshot_start_block->GetBlockHash().ToString());
2851-
result.pushKV("base_height", snapshot_start_block->nHeight);
2829+
result.pushKV("tip_hash", metadata.m_base_blockhash.ToString());
2830+
result.pushKV("base_height", metadata.m_base_blockheight);
28522831
result.pushKV("path", fs::PathToString(path));
28532832
return result;
28542833
},

src/test/fuzz/utxo_snapshot.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ FUZZ_TARGET(utxo_snapshot, .init = initialize_chain)
5454
} catch (const std::ios_base::failure&) {
5555
return false;
5656
}
57-
return chainman.ActivateSnapshot(infile, metadata, /*in_memory=*/true);
57+
return !!chainman.ActivateSnapshot(infile, metadata, /*in_memory=*/true);
5858
}};
5959

6060
if (fuzzed_data_provider.ConsumeBool()) {

src/test/util/chainstate.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,11 +124,11 @@ CreateAndActivateUTXOSnapshot(
124124
new_active.m_chain.SetTip(*(tip->pprev));
125125
}
126126

127-
bool res = node.chainman->ActivateSnapshot(auto_infile, metadata, in_memory_chainstate);
127+
auto res = node.chainman->ActivateSnapshot(auto_infile, metadata, in_memory_chainstate);
128128

129129
// Restore the old tip.
130130
new_active.m_chain.SetTip(*tip);
131-
return res;
131+
return !!res;
132132
}
133133

134134

src/validation.cpp

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5647,23 +5647,38 @@ Chainstate& ChainstateManager::InitializeChainstate(CTxMemPool* mempool)
56475647
return destroyed && !fs::exists(db_path);
56485648
}
56495649

5650-
bool ChainstateManager::ActivateSnapshot(
5650+
util::Result<void> ChainstateManager::ActivateSnapshot(
56515651
AutoFile& coins_file,
56525652
const SnapshotMetadata& metadata,
56535653
bool in_memory)
56545654
{
56555655
uint256 base_blockhash = metadata.m_base_blockhash;
5656+
int base_blockheight = metadata.m_base_blockheight;
56565657

56575658
if (this->SnapshotBlockhash()) {
5658-
LogPrintf("[snapshot] can't activate a snapshot-based chainstate more than once\n");
5659-
return false;
5659+
return util::Error{_("Can't activate a snapshot-based chainstate more than once")};
56605660
}
56615661

56625662
{
56635663
LOCK(::cs_main);
5664+
5665+
if (!GetParams().AssumeutxoForBlockhash(base_blockhash).has_value()) {
5666+
auto available_heights = GetParams().GetAvailableSnapshotHeights();
5667+
std::string heights_formatted = util::Join(available_heights, ", ", [&](const auto& i) { return util::ToString(i); });
5668+
return util::Error{strprintf(_("assumeutxo block hash in snapshot metadata not recognized (hash: %s, height: %s). The following snapshot heights are available: %s."),
5669+
base_blockhash.ToString(),
5670+
base_blockheight,
5671+
heights_formatted)};
5672+
}
5673+
5674+
CBlockIndex* snapshot_start_block = m_blockman.LookupBlockIndex(base_blockhash);
5675+
if (!snapshot_start_block) {
5676+
return util::Error{strprintf(_("The base block header (%s) must appear in the headers chain. Make sure all headers are syncing, and call loadtxoutset again."),
5677+
base_blockhash.ToString())};
5678+
}
5679+
56645680
if (Assert(m_active_chainstate->GetMempool())->size() > 0) {
5665-
LogPrintf("[snapshot] can't activate a snapshot when mempool not empty\n");
5666-
return false;
5681+
return util::Error{_("Can't activate a snapshot when mempool not empty.")};
56675682
}
56685683
}
56695684

@@ -5713,7 +5728,6 @@ bool ChainstateManager::ActivateSnapshot(
57135728
}
57145729

57155730
auto cleanup_bad_snapshot = [&](const char* reason) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
5716-
LogPrintf("[snapshot] activation failed - %s\n", reason);
57175731
this->MaybeRebalanceCaches();
57185732

57195733
// PopulateAndValidateSnapshot can return (in error) before the leveldb datadir
@@ -5729,7 +5743,7 @@ bool ChainstateManager::ActivateSnapshot(
57295743
"Manually remove it before restarting.\n"), fs::PathToString(*snapshot_datadir)));
57305744
}
57315745
}
5732-
return false;
5746+
return util::Error{_(reason)};
57335747
};
57345748

57355749
if (!this->PopulateAndValidateSnapshot(*snapshot_chainstate, coins_file, metadata)) {
@@ -5772,7 +5786,7 @@ bool ChainstateManager::ActivateSnapshot(
57725786
m_snapshot_chainstate->CoinsTip().DynamicMemoryUsage() / (1000 * 1000));
57735787

57745788
this->MaybeRebalanceCaches();
5775-
return true;
5789+
return {};
57765790
}
57775791

57785792
static void FlushSnapshotToDisk(CCoinsViewCache& coins_cache, bool snapshot_loaded)

src/validation.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1054,7 +1054,7 @@ class ChainstateManager
10541054
//! faking nTx* block index data along the way.
10551055
//! - Move the new chainstate to `m_snapshot_chainstate` and make it our
10561056
//! ChainstateActive().
1057-
[[nodiscard]] bool ActivateSnapshot(
1057+
[[nodiscard]] util::Result<void> ActivateSnapshot(
10581058
AutoFile& coins_file, const node::SnapshotMetadata& metadata, bool in_memory);
10591059

10601060
//! Once the background validation chainstate has reached the height which

test/functional/feature_assumeutxo.py

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -70,23 +70,24 @@ def test_invalid_snapshot_scenarios(self, valid_snapshot_path):
7070
with open(valid_snapshot_path, 'rb') as f:
7171
valid_snapshot_contents = f.read()
7272
bad_snapshot_path = valid_snapshot_path + '.mod'
73+
node = self.nodes[1]
7374

7475
def expected_error(log_msg="", rpc_details=""):
75-
with self.nodes[1].assert_debug_log([log_msg]):
76-
assert_raises_rpc_error(-32603, f"Unable to load UTXO snapshot{rpc_details}", self.nodes[1].loadtxoutset, bad_snapshot_path)
76+
with node.assert_debug_log([log_msg]):
77+
assert_raises_rpc_error(-32603, f"Unable to load UTXO snapshot{rpc_details}", node.loadtxoutset, bad_snapshot_path)
7778

7879
self.log.info(" - snapshot file with invalid file magic")
7980
parsing_error_code = -22
8081
bad_magic = 0xf00f00f000
8182
with open(bad_snapshot_path, 'wb') as f:
8283
f.write(bad_magic.to_bytes(5, "big") + valid_snapshot_contents[5:])
83-
assert_raises_rpc_error(parsing_error_code, "Unable to parse metadata: Invalid UTXO set snapshot magic bytes. Please check if this is indeed a snapshot file or if you are using an outdated snapshot format.", self.nodes[1].loadtxoutset, bad_snapshot_path)
84+
assert_raises_rpc_error(parsing_error_code, "Unable to parse metadata: Invalid UTXO set snapshot magic bytes. Please check if this is indeed a snapshot file or if you are using an outdated snapshot format.", node.loadtxoutset, bad_snapshot_path)
8485

8586
self.log.info(" - snapshot file with unsupported version")
8687
for version in [0, 2]:
8788
with open(bad_snapshot_path, 'wb') as f:
8889
f.write(valid_snapshot_contents[:5] + version.to_bytes(2, "little") + valid_snapshot_contents[7:])
89-
assert_raises_rpc_error(parsing_error_code, f"Unable to parse metadata: Version of snapshot {version} does not match any of the supported versions.", self.nodes[1].loadtxoutset, bad_snapshot_path)
90+
assert_raises_rpc_error(parsing_error_code, f"Unable to parse metadata: Version of snapshot {version} does not match any of the supported versions.", node.loadtxoutset, bad_snapshot_path)
9091

9192
self.log.info(" - snapshot file with mismatching network magic")
9293
invalid_magics = [
@@ -101,9 +102,9 @@ def expected_error(log_msg="", rpc_details=""):
101102
with open(bad_snapshot_path, 'wb') as f:
102103
f.write(valid_snapshot_contents[:7] + magic.to_bytes(4, 'big') + valid_snapshot_contents[11:])
103104
if real:
104-
assert_raises_rpc_error(parsing_error_code, f"Unable to parse metadata: The network of the snapshot ({name}) does not match the network of this node (regtest).", self.nodes[1].loadtxoutset, bad_snapshot_path)
105+
assert_raises_rpc_error(parsing_error_code, f"Unable to parse metadata: The network of the snapshot ({name}) does not match the network of this node (regtest).", node.loadtxoutset, bad_snapshot_path)
105106
else:
106-
assert_raises_rpc_error(parsing_error_code, "Unable to parse metadata: This snapshot has been created for an unrecognized network. This could be a custom signet, a new testnet or possibly caused by data corruption.", self.nodes[1].loadtxoutset, bad_snapshot_path)
107+
assert_raises_rpc_error(parsing_error_code, "Unable to parse metadata: This snapshot has been created for an unrecognized network. This could be a custom signet, a new testnet or possibly caused by data corruption.", node.loadtxoutset, bad_snapshot_path)
107108

108109
self.log.info(" - snapshot file referring to a block that is not in the assumeutxo parameters")
109110
prev_block_hash = self.nodes[0].getblockhash(SNAPSHOT_BASE_HEIGHT - 1)
@@ -114,8 +115,9 @@ def expected_error(log_msg="", rpc_details=""):
114115
for bad_block_hash in [bogus_block_hash, prev_block_hash]:
115116
with open(bad_snapshot_path, 'wb') as f:
116117
f.write(valid_snapshot_contents[:11] + bogus_height.to_bytes(4, "little") + bytes.fromhex(bad_block_hash)[::-1] + valid_snapshot_contents[47:])
117-
error_details = f", assumeutxo block hash in snapshot metadata not recognized (hash: {bad_block_hash}, height: {bogus_height}). The following snapshot heights are available: 110, 299."
118-
expected_error(rpc_details=error_details)
118+
119+
msg = f"Unable to load UTXO snapshot: assumeutxo block hash in snapshot metadata not recognized (hash: {bad_block_hash}, height: {bogus_height}). The following snapshot heights are available: 110, 299."
120+
assert_raises_rpc_error(-32603, msg, node.loadtxoutset, bad_snapshot_path)
119121

120122
self.log.info(" - snapshot file with wrong number of coins")
121123
valid_num_coins = int.from_bytes(valid_snapshot_contents[47:47 + 8], "little")
@@ -151,9 +153,8 @@ def expected_error(log_msg="", rpc_details=""):
151153

152154
def test_headers_not_synced(self, valid_snapshot_path):
153155
for node in self.nodes[1:]:
154-
assert_raises_rpc_error(-32603, "The base block header (3bb7ce5eba0be48939b7a521ac1ba9316afee2c7bada3a0cca24188e6d7d96c0) must appear in the headers chain. Make sure all headers are syncing, and call this RPC again.",
155-
node.loadtxoutset,
156-
valid_snapshot_path)
156+
msg = "Unable to load UTXO snapshot: The base block header (3bb7ce5eba0be48939b7a521ac1ba9316afee2c7bada3a0cca24188e6d7d96c0) must appear in the headers chain. Make sure all headers are syncing, and call loadtxoutset again."
157+
assert_raises_rpc_error(-32603, msg, node.loadtxoutset, valid_snapshot_path)
157158

158159
def test_invalid_chainstate_scenarios(self):
159160
self.log.info("Test different scenarios of invalid snapshot chainstate in datadir")
@@ -185,8 +186,8 @@ def test_invalid_mempool_state(self, dump_output_path):
185186
assert tx['txid'] in node.getrawmempool()
186187

187188
# Attempt to load the snapshot on Node 2 and expect it to fail
188-
with node.assert_debug_log(expected_msgs=["[snapshot] can't activate a snapshot when mempool not empty"]):
189-
assert_raises_rpc_error(-32603, "Unable to load UTXO snapshot", node.loadtxoutset, dump_output_path)
189+
msg = "Unable to load UTXO snapshot: Can't activate a snapshot when mempool not empty"
190+
assert_raises_rpc_error(-32603, msg, node.loadtxoutset, dump_output_path)
190191

191192
self.restart_node(2, extra_args=self.extra_args[2])
192193

@@ -450,8 +451,8 @@ def check_tx_counts(final: bool) -> None:
450451
assert_equal(snapshot['validated'], False)
451452

452453
self.log.info("Check that loading the snapshot again will fail because there is already an active snapshot.")
453-
with n2.assert_debug_log(expected_msgs=["[snapshot] can't activate a snapshot-based chainstate more than once"]):
454-
assert_raises_rpc_error(-32603, "Unable to load UTXO snapshot", n2.loadtxoutset, dump_output['path'])
454+
msg = "Unable to load UTXO snapshot: Can't activate a snapshot-based chainstate more than once"
455+
assert_raises_rpc_error(-32603, msg, n2.loadtxoutset, dump_output['path'])
455456

456457
self.connect_nodes(0, 2)
457458
self.wait_until(lambda: n2.getchainstates()['chainstates'][-1]['blocks'] == FINAL_HEIGHT)

0 commit comments

Comments
 (0)