Skip to content

Commit 69df012

Browse files
committed
Merge bitcoin/bitcoin#30497: rpc: Return errors in loadtxoutset that currently go to logs
fa530ec rpc: Return precise loadtxoutset error messages (MarcoFalke) faa5c86 refactor: Use untranslated error message in ActivateSnapshot (MarcoFalke) Pull request description: The error messages should never happen in normal operation. However, if they do, they are helpful to return to the user to debug the issue. For example, to notice a truncated file. This fixes bitcoin/bitcoin#28621 Also includes a minor refactor commit. ACKs for top commit: fjahr: Code review ACK fa530ec ryanofsky: Code review ACK fa530ec, just adjusting error messages a little since last review. (Thanks!) Tree-SHA512: 224968c9b13d082ca2ed1f6a8fcc5f51ff16d6c96bd38c3679699505b54337b99cccaf7a8474391f6b11f9ccb101977b4e626898c1217eae95802e290cf105f1
2 parents 21c2879 + fa530ec commit 69df012

File tree

3 files changed

+38
-49
lines changed

3 files changed

+38
-49
lines changed

src/validation.cpp

Lines changed: 26 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Copyright (c) 2009-2010 Satoshi Nakamoto
2-
// Copyright (c) 2009-2022 The Bitcoin Core developers
2+
// Copyright (c) 2009-present The Bitcoin Core developers
33
// Distributed under the MIT software license, see the accompanying
44
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
55

@@ -5681,7 +5681,7 @@ util::Result<void> ChainstateManager::ActivateSnapshot(
56815681
}
56825682

56835683
if (!m_best_header || m_best_header->GetAncestor(base_blockheight) != snapshot_start_block) {
5684-
return util::Error{_("A forked headers-chain with more work than the chain with the snapshot base block header exists. Please proceed to sync without AssumeUtxo.")};
5684+
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.")};
56855685
}
56865686

56875687
auto mempool{m_active_chainstate->GetMempool()};
@@ -5735,7 +5735,7 @@ util::Result<void> ChainstateManager::ActivateSnapshot(
57355735
static_cast<size_t>(current_coinstip_cache_size * SNAPSHOT_CACHE_PERC));
57365736
}
57375737

5738-
auto cleanup_bad_snapshot = [&](bilingual_str&& reason) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
5738+
auto cleanup_bad_snapshot = [&](bilingual_str reason) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
57395739
this->MaybeRebalanceCaches();
57405740

57415741
// PopulateAndValidateSnapshot can return (in error) before the leveldb datadir
@@ -5754,9 +5754,9 @@ util::Result<void> ChainstateManager::ActivateSnapshot(
57545754
return util::Error{std::move(reason)};
57555755
};
57565756

5757-
if (!this->PopulateAndValidateSnapshot(*snapshot_chainstate, coins_file, metadata)) {
5757+
if (auto res{this->PopulateAndValidateSnapshot(*snapshot_chainstate, coins_file, metadata)}; !res) {
57585758
LOCK(::cs_main);
5759-
return cleanup_bad_snapshot(Untranslated("population failed"));
5759+
return cleanup_bad_snapshot(strprintf(Untranslated("Population failed: %s"), util::ErrorString(res)));
57605760
}
57615761

57625762
LOCK(::cs_main); // cs_main required for rest of snapshot activation.
@@ -5821,7 +5821,7 @@ static void SnapshotUTXOHashBreakpoint(const util::SignalInterrupt& interrupt)
58215821
if (interrupt) throw StopHashingException();
58225822
}
58235823

