aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/rpc/blockchain.cpp31
-rw-r--r--src/test/fuzz/utxo_snapshot.cpp2
-rw-r--r--src/test/util/chainstate.h4
-rw-r--r--src/validation.cpp35
-rw-r--r--src/validation.h2
-rwxr-xr-xtest/functional/feature_assumeutxo.py46
6 files changed, 66 insertions, 54 deletions
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index e785678614..4de636b69f 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -62,9 +62,7 @@ using kernel::CoinStatsHashType;
using node::BlockManager;
using node::NodeContext;
using node::SnapshotMetadata;
-using util::Join;
using util::MakeUnorderedList;
-using util::ToString;
struct CUpdatedBlock
{
@@ -2821,34 +2819,15 @@ static RPCHelpMan loadtxoutset()
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("Unable to parse metadata: %s", e.what()));
}
- uint256 base_blockhash = metadata.m_base_blockhash;
- int base_blockheight = metadata.m_base_blockheight;
- if (!chainman.GetParams().AssumeutxoForBlockhash(base_blockhash).has_value()) {
- auto available_heights = chainman.GetParams().GetAvailableSnapshotHeights();
- std::string heights_formatted = Join(available_heights, ", ", [&](const auto& i) { return ToString(i); });
- throw JSONRPCError(RPC_INTERNAL_ERROR, strprintf("Unable to load UTXO snapshot, "
- "assumeutxo block hash in snapshot metadata not recognized (hash: %s, height: %s). The following snapshot heights are available: %s.",
- base_blockhash.ToString(),
- base_blockheight,
- heights_formatted));
- }
- CBlockIndex* snapshot_start_block = WITH_LOCK(::cs_main,
- return chainman.m_blockman.LookupBlockIndex(base_blockhash));
-
- if (!snapshot_start_block) {
- throw JSONRPCError(
- RPC_INTERNAL_ERROR,
- strprintf("The base block header (%s) must appear in the headers chain. Make sure all headers are syncing, and call this RPC again.",
- base_blockhash.ToString()));
- }
- if (!chainman.ActivateSnapshot(afile, metadata, false)) {
- throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to load UTXO snapshot " + fs::PathToString(path));
+ auto activation_result{chainman.ActivateSnapshot(afile, metadata, false)};
+ if (!activation_result) {
+ throw JSONRPCError(RPC_INTERNAL_ERROR, strprintf(_("Unable to load UTXO snapshot: %s\n"), util::ErrorString(activation_result)).original);
}
UniValue result(UniValue::VOBJ);
result.pushKV("coins_loaded", metadata.m_coins_count);
- result.pushKV("tip_hash", snapshot_start_block->GetBlockHash().ToString());
- result.pushKV("base_height", snapshot_start_block->nHeight);
+ result.pushKV("tip_hash", metadata.m_base_blockhash.ToString());
+ result.pushKV("base_height", metadata.m_base_blockheight);
result.pushKV("path", fs::PathToString(path));
return result;
},
diff --git a/src/test/fuzz/utxo_snapshot.cpp b/src/test/fuzz/utxo_snapshot.cpp
index 8c9c67a91c..fa608385d9 100644
--- a/src/test/fuzz/utxo_snapshot.cpp
+++ b/src/test/fuzz/utxo_snapshot.cpp
@@ -54,7 +54,7 @@ FUZZ_TARGET(utxo_snapshot, .init = initialize_chain)
} catch (const std::ios_base::failure&) {
return false;
}
- return chainman.ActivateSnapshot(infile, metadata, /*in_memory=*/true);
+ return !!chainman.ActivateSnapshot(infile, metadata, /*in_memory=*/true);
}};
if (fuzzed_data_provider.ConsumeBool()) {
diff --git a/src/test/util/chainstate.h b/src/test/util/chainstate.h
index 03b44fc894..a4636365ca 100644
--- a/src/test/util/chainstate.h
+++ b/src/test/util/chainstate.h
@@ -124,11 +124,11 @@ CreateAndActivateUTXOSnapshot(
new_active.m_chain.SetTip(*(tip->pprev));
}
- bool res = node.chainman->ActivateSnapshot(auto_infile, metadata, in_memory_chainstate);
+ auto res = node.chainman->ActivateSnapshot(auto_infile, metadata, in_memory_chainstate);
// Restore the old tip.
new_active.m_chain.SetTip(*tip);
- return res;
+ return !!res;
}
diff --git a/src/validation.cpp b/src/validation.cpp
index 3e9ba08bb1..4241d51d15 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -5646,23 +5646,43 @@ Chainstate& ChainstateManager::InitializeChainstate(CTxMemPool* mempool)
return destroyed && !fs::exists(db_path);
}
-bool ChainstateManager::ActivateSnapshot(
+util::Result<void> ChainstateManager::ActivateSnapshot(
AutoFile& coins_file,
const SnapshotMetadata& metadata,
bool in_memory)
{
uint256 base_blockhash = metadata.m_base_blockhash;
+ int base_blockheight = metadata.m_base_blockheight;
if (this->SnapshotBlockhash()) {
- LogPrintf("[snapshot] can't activate a snapshot-based chainstate more than once\n");
- return false;
+ return util::Error{_("Can't activate a snapshot-based chainstate more than once")};
}
{
LOCK(::cs_main);
+
+ if (!GetParams().AssumeutxoForBlockhash(base_blockhash).has_value()) {
+ auto available_heights = GetParams().GetAvailableSnapshotHeights();
+ std::string heights_formatted = util::Join(available_heights, ", ", [&](const auto& i) { return util::ToString(i); });
+ return util::Error{strprintf(_("assumeutxo block hash in snapshot metadata not recognized (hash: %s, height: %s). The following snapshot heights are available: %s."),
+ base_blockhash.ToString(),
+ base_blockheight,
+ heights_formatted)};
+ }
+
+ CBlockIndex* snapshot_start_block = m_blockman.LookupBlockIndex(base_blockhash);
+ if (!snapshot_start_block) {
+ 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."),
+ base_blockhash.ToString())};
+ }
+
+ bool start_block_invalid = snapshot_start_block->nStatus & BLOCK_FAILED_MASK;
+ if (start_block_invalid) {
+ return util::Error{strprintf(_("The base block header (%s) is part of an invalid chain."), base_blockhash.ToString())};
+ }
+
if (Assert(m_active_chainstate->GetMempool())->size() > 0) {
- LogPrintf("[snapshot] can't activate a snapshot when mempool not empty\n");
- return false;
+ return util::Error{_("Can't activate a snapshot when mempool not empty.")};
}
}
@@ -5712,7 +5732,6 @@ bool ChainstateManager::ActivateSnapshot(
}
auto cleanup_bad_snapshot = [&](const char* reason) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
- LogPrintf("[snapshot] activation failed - %s\n", reason);
this->MaybeRebalanceCaches();
// PopulateAndValidateSnapshot can return (in error) before the leveldb datadir
@@ -5728,7 +5747,7 @@ bool ChainstateManager::ActivateSnapshot(
"Manually remove it before restarting.\n"), fs::PathToString(*snapshot_datadir)));
}
}
- return false;
+ return util::Error{_(reason)};
};
if (!this->PopulateAndValidateSnapshot(*snapshot_chainstate, coins_file, metadata)) {
@@ -5771,7 +5790,7 @@ bool ChainstateManager::ActivateSnapshot(
m_snapshot_chainstate->CoinsTip().DynamicMemoryUsage() / (1000 * 1000));
this->MaybeRebalanceCaches();
- return true;
+ return {};
}
static void FlushSnapshotToDisk(CCoinsViewCache& coins_cache, bool snapshot_loaded)
diff --git a/src/validation.h b/src/validation.h
index ab7891539a..2f5f2e2b95 100644
--- a/src/validation.h
+++ b/src/validation.h
@@ -1054,7 +1054,7 @@ public:
//! faking nTx* block index data along the way.
//! - Move the new chainstate to `m_snapshot_chainstate` and make it our
//! ChainstateActive().
- [[nodiscard]] bool ActivateSnapshot(
+ [[nodiscard]] util::Result<void> ActivateSnapshot(
AutoFile& coins_file, const node::SnapshotMetadata& metadata, bool in_memory);
//! Once the background validation chainstate has reached the height which
diff --git a/test/functional/feature_assumeutxo.py b/test/functional/feature_assumeutxo.py
index 658eea0a0e..0b434d1616 100755
--- a/test/functional/feature_assumeutxo.py
+++ b/test/functional/feature_assumeutxo.py
@@ -70,23 +70,24 @@ class AssumeutxoTest(BitcoinTestFramework):
with open(valid_snapshot_path, 'rb') as f:
valid_snapshot_contents = f.read()
bad_snapshot_path = valid_snapshot_path + '.mod'
+ node = self.nodes[1]
def expected_error(log_msg="", rpc_details=""):
- with self.nodes[1].assert_debug_log([log_msg]):
- assert_raises_rpc_error(-32603, f"Unable to load UTXO snapshot{rpc_details}", self.nodes[1].loadtxoutset, bad_snapshot_path)
+ with node.assert_debug_log([log_msg]):
+ assert_raises_rpc_error(-32603, f"Unable to load UTXO snapshot{rpc_details}", node.loadtxoutset, bad_snapshot_path)
self.log.info(" - snapshot file with invalid file magic")
parsing_error_code = -22
bad_magic = 0xf00f00f000
with open(bad_snapshot_path, 'wb') as f:
f.write(bad_magic.to_bytes(5, "big") + valid_snapshot_contents[5:])
- 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)
+ 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)
self.log.info(" - snapshot file with unsupported version")
for version in [0, 2]:
with open(bad_snapshot_path, 'wb') as f:
f.write(valid_snapshot_contents[:5] + version.to_bytes(2, "little") + valid_snapshot_contents[7:])
- 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)
+ 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)
self.log.info(" - snapshot file with mismatching network magic")
invalid_magics = [
@@ -101,9 +102,9 @@ class AssumeutxoTest(BitcoinTestFramework):
with open(bad_snapshot_path, 'wb') as f:
f.write(valid_snapshot_contents[:7] + magic.to_bytes(4, 'big') + valid_snapshot_contents[11:])
if real:
- 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)
+ 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)
else:
- 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)
+ 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)
self.log.info(" - snapshot file referring to a block that is not in the assumeutxo parameters")
prev_block_hash = self.nodes[0].getblockhash(SNAPSHOT_BASE_HEIGHT - 1)
@@ -114,8 +115,9 @@ class AssumeutxoTest(BitcoinTestFramework):
for bad_block_hash in [bogus_block_hash, prev_block_hash]:
with open(bad_snapshot_path, 'wb') as f:
f.write(valid_snapshot_contents[:11] + bogus_height.to_bytes(4, "little") + bytes.fromhex(bad_block_hash)[::-1] + valid_snapshot_contents[47:])
- 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."
- expected_error(rpc_details=error_details)
+
+ 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."
+ assert_raises_rpc_error(-32603, msg, node.loadtxoutset, bad_snapshot_path)
self.log.info(" - snapshot file with wrong number of coins")
valid_num_coins = int.from_bytes(valid_snapshot_contents[47:47 + 8], "little")
@@ -151,9 +153,8 @@ class AssumeutxoTest(BitcoinTestFramework):
def test_headers_not_synced(self, valid_snapshot_path):
for node in self.nodes[1:]:
- 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.",
- node.loadtxoutset,
- valid_snapshot_path)
+ 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."
+ assert_raises_rpc_error(-32603, msg, node.loadtxoutset, valid_snapshot_path)
def test_invalid_chainstate_scenarios(self):
self.log.info("Test different scenarios of invalid snapshot chainstate in datadir")
@@ -185,8 +186,8 @@ class AssumeutxoTest(BitcoinTestFramework):
assert tx['txid'] in node.getrawmempool()
# Attempt to load the snapshot on Node 2 and expect it to fail
- with node.assert_debug_log(expected_msgs=["[snapshot] can't activate a snapshot when mempool not empty"]):
- assert_raises_rpc_error(-32603, "Unable to load UTXO snapshot", node.loadtxoutset, dump_output_path)
+ msg = "Unable to load UTXO snapshot: Can't activate a snapshot when mempool not empty"
+ assert_raises_rpc_error(-32603, msg, node.loadtxoutset, dump_output_path)
self.restart_node(2, extra_args=self.extra_args[2])
@@ -202,7 +203,19 @@ class AssumeutxoTest(BitcoinTestFramework):
assert_equal(node.getblockcount(), FINAL_HEIGHT)
with node.assert_debug_log(expected_msgs=["[snapshot] activation failed - work does not exceed active chainstate"]):
assert_raises_rpc_error(-32603, "Unable to load UTXO snapshot", node.loadtxoutset, dump_output_path)
- self.restart_node(0, extra_args=self.extra_args[0])
+
+ def test_snapshot_block_invalidated(self, dump_output_path):
+ self.log.info("Test snapshot is not loaded when base block is invalid.")
+ node = self.nodes[0]
+ # We are testing the case where the base block is invalidated itself
+ # and also the case where one of its parents is invalidated.
+ for height in [SNAPSHOT_BASE_HEIGHT, SNAPSHOT_BASE_HEIGHT - 1]:
+ block_hash = node.getblockhash(height)
+ node.invalidateblock(block_hash)
+ assert_equal(node.getblockcount(), height - 1)
+ msg = "Unable to load UTXO snapshot: The base block header (3bb7ce5eba0be48939b7a521ac1ba9316afee2c7bada3a0cca24188e6d7d96c0) is part of an invalid chain."
+ assert_raises_rpc_error(-32603, msg, node.loadtxoutset, dump_output_path)
+ node.reconsiderblock(block_hash)
def run_test(self):
"""
@@ -290,6 +303,7 @@ class AssumeutxoTest(BitcoinTestFramework):
self.test_invalid_snapshot_scenarios(dump_output['path'])
self.test_invalid_chainstate_scenarios()
self.test_invalid_file_path()
+ self.test_snapshot_block_invalidated(dump_output['path'])
self.log.info(f"Loading snapshot into second node from {dump_output['path']}")
loaded = n1.loadtxoutset(dump_output['path'])
@@ -450,8 +464,8 @@ class AssumeutxoTest(BitcoinTestFramework):
assert_equal(snapshot['validated'], False)
self.log.info("Check that loading the snapshot again will fail because there is already an active snapshot.")
- with n2.assert_debug_log(expected_msgs=["[snapshot] can't activate a snapshot-based chainstate more than once"]):
- assert_raises_rpc_error(-32603, "Unable to load UTXO snapshot", n2.loadtxoutset, dump_output['path'])
+ msg = "Unable to load UTXO snapshot: Can't activate a snapshot-based chainstate more than once"
+ assert_raises_rpc_error(-32603, msg, n2.loadtxoutset, dump_output['path'])
self.connect_nodes(0, 2)
self.wait_until(lambda: n2.getchainstates()['chainstates'][-1]['blocks'] == FINAL_HEIGHT)