Skip to content

Commit 9a69639

Browse files
committed
Merge bitcoin#30598: assumeutxo: Drop block height from metadata
00618e8 assumeutxo: Drop block height from metadata (Fabian Jahr) Pull request description: Fixes bitcoin#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 bitcoin#30516 with much of the [code being suggested there](bitcoin#30516 (comment)). ACKs for top commit: maflcko: re-ACK 00618e8 🎌 achow101: ACK 00618e8 theStack: Code-review ACK 00618e8 ismaelsadeeq: Re-ACK 00618e8 mzumsande: ACK 00618e8 Tree-SHA512: db9575247bae838ad7742a27a216faaf55bb11e022f9afdd05752bb09bbf9614717d0ad64304ff5722a16bf41d8dea888af544e4ae26dcaa528c1add0269a4a8
2 parents 389cf32 + 00618e8 commit 9a69639

File tree

7 files changed

+29
-31
lines changed

7 files changed

+29
-31
lines changed

src/node/utxo_snapshot.h

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,17 @@ class Chainstate;
2828
namespace node {
2929
//! Metadata describing a serialized version of a UTXO set from which an
3030
//! assumeutxo Chainstate can be constructed.
31+
//! All metadata fields come from an untrusted file, so must be validated
32+
//! before being used. Thus, new fields should be added only if needed.
3133
class SnapshotMetadata
3234
{
33-
const uint16_t m_version{1};
34-
const std::set<uint16_t> m_supported_versions{1};
35+
inline static const uint16_t VERSION{2};
36+
const std::set<uint16_t> m_supported_versions{VERSION};
3537
const MessageStartChars m_network_magic;
3638
public:
3739
//! The hash of the block that reflects the tip of the chain for the
3840
//! UTXO set contained in this snapshot.
3941
uint256 m_base_blockhash;
40-
uint32_t m_base_blockheight;
4142

4243

4344
//! The number of coins in the UTXO set contained in this snapshot. Used
@@ -50,19 +51,16 @@ class SnapshotMetadata
5051
SnapshotMetadata(
5152
const MessageStartChars network_magic,
5253
const uint256& base_blockhash,
53-
const int base_blockheight,
5454
uint64_t coins_count) :
5555
m_network_magic(network_magic),
5656
m_base_blockhash(base_blockhash),
57-
m_base_blockheight(base_blockheight),
5857
m_coins_count(coins_count) { }
5958

6059
template <typename Stream>
6160
inline void Serialize(Stream& s) const {
6261
s << SNAPSHOT_MAGIC_BYTES;
63-
s << m_version;
62+
s << VERSION;
6463
s << m_network_magic;
65-
s << m_base_blockheight;
6664
s << m_base_blockhash;
6765
s << m_coins_count;
6866
}
@@ -98,7 +96,6 @@ class SnapshotMetadata
9896
}
9997
}
10098

101-
s >> m_base_blockheight;
10299
s >> m_base_blockhash;
103100
s >> m_coins_count;
104101
}

src/rpc/blockchain.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2741,7 +2741,7 @@ UniValue CreateUTXOSnapshot(
27412741
tip->nHeight, tip->GetBlockHash().ToString(),
27422742
fs::PathToString(path), fs::PathToString(temppath)));
27432743

2744-
SnapshotMetadata metadata{chainstate.m_chainman.GetParams().MessageStart(), tip->GetBlockHash(), tip->nHeight, maybe_stats->coins_count};
2744+
SnapshotMetadata metadata{chainstate.m_chainman.GetParams().MessageStart(), tip->GetBlockHash(), maybe_stats->coins_count};
27452745

27462746
afile << metadata;
27472747

@@ -2865,10 +2865,12 @@ static RPCHelpMan loadtxoutset()
28652865
throw JSONRPCError(RPC_INTERNAL_ERROR, strprintf("Unable to load UTXO snapshot: %s. (%s)", util::ErrorString(activation_result).original, path.utf8string()));
28662866
}
28672867

2868+
CBlockIndex& snapshot_index{*CHECK_NONFATAL(*activation_result)};
2869+
28682870
UniValue result(UniValue::VOBJ);
28692871
result.pushKV("coins_loaded", metadata.m_coins_count);
2870-
result.pushKV("tip_hash", metadata.m_base_blockhash.ToString());
2871-
result.pushKV("base_height", metadata.m_base_blockheight);
2872+
result.pushKV("tip_hash", snapshot_index.GetBlockHash().ToString());
2873+
result.pushKV("base_height", snapshot_index.nHeight);
28722874
result.pushKV("path", fs::PathToString(path));
28732875
return result;
28742876
},

