mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-02-22 06:52:36 +01:00
assumeutxo: Drop block height from metadata
The Snapshot format version is updated to 2 to indicate this change. Co-authored-by: MarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz>
This commit is contained in:
parent
bb25e0691f
commit
00618e8745
7 changed files with 29 additions and 31 deletions
|
@ -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
|
||||||
|
|
|
@ -5645,31 +5645,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())};
|
||||||
|
@ -5680,7 +5680,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.")};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5794,7 +5794,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)
|
||||||
|
|
|
@ -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')
|
||||||
|
|
Loading…
Add table
Reference in a new issue