Skip to content

Commit 053a5fc

Browse files
author
merge-script
committed
Merge bitcoin/bitcoin#22762: Raise InitError when peers.dat is invalid or corrupted
fa55c3d Raise InitError when peers.dat is invalid or corrupted (MarcoFalke) fa4e2cc Inline ReadPeerAddresses (MarcoFalke) fa5aeec Move LoadAddrman from init to addrdb (MarcoFalke) Pull request description: peers.dat is silently erased when it can not be parsed or when it appears corrupted. Fix that by notifying the user. This might help in the following examples: * The user provided the database, but picked the wrong one. * A future version of Bitcoin Core wrote the file and it can't be read. * The file was corrupted by a logic bug in Bitcoin Core. * The file was corrupted by a disk failure. ACKs for top commit: jonatack: Code review re-ACK fa55c3d per `git range-diff eb1f570 fa59c6d fa55c3` and verified the new tests fail on master, except "Check mocked addrman is valid", as expected prayank23: tACK bitcoin/bitcoin@fa55c3d vasild: ACK fa55c3d Tree-SHA512: 78264a78ee570a3c3262cf9c8542b5ffaffa5f52da1eef66c8c381f346989272967cfe1769c573502d9d7d3f7ad68c3ac3b2ec734185d2e4e7595b7122b14196
2 parents 384d076 + fa55c3d commit 053a5fc

File tree

6 files changed

+88
-67
lines changed

6 files changed

+88
-67
lines changed

src/addrdb.cpp

Lines changed: 54 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,15 @@
1818
#include <univalue.h>
1919
#include <util/settings.h>
2020
#include <util/system.h>
21+
#include <util/translation.h>
2122