5824-
bool ChainstateManager::PopulateAndValidateSnapshot(
5824+
util::Result<void> ChainstateManager::PopulateAndValidateSnapshot(
58255825
Chainstate& snapshot_chainstate,
58265826
AutoFile& coins_file,
58275827
const SnapshotMetadata& metadata)
@@ -5837,18 +5837,16 @@ bool ChainstateManager::PopulateAndValidateSnapshot(
58375837
if (!snapshot_start_block) {
58385838
// Needed for ComputeUTXOStats to determine the
58395839
// height and to avoid a crash when base_blockhash.IsNull()
5840-
LogPrintf("[snapshot] Did not find snapshot start blockheader %s\n",
5841-
base_blockhash.ToString());
5842-
return false;
5840+
return util::Error{strprintf(Untranslated("Did not find snapshot start blockheader %s"),
5841+
base_blockhash.ToString())};
58435842
}
58445843

58455844
int base_height = snapshot_start_block->nHeight;
58465845
const auto& maybe_au_data = GetParams().AssumeutxoForHeight(base_height);
58475846

58485847
if (!maybe_au_data) {
5849-
LogPrintf("[snapshot] assumeutxo height in snapshot metadata not recognized "
5850-
"(%d) - refusing to load snapshot\n", base_height);
5851-
return false;
5848+
return util::Error{strprintf(Untranslated("Assumeutxo height in snapshot metadata not recognized "
5849+
"(%d) - refusing to load snapshot"), base_height)};
58525850
}
58535851

58545852
const AssumeutxoData& au_data = *maybe_au_data;
@@ -5857,8 +5855,7 @@ bool ChainstateManager::PopulateAndValidateSnapshot(
58575855
// ActivateSnapshot(), but is done so that we avoid doing the long work of staging
58585856
// a snapshot that isn't actually usable.
58595857
if (WITH_LOCK(::cs_main, return !CBlockIndexWorkComparator()(ActiveTip(), snapshot_start_block))) {
5860-
LogPrintf("[snapshot] activation failed - work does not exceed active chainstate\n");
5861-
return false;
5858+
return util::Error{Untranslated("Work does not exceed active chainstate")};
58625859
}
58635860

58645861
const uint64_t coins_count = metadata.m_coins_count;
@@ -5875,8 +5872,7 @@ bool ChainstateManager::PopulateAndValidateSnapshot(
58755872
coins_per_txid = ReadCompactSize(coins_file);
58765873

58775874
if (coins_per_txid > coins_left) {
5878-
LogPrintf("[snapshot] mismatch in coins count in snapshot metadata and actual snapshot data\n");
5879-
return false;
5875+
return util::Error{Untranslated("Mismatch in coins count in snapshot metadata and actual snapshot data")};
58805876
}
58815877

58825878
for (size_t i = 0; i < coins_per_txid; i++) {
@@ -5888,14 +5884,12 @@ bool ChainstateManager::PopulateAndValidateSnapshot(
58885884
if (coin.nHeight > base_height ||
58895885
outpoint.n >= std::numeric_limits<decltype(outpoint.n)>::max() // Avoid integer wrap-around in coinstats.cpp:ApplyHash
58905886
) {
5891-
LogPrintf("[snapshot] bad snapshot data after deserializing %d coins\n",
5892-
coins_count - coins_left);
5893-
return false;
5887+
return util::Error{strprintf(Untranslated("Bad snapshot data after deserializing %d coins"),
5888+
coins_count - coins_left)};
58945889
}
58955890
if (!MoneyRange(coin.out.nValue)) {
5896-
LogPrintf("[snapshot] bad snapshot data after deserializing %d coins - bad tx out value\n",
5897-
coins_count - coins_left);
5898-
return false;
5891+
return util::Error{strprintf(Untranslated("Bad snapshot data after deserializing %d coins - bad tx out value"),
5892+
coins_count - coins_left)};
58995893
}
59005894
coins_cache.EmplaceCoinInternalDANGER(std::move(outpoint), std::move(coin));
59015895

@@ -5915,7 +5909,7 @@ bool ChainstateManager::PopulateAndValidateSnapshot(
59155909
// means <5MB of memory imprecision.
59165910
if (coins_processed % 120000 == 0) {
59175911
if (m_interrupt) {
5918-
return false;
5912+
return util::Error{Untranslated("Aborting after an interrupt was requested")};
59195913
}
59205914

59215915
const auto snapshot_cache_state = WITH_LOCK(::cs_main,
@@ -5933,9 +5927,8 @@ bool ChainstateManager::PopulateAndValidateSnapshot(
59335927
}
59345928
}
59355929
} catch (const std::ios_base::failure&) {
5936-
LogPrintf("[snapshot] bad snapshot format or truncated snapshot after deserializing %d coins\n",
5937-
coins_processed);
5938-
return false;
5930+
return util::Error{strprintf(Untranslated("Bad snapshot format or truncated snapshot after deserializing %d coins"),
5931+
coins_processed)};
59395932
}
59405933
}
59415934

@@ -5955,9 +5948,8 @@ bool ChainstateManager::PopulateAndValidateSnapshot(
59555948
out_of_coins = true;
59565949
}
59575950
if (!out_of_coins) {
5958-
LogPrintf("[snapshot] bad snapshot - coins left over after deserializing %d coins\n",
5959-
coins_count);
5960-
return false;
5951+
return util::Error{strprintf(Untranslated("Bad snapshot - coins left over after deserializing %d coins"),
5952+
coins_count)};
59615953
}
59625954

59635955
LogPrintf("[snapshot] loaded %d (%.2f MB) coins from snapshot %s\n",
@@ -5980,18 +5972,16 @@ bool ChainstateManager::PopulateAndValidateSnapshot(
59805972
maybe_stats = ComputeUTXOStats(
59815973
CoinStatsHashType::HASH_SERIALIZED, snapshot_coinsdb, m_blockman, [&interrupt = m_interrupt] { SnapshotUTXOHashBreakpoint(interrupt); });
59825974
} catch (StopHashingException const&) {
5983-
return false;
5975+
return util::Error{Untranslated("Aborting after an interrupt was requested")};
59845976
}
59855977
if (!maybe_stats.has_value()) {
5986-
LogPrintf("[snapshot] failed to generate coins stats\n");
5987-
return false;
5978+
return util::Error{Untranslated("Failed to generate coins stats")};
59885979
}
59895980

59905981
// Assert that the deserialized chainstate contents match the expected assumeutxo value.
59915982
if (AssumeutxoHash{maybe_stats->hashSerialized} != au_data.hash_serialized) {
5992-
LogPrintf("[snapshot] bad snapshot content hash: expected %s, got %s\n",
5993-
au_data.hash_serialized.ToString(), maybe_stats->hashSerialized.ToString());
5994-
return false;
5983+
return util::Error{strprintf(Untranslated("Bad snapshot content hash: expected %s, got %s"),
5984+
au_data.hash_serialized.ToString(), maybe_stats->hashSerialized.ToString())};
59955985
}
59965986

59975987
snapshot_chainstate.m_chain.SetTip(*snapshot_start_block);
@@ -6031,7 +6021,7 @@ bool ChainstateManager::PopulateAndValidateSnapshot(
60316021

60326022
LogPrintf("[snapshot] validated snapshot (%.2f MB)\n",
60336023
coins_cache.DynamicMemoryUsage() / (1000 * 1000));
6034-
return true;
6024+
return {};
60356025
}
60366026

60376027
// Currently, this function holds cs_main for its duration, which could be for

src/validation.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -918,7 +918,7 @@ class ChainstateManager
918918
//! To reduce space the serialization format of the snapshot avoids
919919
//! duplication of tx hashes. The code takes advantage of the guarantee by
920920
//! leveldb that keys are lexicographically sorted.
921-
[[nodiscard]] bool PopulateAndValidateSnapshot(
921+
[[nodiscard]] util::Result<void> PopulateAndValidateSnapshot(
922922
Chainstate& snapshot_chainstate,
923923
AutoFile& coins_file,
924924
const node::SnapshotMetadata& metadata);

test/functional/feature_assumeutxo.py

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,8 @@ def test_invalid_snapshot_scenarios(self, valid_snapshot_path):
6060
bad_snapshot_path = valid_snapshot_path + '.mod'
6161
node = self.nodes[1]
6262

63-
def expected_error(log_msg="", rpc_details=""):
64-
with node.assert_debug_log([log_msg]):
65-
assert_raises_rpc_error(-32603, f"Unable to load UTXO snapshot{rpc_details}", node.loadtxoutset, bad_snapshot_path)
63+
def expected_error(msg):
64+
assert_raises_rpc_error(-32603, f"Unable to load UTXO snapshot: Population failed: {msg}", node.loadtxoutset, bad_snapshot_path)
6665

6766
self.log.info(" - snapshot file with invalid file magic")
6867
parsing_error_code = -22
@@ -114,19 +113,19 @@ def expected_error(log_msg="", rpc_details=""):
114113
f.write(valid_snapshot_contents[:47])
115114
f.write((valid_num_coins + off).to_bytes(8, "little"))
116115
f.write(valid_snapshot_contents[47 + 8:])
117-
expected_error(log_msg=f"bad snapshot - coins left over after deserializing 298 coins" if off == -1 else f"bad snapshot format or truncated snapshot after deserializing 299 coins")
116+
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.")
118117

119118
self.log.info(" - snapshot file with alternated but parsable UTXO data results in different hash")
120119
cases = [
121120
# (content, offset, wrong_hash, custom_message)
122121
[b"\xff" * 32, 0, "7d52155c9a9fdc4525b637ef6170568e5dad6fabd0b1fdbb9432010b8453095b", None], # wrong outpoint hash
123-
[(2).to_bytes(1, "little"), 32, None, "[snapshot] bad snapshot data after deserializing 1 coins"], # wrong txid coins count
124-
[b"\xfd\xff\xff", 32, None, "[snapshot] mismatch in coins count in snapshot metadata and actual snapshot data"], # txid coins count exceeds coins left
122+
[(2).to_bytes(1, "little"), 32, None, "Bad snapshot data after deserializing 1 coins."], # wrong txid coins count
123+
[b"\xfd\xff\xff", 32, None, "Mismatch in coins count in snapshot metadata and actual snapshot data"], # txid coins count exceeds coins left
125124
[b"\x01", 33, "9f4d897031ab8547665b4153317ae2fdbf0130c7840b66427ebc48b881cb80ad", None], # wrong outpoint index
126125
[b"\x81", 34, "3da966ba9826fb6d2604260e01607b55ba44e1a5de298606b08704bc62570ea8", None], # wrong coin code VARINT
127126
[b"\x80", 34, "091e893b3ccb4334378709578025356c8bcb0a623f37c7c4e493133c988648e5", None], # another wrong coin code
128-
[b"\x84\x58", 34, None, "[snapshot] bad snapshot data after deserializing 0 coins"], # wrong coin case with height 364 and coinbase 0
129-
[b"\xCA\xD2\x8F\x5A", 39, None, "[snapshot] bad snapshot data after deserializing 0 coins - bad tx out value"], # Amount exceeds MAX_MONEY
127+
[b"\x84\x58", 34, None, "Bad snapshot data after deserializing 0 coins"], # wrong coin case with height 364 and coinbase 0
128+
[b"\xCA\xD2\x8F\x5A", 39, None, "Bad snapshot data after deserializing 0 coins - bad tx out value"], # Amount exceeds MAX_MONEY
130129
]
131130

132131
for content, offset, wrong_hash, custom_message in cases:
@@ -136,8 +135,8 @@ def expected_error(log_msg="", rpc_details=""):
136135
f.write(content)
137136
f.write(valid_snapshot_contents[(5 + 2 + 4 + 4 + 32 + 8 + offset + len(content)):])
138137

139-
log_msg = custom_message if custom_message is not None else f"[snapshot] bad snapshot content hash: expected a4bf3407ccb2cc0145c49ebba8fa91199f8a3903daf0883875941497d2493c27, got {wrong_hash}"
140-
expected_error(log_msg=log_msg)
138+
msg = custom_message if custom_message is not None else f"Bad snapshot content hash: expected a4bf3407ccb2cc0145c49ebba8fa91199f8a3903daf0883875941497d2493c27, got {wrong_hash}."
139+
expected_error(msg)
141140

142141
def test_headers_not_synced(self, valid_snapshot_path):
143142
for node in self.nodes[1:]:
@@ -188,8 +187,8 @@ def test_invalid_file_path(self):
188187
def test_snapshot_with_less_work(self, dump_output_path):
189188
self.log.info("Test bitcoind should fail when snapshot has less accumulated work than this node.")
190189
node = self.nodes[0]
191-
with node.assert_debug_log(expected_msgs=["[snapshot] activation failed - work does not exceed active chainstate"]):
192-
assert_raises_rpc_error(-32603, "Unable to load UTXO snapshot", node.loadtxoutset, dump_output_path)
190+
msg = "Unable to load UTXO snapshot: Population failed: Work does not exceed active chainstate."
191+
assert_raises_rpc_error(-32603, msg, node.loadtxoutset, dump_output_path)
193192

194193
def test_snapshot_block_invalidated(self, dump_output_path):
195194
self.log.info("Test snapshot is not loaded when base block is invalid.")

0 commit comments

Comments
 (0)