src/test/fuzz/utxo_snapshot.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ FUZZ_TARGET(utxo_snapshot, .init = initialize_chain)
5757
int base_blockheight{fuzzed_data_provider.ConsumeIntegralInRange<int>(1, 2 * COINBASE_MATURITY)};
5858
uint256 base_blockhash{g_chain->at(base_blockheight - 1)->GetHash()};
5959
uint64_t m_coins_count{fuzzed_data_provider.ConsumeIntegralInRange<uint64_t>(1, 3 * COINBASE_MATURITY)};
60-
SnapshotMetadata metadata{msg_start, base_blockhash, base_blockheight, m_coins_count};
60+
SnapshotMetadata metadata{msg_start, base_blockhash, m_coins_count};
6161
outfile << metadata;
6262
}
6363
// Coins

src/validation.cpp

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5672,31 +5672,31 @@ Chainstate& ChainstateManager::InitializeChainstate(CTxMemPool* mempool)
56725672
return destroyed && !fs::exists(db_path);
56735673
}
56745674

5675-
util::Result<void> ChainstateManager::ActivateSnapshot(
5675+
util::Result<CBlockIndex*> ChainstateManager::ActivateSnapshot(
56765676
AutoFile& coins_file,
56775677
const SnapshotMetadata& metadata,
56785678
bool in_memory)
56795679
{
56805680
uint256 base_blockhash = metadata.m_base_blockhash;
5681-
int base_blockheight = metadata.m_base_blockheight;
56825681

56835682
if (this->SnapshotBlockhash()) {
56845683
return util::Error{Untranslated("Can't activate a snapshot-based chainstate more than once")};
56855684
}
56865685

5686+
CBlockIndex* snapshot_start_block{};
5687+
56875688
{
56885689
LOCK(::cs_main);
56895690

56905691
if (!GetParams().AssumeutxoForBlockhash(base_blockhash).has_value()) {
56915692
auto available_heights = GetParams().GetAvailableSnapshotHeights();
56925693
std::string heights_formatted = util::Join(available_heights, ", ", [&](const auto& i) { return util::ToString(i); });
5693-
return util::Error{strprintf(Untranslated("assumeutxo block hash in snapshot metadata not recognized (hash: %s, height: %s). The following snapshot heights are available: %s"),
5694+
return util::Error{strprintf(Untranslated("assumeutxo block hash in snapshot metadata not recognized (hash: %s). The following snapshot heights are available: %s"),
56945695
base_blockhash.ToString(),
5695-
base_blockheight,
56965696
heights_formatted)};
56975697
}
56985698

5699-
CBlockIndex* snapshot_start_block = m_blockman.LookupBlockIndex(base_blockhash);
5699+
snapshot_start_block = m_blockman.LookupBlockIndex(base_blockhash);
57005700
if (!snapshot_start_block) {
57015701
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"),
57025702
base_blockhash.ToString())};
@@ -5707,7 +5707,7 @@ util::Result<void> ChainstateManager::ActivateSnapshot(
57075707
return util::Error{strprintf(Untranslated("The base block header (%s) is part of an invalid chain"), base_blockhash.ToString())};
57085708
}
57095709

5710-
if (!m_best_header || m_best_header->GetAncestor(base_blockheight) != snapshot_start_block) {
5710+
if (!m_best_header || m_best_header->GetAncestor(snapshot_start_block->nHeight) != snapshot_start_block) {
57115711
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.")};
57125712
}
57135713

@@ -5821,7 +5821,7 @@ util::Result<void> ChainstateManager::ActivateSnapshot(
58215821
m_snapshot_chainstate->CoinsTip().DynamicMemoryUsage() / (1000 * 1000));
58225822

58235823
this->MaybeRebalanceCaches();
5824-
return {};
5824+
return snapshot_start_block;
58255825
}
58265826

58275827
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
@@ -1098,7 +1098,7 @@ class ChainstateManager
10981098
//! faking nTx* block index data along the way.
10991099
//! - Move the new chainstate to `m_snapshot_chainstate` and make it our
11001100
//! ChainstateActive().
1101-
[[nodiscard]] util::Result<void> ActivateSnapshot(
1101+
[[nodiscard]] util::Result<CBlockIndex*> ActivateSnapshot(
11021102
AutoFile& coins_file, const node::SnapshotMetadata& metadata, bool in_memory);
11031103

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

test/functional/feature_assumeutxo.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ def expected_error(msg):
7171
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)
7272

7373
self.log.info(" - snapshot file with unsupported version")
74-
for version in [0, 2]:
74+
for version in [0, 1, 3]:
7575
with open(bad_snapshot_path, 'wb') as f:
7676
f.write(valid_snapshot_contents[:5] + version.to_bytes(2, "little") + valid_snapshot_contents[7:])
7777
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 @@ def expected_error(msg):
9898
bogus_block_hash = "0" * 64 # Represents any unknown block hash
9999
# The height is not used for anything critical currently, so we just
100100
# confirm the manipulation in the error message
101-
bogus_height = 1337
102101
for bad_block_hash in [bogus_block_hash, prev_block_hash]:
103102
with open(bad_snapshot_path, 'wb') as f:
104-
f.write(valid_snapshot_contents[:11] + bogus_height.to_bytes(4, "little") + bytes.fromhex(bad_block_hash)[::-1] + valid_snapshot_contents[47:])
103+
f.write(valid_snapshot_contents[:11] + bytes.fromhex(bad_block_hash)[::-1] + valid_snapshot_contents[43:])
105104

106-
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."
105+
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."
107106
assert_raises_rpc_error(-32603, msg, node.loadtxoutset, bad_snapshot_path)
108107

109108
self.log.info(" - snapshot file with wrong number of coins")
110-
valid_num_coins = int.from_bytes(valid_snapshot_contents[47:47 + 8], "little")
109+
valid_num_coins = int.from_bytes(valid_snapshot_contents[43:43 + 8], "little")
111110
for off in [-1, +1]:
112111
with open(bad_snapshot_path, 'wb') as f:
113-
f.write(valid_snapshot_contents[:47])
112+
f.write(valid_snapshot_contents[:43])
114113
f.write((valid_num_coins + off).to_bytes(8, "little"))
115-
f.write(valid_snapshot_contents[47 + 8:])
114+
f.write(valid_snapshot_contents[43 + 8:])
116115
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.")
117116

118117
self.log.info(" - snapshot file with alternated but parsable UTXO data results in different hash")
@@ -130,10 +129,10 @@ def expected_error(msg):
130129

131130
for content, offset, wrong_hash, custom_message in cases:
132131
with open(bad_snapshot_path, "wb") as f:
133-
# Prior to offset: Snapshot magic, snapshot version, network magic, height, hash, coins count
134-
f.write(valid_snapshot_contents[:(5 + 2 + 4 + 4 + 32 + 8 + offset)])
132+
# Prior to offset: Snapshot magic, snapshot version, network magic, hash, coins count
133+
f.write(valid_snapshot_contents[:(5 + 2 + 4 + 32 + 8 + offset)])
135134
f.write(content)
136-
f.write(valid_snapshot_contents[(5 + 2 + 4 + 4 + 32 + 8 + offset + len(content)):])
135+
f.write(valid_snapshot_contents[(5 + 2 + 4 + 32 + 8 + offset + len(content)):])
137136

138137
msg = custom_message if custom_message is not None else f"Bad snapshot content hash: expected a4bf3407ccb2cc0145c49ebba8fa91199f8a3903daf0883875941497d2493c27, got {wrong_hash}."
139138
expected_error(msg)

test/functional/rpc_dumptxoutset.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def run_test(self):
4343
# UTXO snapshot hash should be deterministic based on mocked time.
4444
assert_equal(
4545
sha256sum_file(str(expected_path)).hex(),
46-
'2f775f82811150d310527b5ff773f81fb0fb517e941c543c1f7c4d38fd2717b3')
46+
'31fcdd0cf542a4b1dfc13c3c05106620ce48951ef62907dd8e5e8c15a0aa993b')
4747

4848
assert_equal(
4949
out['txoutset_hash'], 'a0b7baa3bf5ccbd3279728f230d7ca0c44a76e9923fca8f32dbfd08d65ea496a')

0 commit comments

Comments
 (0)