2223
namespace {
24+
25+
class DbNotFoundError : public std::exception
26+
{
27+
using std::exception::exception;
28+
};
29+
2330
template <typename Stream, typename Data>
2431
bool SerializeDB(Stream& stream, const Data& data)
2532
{
@@ -77,47 +84,40 @@ bool SerializeFileDB(const std::string& prefix, const fs::path& path, const Data
7784
}
7885

7986
template <typename Stream, typename Data>
80-
bool DeserializeDB(Stream& stream, Data& data, bool fCheckSum = true)
87+
void DeserializeDB(Stream& stream, Data& data, bool fCheckSum = true)
8188
{
82-
try {
83-
CHashVerifier<Stream> verifier(&stream);
84-
// de-serialize file header (network specific magic number) and ..
85-
unsigned char pchMsgTmp[4];
86-
verifier >> pchMsgTmp;
87-
// ... verify the network matches ours
88-
if (memcmp(pchMsgTmp, Params().MessageStart(), sizeof(pchMsgTmp)))
89-
return error("%s: Invalid network magic number", __func__);
90-
91-
// de-serialize data
92-
verifier >> data;
93-
94-
// verify checksum
95-
if (fCheckSum) {
96-
uint256 hashTmp;
97-
stream >> hashTmp;
98-
if (hashTmp != verifier.GetHash()) {
99-
return error("%s: Checksum mismatch, data corrupted", __func__);
100-
}
101-
}
102-
}
103-
catch (const std::exception& e) {
104-
return error("%s: Deserialize or I/O error - %s", __func__, e.what());
89+
CHashVerifier<Stream> verifier(&stream);
90+
// de-serialize file header (network specific magic number) and ..
91+
unsigned char pchMsgTmp[4];
92+
verifier >> pchMsgTmp;
93+
// ... verify the network matches ours
94+
if (memcmp(pchMsgTmp, Params().MessageStart(), sizeof(pchMsgTmp))) {
95+
throw std::runtime_error{"Invalid network magic number"};
10596
}
10697

107-
return true;
98+
// de-serialize data
99+
verifier >> data;
100+
101+
// verify checksum
102+
if (fCheckSum) {
103+
uint256 hashTmp;
104+
stream >> hashTmp;
105+
if (hashTmp != verifier.GetHash()) {
106+
throw std::runtime_error{"Checksum mismatch, data corrupted"};
107+
}
108+
}
108109
}
109110

110111
template <typename Data>
111-
bool DeserializeFileDB(const fs::path& path, Data& data, int version)
112+
void DeserializeFileDB(const fs::path& path, Data& data, int version)
112113
{
113114
// open input file, and associate with CAutoFile
114115
FILE* file = fsbridge::fopen(path, "rb");
115116
CAutoFile filein(file, SER_DISK, version);
116117
if (filein.IsNull()) {
117-
LogPrintf("Missing or invalid file %s\n", path.string());
118-
return false;
118+
throw DbNotFoundError{};
119119
}
120-
return DeserializeDB(filein, data);
120+
DeserializeDB(filein, data);
121121
}
122122
} // namespace
123123

@@ -176,15 +176,32 @@ bool DumpPeerAddresses(const ArgsManager& args, const CAddrMan& addr)
176176
return SerializeFileDB("peers", pathAddr, addr, CLIENT_VERSION);
177177
}
178178

179-
bool ReadPeerAddresses(const ArgsManager& args, CAddrMan& addr)
179+
void ReadFromStream(CAddrMan& addr, CDataStream& ssPeers)
180180
{
181-
const auto pathAddr = args.GetDataDirNet() / "peers.dat";
182-
return DeserializeFileDB(pathAddr, addr, CLIENT_VERSION);
181+
DeserializeDB(ssPeers, addr, false);
183182
}
184183

185-
bool ReadFromStream(CAddrMan& addr, CDataStream& ssPeers)
184+
std::optional<bilingual_str> LoadAddrman(const std::vector<bool>& asmap, const ArgsManager& args, std::unique_ptr<CAddrMan>& addrman)
186185
{
187-
return DeserializeDB(ssPeers, addr, false);
186+
auto check_addrman = std::clamp<int32_t>(args.GetArg("-checkaddrman", DEFAULT_ADDRMAN_CONSISTENCY_CHECKS), 0, 1000000);
187+
addrman = std::make_unique<CAddrMan>(asmap, /* deterministic */ false, /* consistency_check_ratio */ check_addrman);
188+
189+
int64_t nStart = GetTimeMillis();
190+
const auto path_addr{args.GetDataDirNet() / "peers.dat"};
191+
try {
192+
DeserializeFileDB(path_addr, *addrman, CLIENT_VERSION);
193+
LogPrintf("Loaded %i addresses from peers.dat %dms\n", addrman->size(), GetTimeMillis() - nStart);
194+
} catch (const DbNotFoundError&) {
195+
// Addrman can be in an inconsistent state after failure, reset it
196+
addrman = std::make_unique<CAddrMan>(asmap, /* deterministic */ false, /* consistency_check_ratio */ check_addrman);
197+
LogPrintf("Creating peers.dat because the file was not found (%s)\n", path_addr);
198+
DumpPeerAddresses(args, *addrman);
199+
} catch (const std::exception& e) {
200+
addrman = nullptr;
201+
return strprintf(_("Invalid or corrupt peers.dat (%s). If you believe this is a bug, please report it to %s. As a workaround, you can move the file (%s) out of the way (rename, move, or delete) to have a new one created on the next start."),
202+
e.what(), PACKAGE_BUGREPORT, path_addr);
203+
}
204+
return std::nullopt;
188205
}
189206

190207
void DumpAnchors(const fs::path& anchors_db_path, const std::vector<CAddress>& anchors)
@@ -196,9 +213,10 @@ void DumpAnchors(const fs::path& anchors_db_path, const std::vector<CAddress>& a
196213
std::vector<CAddress> ReadAnchors(const fs::path& anchors_db_path)
197214
{
198215
std::vector<CAddress> anchors;
199-
if (DeserializeFileDB(anchors_db_path, anchors, CLIENT_VERSION | ADDRV2_FORMAT)) {
216+
try {
217+
DeserializeFileDB(anchors_db_path, anchors, CLIENT_VERSION | ADDRV2_FORMAT);
200218
LogPrintf("Loaded %i addresses from %s\n", anchors.size(), anchors_db_path.filename());
201-
} else {
219+
} catch (const std::exception&) {
202220
anchors.clear();
203221
}
204222

src/addrdb.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,18 @@
1010
#include <net_types.h> // For banmap_t
1111
#include <univalue.h>
1212

13+
#include <optional>
1314
#include <vector>
1415

1516
class ArgsManager;
1617
class CAddrMan;
1718
class CAddress;
1819
class CDataStream;
20+
struct bilingual_str;
1921

2022
bool DumpPeerAddresses(const ArgsManager& args, const CAddrMan& addr);
21-
bool ReadPeerAddresses(const ArgsManager& args, CAddrMan& addr);
2223
/** Only used by tests. */
23-
bool ReadFromStream(CAddrMan& addr, CDataStream& ssPeers);
24+
void ReadFromStream(CAddrMan& addr, CDataStream& ssPeers);
2425

2526
/** Access to the banlist database (banlist.json) */
2627
class CBanDB
@@ -46,6 +47,9 @@ class CBanDB
4647
bool Read(banmap_t& banSet);
4748
};
4849

50+
/** Returns an error string on failure */
51+
std::optional<bilingual_str> LoadAddrman(const std::vector<bool>& asmap, const ArgsManager& args, std::unique_ptr<CAddrMan>& addrman);
52+
4953
/**
5054
* Dump the anchor IP address database (anchors.dat)
5155
*

src/init.cpp

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1200,19 +1200,9 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
12001200
LogPrintf("Using /16 prefix for IP bucketing\n");
12011201
}
12021202

1203-
auto check_addrman = std::clamp<int32_t>(args.GetArg("-checkaddrman", DEFAULT_ADDRMAN_CONSISTENCY_CHECKS), 0, 1000000);
1204-
node.addrman = std::make_unique<CAddrMan>(asmap, /* deterministic */ false, /* consistency_check_ratio */ check_addrman);
1205-
1206-
// Load addresses from peers.dat
12071203
uiInterface.InitMessage(_("Loading P2P addresses…").translated);
1208-
int64_t nStart = GetTimeMillis();
1209-
if (ReadPeerAddresses(args, *node.addrman)) {
1210-
LogPrintf("Loaded %i addresses from peers.dat %dms\n", node.addrman->size(), GetTimeMillis() - nStart);
1211-
} else {
1212-
// Addrman can be in an inconsistent state after failure, reset it
1213-
node.addrman = std::make_unique<CAddrMan>(asmap, /* deterministic */ false, /* consistency_check_ratio */ check_addrman);
1214-
LogPrintf("Recreating peers.dat\n");
1215-
DumpPeerAddresses(args, *node.addrman);
1204+
if (const auto error{LoadAddrman(asmap, args, node.addrman)}) {
1205+
return InitError(*error);
12161206
}
12171207
}
12181208

src/test/addrman_tests.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1043,7 +1043,7 @@ BOOST_AUTO_TEST_CASE(load_addrman)
10431043

10441044
CAddrMan addrman2(/* asmap */ std::vector<bool>(), /* deterministic */ false, /* consistency_check_ratio */ 100);
10451045
BOOST_CHECK(addrman2.size() == 0);
1046-
BOOST_CHECK(ReadFromStream(addrman2, ssPeers2));
1046+
ReadFromStream(addrman2, ssPeers2);
10471047
BOOST_CHECK(addrman2.size() == 3);
10481048
}
10491049

@@ -1073,7 +1073,7 @@ BOOST_AUTO_TEST_CASE(load_addrman_corrupted)
10731073

10741074
CAddrMan addrman2(/* asmap */ std::vector<bool>(), /* deterministic */ false, /* consistency_check_ratio */ 100);
10751075
BOOST_CHECK(addrman2.size() == 0);
1076-
BOOST_CHECK(!ReadFromStream(addrman2, ssPeers2));
1076+
BOOST_CHECK_THROW(ReadFromStream(addrman2, ssPeers2), std::ios_base::failure);
10771077
}
10781078

