Merge bitcoin/bitcoin#30598: assumeutxo: Drop block height from metadata
00618e8745assumeutxo: Drop block height from metadata (Fabian Jahr) Pull request description: Fixes https://github.com/bitcoin/bitcoin/issues/30514 which has more context and shows how the issue can be reproduced. Since the value in question is removed, there is no test to add to reproduce anything. This is an alternative approach to #30516 with much of the [code being suggested there](https://github.com/bitcoin/bitcoin/pull/30516#discussion_r1689146902). ACKs for top commit: maflcko: re-ACK00618e8745🎌 achow101: ACK00618e8745theStack: Code-review ACK00618e8745ismaelsadeeq: Re-ACK00618e8745mzumsande: ACK00618e8745Tree-SHA512: db9575247bae838ad7742a27a216faaf55bb11e022f9afdd05752bb09bbf9614717d0ad64304ff5722a16bf41d8dea888af544e4ae26dcaa528c1add0269a4a8
This commit is contained in:
7 files changed
+29
-31
No files matched your search
@@ -28,16 +28,17 @@ class Chainstate;
|
|||||||
namespace node {
|
namespace node {
|
||||||
//! Metadata describing a serialized version of a UTXO set from which an
|
//! Metadata describing a serialized version of a UTXO set from which an
|
||||||
//! assumeutxo Chainstate can be constructed.
|
//! assumeutxo Chainstate can be constructed.
|
||||||
|
//! All metadata fields come from an untrusted file, so must be validated
|
||||||
|
//! before being used. Thus, new fields should be added only if needed.
|
||||||
class SnapshotMetadata
|
class SnapshotMetadata
|
||||||
{
|
{
|
||||||
const uint16_t m_version{1};
|
inline static const uint16_t VERSION{2};
|
||||||
const std::set<uint16_t> m_supported_versions{1};
|
const std::set<uint16_t> m_supported_versions{VERSION};
|
||||||
const MessageStartChars m_network_magic;
|
const MessageStartChars m_network_magic;
|
||||||
public:
|
public:
|
||||||
//! The hash of the block that reflects the tip of the chain for the
|
//! The hash of the block that reflects the tip of the chain for the
|
||||||
//! UTXO set contained in this snapshot.
|
//! UTXO set contained in this snapshot.
|
||||||
uint256 m_base_blockhash;
|
uint256 m_base_blockhash;
|
||||||
uint32_t m_base_blockheight;
|
|
||||||
|
|
||||||
|
|
||||||
//! The number of coins in the UTXO set contained in this snapshot. Used
|
//! The number of coins in the UTXO set contained in this snapshot. Used
|
||||||
@@ -50,19 +51,16 @@ public:
|
|||||||
SnapshotMetadata(
|
SnapshotMetadata(
|
||||||
const MessageStartChars network_magic,
|
const MessageStartChars network_magic,
|
||||||
const uint256& base_blockhash,
|
const uint256& base_blockhash,
|
||||||
const int base_blockheight,
|
|
||||||
uint64_t coins_count) :
|
uint64_t coins_count) :
|
||||||
m_network_magic(network_magic),
|
m_network_magic(network_magic),
|
||||||
m_base_blockhash(base_blockhash),
|
m_base_blockhash(base_blockhash),
|
||||||
m_base_blockheight(base_blockheight),
|
|
||||||
m_coins_count(coins_count) { }
|
m_coins_count(coins_count) { }
|
||||||
|
|
||||||
template <typename Stream>
|
template <typename Stream>
|
||||||
inline void Serialize(Stream& s) const {
|
inline void Serialize(Stream& s) const {
|
||||||
s << SNAPSHOT_MAGIC_BYTES;
|
s << SNAPSHOT_MAGIC_BYTES;
|
||||||
s << m_version;
|
s << VERSION;
|
||||||
s << m_network_magic;
|
s << m_network_magic;
|
||||||
s << m_base_blockheight;
|
|
||||||
s << m_base_blockhash;
|
s << m_base_blockhash;
|
||||||
s << m_coins_count;
|
s << m_coins_count;
|
||||||
}
|
}
|
||||||
@@ -98,7 +96,6 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
s >> m_base_blockheight;
|
|
||||||
s >> m_base_blockhash;
|
s >> m_base_blockhash;
|
||||||
s >> m_coins_count;
|
s >> m_coins_count;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2741,7 +2741,7 @@ UniValue CreateUTXOSnapshot(
|
|||||||
tip->nHeight, tip->GetBlockHash().ToString(),
|
tip->nHeight, tip->GetBlockHash().ToString(),
|
||||||
fs::PathToString(path), fs::PathToString(temppath)));
|
fs::PathToString(path), fs::PathToString(temppath)));
|
||||||
|
|
||||||
SnapshotMetadata metadata{chainstate.m_chainman.GetParams().MessageStart(), tip->GetBlockHash(), tip->nHeight, maybe_stats->coins_count};
|
SnapshotMetadata metadata{chainstate.m_chainman.GetParams().MessageStart(), tip->GetBlockHash(), maybe_stats->coins_count};
|
||||||
|
|
||||||
afile << metadata;
|
afile << metadata;
|
||||||
|
|
||||||
@@ -2865,10 +2865,12 @@ static RPCHelpMan loadtxoutset()
|
|||||||
throw JSONRPCError(RPC_INTERNAL_ERROR, strprintf("Unable to load UTXO snapshot: %s. (%s)", util::ErrorString(activation_result).original, path.utf8string()));
|
throw JSONRPCError(RPC_INTERNAL_ERROR, strprintf("Unable to load UTXO snapshot: %s. (%s)", util::ErrorString(activation_result).original, path.utf8string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CBlockIndex& snapshot_index{*CHECK_NONFATAL(*activation_result)};
|
||||||
|
|
||||||
UniValue result(UniValue::VOBJ);
|
UniValue result(UniValue::VOBJ);
|
||||||
result.pushKV("coins_loaded", metadata.m_coins_count);
|
result.pushKV("coins_loaded", metadata.m_coins_count);
|
||||||
result.pushKV("tip_hash", metadata.m_base_blockhash.ToString());
|
result.pushKV("tip_hash", snapshot_index.GetBlockHash().ToString());
|
||||||
result.pushKV("base_height", metadata.m_base_blockheight);
|
result.pushKV("base_height", snapshot_index.nHeight);
|
||||||
result.pushKV("path", fs::PathToString(path));
|
result.pushKV("path", fs::PathToString(path));
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ FUZZ_TARGET(utxo_snapshot, .init = initialize_chain)
|
|||||||
int base_blockheight{fuzzed_data_provider.ConsumeIntegralInRange<int>(1, 2 * COINBASE_MATURITY)};
|
int base_blockheight{fuzzed_data_provider.ConsumeIntegralInRange<int>(1, 2 * COINBASE_MATURITY)};
|
||||||
uint256 base_blockhash{g_chain->at(base_blockheight - 1)->GetHash()};
|
uint256 base_blockhash{g_chain->at(base_blockheight - 1)->GetHash()};
|
||||||
uint64_t m_coins_count{fuzzed_data_provider.ConsumeIntegralInRange<uint64_t>(1, 3 * COINBASE_MATURITY)};
|
uint64_t m_coins_count{fuzzed_data_provider.ConsumeIntegralInRange<uint64_t>(1, 3 * COINBASE_MATURITY)};
|
||||||
SnapshotMetadata metadata{msg_start, base_blockhash, base_blockheight, m_coins_count};
|
SnapshotMetadata metadata{msg_start, base_blockhash, m_coins_count};
|
||||||
outfile << metadata;
|
outfile << metadata;
|
||||||
}
|
}
|
||||||
// Coins
|
// Coins
|
||||||
|
|||||||
+7
-7
@@ -5672,31 +5672,31 @@ Chainstate& ChainstateManager::InitializeChainstate(CTxMemPool* mempool)
|
|||||||
return destroyed && !fs::exists(db_path);
|
return destroyed && !fs::exists(db_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
util::Result<void> ChainstateManager::ActivateSnapshot(
|
util::Result<CBlockIndex*> ChainstateManager::ActivateSnapshot(
|
||||||
AutoFile& coins_file,
|
AutoFile& coins_file,
|
||||||
const SnapshotMetadata& metadata,
|
const SnapshotMetadata& metadata,
|
||||||
bool in_memory)
|
bool in_memory)
|
||||||
{
|
{
|
||||||
uint256 base_blockhash = metadata.m_base_blockhash;
|
uint256 base_blockhash = metadata.m_base_blockhash;
|
||||||
int base_blockheight = metadata.m_base_blockheight;
|
|
||||||
|
|
||||||
if (this->SnapshotBlockhash()) {
|
if (this->SnapshotBlockhash()) {
|
||||||
return util::Error{Untranslated("Can't activate a snapshot-based chainstate more than once")};
|
return util::Error{Untranslated("Can't activate a snapshot-based chainstate more than once")};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CBlockIndex* snapshot_start_block{};
|
||||||
|
|
||||||
{
|
{
|
||||||
LOCK(::cs_main);
|
LOCK(::cs_main);
|
||||||
|
|
||||||
if (!GetParams().AssumeutxoForBlockhash(base_blockhash).has_value()) {
|
if (!GetParams().AssumeutxoForBlockhash(base_blockhash).has_value()) {
|
||||||
auto available_heights = GetParams().GetAvailableSnapshotHeights();
|
auto available_heights = GetParams().GetAvailableSnapshotHeights();
|
||||||
std::string heights_formatted = util::Join(available_heights, ", ", [&](const auto& i) { return util::ToString(i); });
|
std::string heights_formatted = util::Join(available_heights, ", ", [&](const auto& i) { return util::ToString(i); });
|
||||||
return util::Error{strprintf(Untranslated("assumeutxo block hash in snapshot metadata not recognized (hash: %s, height: %s). The following snapshot heights are available: %s"),
|
return util::Error{strprintf(Untranslated("assumeutxo block hash in snapshot metadata not recognized (hash: %s). The following snapshot heights are available: %s"),
|
||||||
base_blockhash.ToString(),
|
base_blockhash.ToString(),
|
||||||
base_blockheight,
|
|
||||||
heights_formatted)};
|
heights_formatted)};
|
||||||
}
|
}
|
||||||
|
|
||||||
CBlockIndex* snapshot_start_block = m_blockman.LookupBlockIndex(base_blockhash);
|
snapshot_start_block = m_blockman.LookupBlockIndex(base_blockhash);
|
||||||
if (!snapshot_start_block) {
|
if (!snapshot_start_block) {
|
||||||
return util::Error{strprintf(Untranslated("The base block header (%s) must appear in the headers chain. Make sure all headers are syncing, and call loadtxoutset again"),
|
return util::Error{strprintf(Untranslated("The base block header (%s) must appear in the headers chain. Make sure all headers are syncing, and call loadtxoutset again"),
|
||||||
base_blockhash.ToString())};
|
base_blockhash.ToString())};
|
||||||
@@ -5707,7 +5707,7 @@ util::Result<void> ChainstateManager::ActivateSnapshot(
|
|||||||
return util::Error{strprintf(Untranslated("The base block header (%s) is part of an invalid chain"), base_blockhash.ToString())};
|
return util::Error{strprintf(Untranslated("The base block header (%s) is part of an invalid chain"), base_blockhash.ToString())};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_best_header || m_best_header->GetAncestor(base_blockheight) != snapshot_start_block) {
|
if (!m_best_header || m_best_header->GetAncestor(snapshot_start_block->nHeight) != snapshot_start_block) {
|
||||||
return util::Error{Untranslated("A forked headers-chain with more work than the chain with the snapshot base block header exists. Please proceed to sync without AssumeUtxo.")};
|
return util::Error{Untranslated("A forked headers-chain with more work than the chain with the snapshot base block header exists. Please proceed to sync without AssumeUtxo.")};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5821,7 +5821,7 @@ util::Result<void> ChainstateManager::ActivateSnapshot(
|
|||||||
m_snapshot_chainstate->CoinsTip().DynamicMemoryUsage() / (1000 * 1000));
|
m_snapshot_chainstate->CoinsTip().DynamicMemoryUsage() / (1000 * 1000));
|
||||||
|
|
||||||
this->MaybeRebalanceCaches();
|
this->MaybeRebalanceCaches();
|
||||||
return {};
|
return snapshot_start_block;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void FlushSnapshotToDisk(CCoinsViewCache& coins_cache, bool snapshot_loaded)
|
static void FlushSnapshotToDisk(CCoinsViewCache& coins_cache, bool snapshot_loaded)
|
||||||
|
|||||||
+1
-1
@@ -1098,7 +1098,7 @@ public:
|
|||||||
//! faking nTx* block index data along the way.
|
//! faking nTx* block index data along the way.
|
||||||
//! - Move the new chainstate to `m_snapshot_chainstate` and make it our
|
//! - Move the new chainstate to `m_snapshot_chainstate` and make it our
|
||||||
//! ChainstateActive().
|
//! ChainstateActive().
|
||||||
[[nodiscard]] util::Result<void> ActivateSnapshot(
|
[[nodiscard]] util::Result<CBlockIndex*> ActivateSnapshot(
|
||||||
AutoFile& coins_file, const node::SnapshotMetadata& metadata, bool in_memory);
|
AutoFile& coins_file, const node::SnapshotMetadata& metadata, bool in_memory);
|
||||||
|
|
||||||
//! Once the background validation chainstate has reached the height which
|
//! Once the background validation chainstate has reached the height which
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ class AssumeutxoTest(BitcoinTestFramework):
|
|||||||
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)
|
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")
|
self.log.info(" - snapshot file with unsupported version")
|
||||||
for version in [0, 2]:
|
for version in [0, 1, 3]:
|
||||||
with open(bad_snapshot_path, 'wb') as f:
|
with open(bad_snapshot_path, 'wb') as f:
|
||||||
f.write(valid_snapshot_contents[:5] + version.to_bytes(2, "little") + valid_snapshot_contents[7:])
|
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.", node.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)
|
||||||
@@ -98,21 +98,20 @@ class AssumeutxoTest(BitcoinTestFramework):
|
|||||||
bogus_block_hash = "0" * 64 # Represents any unknown block hash
|
bogus_block_hash = "0" * 64 # Represents any unknown block hash
|
||||||
# The height is not used for anything critical currently, so we just
|
# The height is not used for anything critical currently, so we just
|
||||||
# confirm the manipulation in the error message
|
# confirm the manipulation in the error message
|
||||||
bogus_height = 1337
|
|
||||||
for bad_block_hash in [bogus_block_hash, prev_block_hash]:
|
for bad_block_hash in [bogus_block_hash, prev_block_hash]:
|
||||||
with open(bad_snapshot_path, 'wb') as f:
|
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:])
|
f.write(valid_snapshot_contents[:11] + bytes.fromhex(bad_block_hash)[::-1] + valid_snapshot_contents[43:])
|
||||||
|
|
||||||
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, 200, 299."
|
msg = f"Unable to load UTXO snapshot: assumeutxo block hash in snapshot metadata not recognized (hash: {bad_block_hash}). The following snapshot heights are available: 110, 200, 299."
|
||||||
assert_raises_rpc_error(-32603, msg, node.loadtxoutset, bad_snapshot_path)
|
assert_raises_rpc_error(-32603, msg, node.loadtxoutset, bad_snapshot_path)
|
||||||
|
|
||||||
self.log.info(" - snapshot file with wrong number of coins")
|
self.log.info(" - snapshot file with wrong number of coins")
|
||||||
valid_num_coins = int.from_bytes(valid_snapshot_contents[47:47 + 8], "little")
|
valid_num_coins = int.from_bytes(valid_snapshot_contents[43:43 + 8], "little")
|
||||||
for off in [-1, +1]:
|
for off in [-1, +1]:
|
||||||
with open(bad_snapshot_path, 'wb') as f:
|
with open(bad_snapshot_path, 'wb') as f:
|
||||||
f.write(valid_snapshot_contents[:47])
|
f.write(valid_snapshot_contents[:43])
|
||||||
f.write((valid_num_coins + off).to_bytes(8, "little"))
|
f.write((valid_num_coins + off).to_bytes(8, "little"))
|
||||||
f.write(valid_snapshot_contents[47 + 8:])
|
f.write(valid_snapshot_contents[43 + 8:])
|
||||||
expected_error(msg="Bad snapshot - coins left over after deserializing 298 coins." if off == -1 else "Bad snapshot format or truncated snapshot after deserializing 299 coins.")
|
expected_error(msg="Bad snapshot - coins left over after deserializing 298 coins." if off == -1 else "Bad snapshot format or truncated snapshot after deserializing 299 coins.")
|
||||||
|
|
||||||
self.log.info(" - snapshot file with alternated but parsable UTXO data results in different hash")
|
self.log.info(" - snapshot file with alternated but parsable UTXO data results in different hash")
|
||||||
@@ -130,10 +129,10 @@ class AssumeutxoTest(BitcoinTestFramework):
|
|||||||
|
|
||||||
for content, offset, wrong_hash, custom_message in cases:
|
for content, offset, wrong_hash, custom_message in cases:
|
||||||
with open(bad_snapshot_path, "wb") as f:
|
with open(bad_snapshot_path, "wb") as f:
|
||||||
# Prior to offset: Snapshot magic, snapshot version, network magic, height, hash, coins count
|
# Prior to offset: Snapshot magic, snapshot version, network magic, hash, coins count
|
||||||
f.write(valid_snapshot_contents[:(5 + 2 + 4 + 4 + 32 + 8 + offset)])
|
f.write(valid_snapshot_contents[:(5 + 2 + 4 + 32 + 8 + offset)])
|
||||||
f.write(content)
|
f.write(content)
|
||||||
f.write(valid_snapshot_contents[(5 + 2 + 4 + 4 + 32 + 8 + offset + len(content)):])
|
f.write(valid_snapshot_contents[(5 + 2 + 4 + 32 + 8 + offset + len(content)):])
|
||||||
|
|
||||||
msg = custom_message if custom_message is not None else f"Bad snapshot content hash: expected a4bf3407ccb2cc0145c49ebba8fa91199f8a3903daf0883875941497d2493c27, got {wrong_hash}."
|
msg = custom_message if custom_message is not None else f"Bad snapshot content hash: expected a4bf3407ccb2cc0145c49ebba8fa91199f8a3903daf0883875941497d2493c27, got {wrong_hash}."
|
||||||
expected_error(msg)
|
expected_error(msg)
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ class DumptxoutsetTest(BitcoinTestFramework):
|
|||||||
# UTXO snapshot hash should be deterministic based on mocked time.
|
# UTXO snapshot hash should be deterministic based on mocked time.
|
||||||
assert_equal(
|
assert_equal(
|
||||||
sha256sum_file(str(expected_path)).hex(),
|
sha256sum_file(str(expected_path)).hex(),
|
||||||
'2f775f82811150d310527b5ff773f81fb0fb517e941c543c1f7c4d38fd2717b3')
|
'31fcdd0cf542a4b1dfc13c3c05106620ce48951ef62907dd8e5e8c15a0aa993b')
|
||||||
|
|
||||||
assert_equal(
|
assert_equal(
|
||||||
out['txoutset_hash'], 'a0b7baa3bf5ccbd3279728f230d7ca0c44a76e9923fca8f32dbfd08d65ea496a')
|
out['txoutset_hash'], 'a0b7baa3bf5ccbd3279728f230d7ca0c44a76e9923fca8f32dbfd08d65ea496a')
|
||||||
|
|||||||
Reference in new issue
Block a user