10791079

src/test/fuzz/data_stream.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,8 @@ FUZZ_TARGET_INIT(data_stream_addr_man, initialize_data_stream_addr_man)
2323
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
2424
CDataStream data_stream = ConsumeDataStream(fuzzed_data_provider);
2525
CAddrMan addr_man(/* asmap */ std::vector<bool>(), /* deterministic */ false, /* consistency_check_ratio */ 0);
26-
ReadFromStream(addr_man, data_stream);
26+
try {
27+
ReadFromStream(addr_man, data_stream);
28+
} catch (const std::exception&) {
29+
}
2730
}

test/functional/feature_addrman.py

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from test_framework.messages import ser_uint256, hash256
1111
from test_framework.p2p import MAGIC_BYTES
1212
from test_framework.test_framework import BitcoinTestFramework
13+
from test_framework.test_node import ErrorMatch
1314
from test_framework.util import assert_equal
1415

1516

@@ -43,6 +44,12 @@ def set_test_params(self):
4344

4445
def run_test(self):
4546
peers_dat = os.path.join(self.nodes[0].datadir, self.chain, "peers.dat")
47+
init_error = lambda reason: (
48+
f"Error: Invalid or corrupt peers.dat \\({reason}\\). If you believe this "
49+
f"is a bug, please report it to {self.config['environment']['PACKAGE_BUGREPORT']}. "
50+
f'As a workaround, you can move the file \\("{peers_dat}"\\) out of the way \\(rename, '
51+
"move, or delete\\) to have a new one created on the next start."
52+
)
4653

4754
self.log.info("Check that mocked addrman is valid")
4855
self.stop_node(0)
@@ -54,30 +61,29 @@ def run_test(self):
5461
self.log.info("Check that addrman from future cannot be read")
5562
self.stop_node(0)
5663
write_addrman(peers_dat, lowest_compatible=111)
57-
with self.nodes[0].assert_debug_log([
58-
f'ERROR: DeserializeDB: Deserialize or I/O error - Unsupported format of addrman database: 1. It is compatible with formats >=111, but the maximum supported by this version of {self.config["environment"]["PACKAGE_NAME"]} is 3.',
59-
"Recreating peers.dat",
60-
]):
61-
self.start_node(0)
62-
assert_equal(self.nodes[0].getnodeaddresses(), [])
64+
self.nodes[0].assert_start_raises_init_error(
65+
expected_msg=init_error(
66+
"Unsupported format of addrman database: 1. It is compatible with "
67+
"formats >=111, but the maximum supported by this version of "
68+
f"{self.config['environment']['PACKAGE_NAME']} is 3.: (.+)"
69+
),
70+
match=ErrorMatch.FULL_REGEX,
71+
)
6372

6473
self.log.info("Check that corrupt addrman cannot be read")
6574
self.stop_node(0)
6675
with open(peers_dat, "wb") as f:
6776
f.write(serialize_addrman()[:-1])
68-
with self.nodes[0].assert_debug_log([
69-
"ERROR: DeserializeDB: Deserialize or I/O error - CAutoFile::read: end of file",
70-
"Recreating peers.dat",
71-
]):
72-
self.start_node(0)
73-
assert_equal(self.nodes[0].getnodeaddresses(), [])
77+
self.nodes[0].assert_start_raises_init_error(
78+
expected_msg=init_error("CAutoFile::read: end of file.*"),
79+
match=ErrorMatch.FULL_REGEX,
80+
)
7481

7582
self.log.info("Check that missing addrman is recreated")
7683
self.stop_node(0)
7784
os.remove(peers_dat)
7885
with self.nodes[0].assert_debug_log([
79-
f"Missing or invalid file {peers_dat}",
80-
"Recreating peers.dat",
86+
f'Creating peers.dat because the file was not found ("{peers_dat}")',
8187
]):
8288
self.start_node(0)
8389
assert_equal(self.nodes[0].getnodeaddresses(), [])

0 commit comments

Comments
 (0)