Line data Source code
1 : // Copyright (c) 2009-2010 Satoshi Nakamoto
2 : // Copyright (c) 2009-2021 The Bitcoin Core developers
3 : // Distributed under the MIT software license, see the accompanying
4 : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 :
6 : #include <addrdb.h>
7 :
8 : #include <addrman.h>
9 : #include <chainparams.h>
10 : #include <clientversion.h>
11 : #include <fs.h>
12 : #include <hash.h>
13 : #include <logging/timer.h>
14 : #include <netbase.h>
15 : #include <netgroup.h>
16 : #include <random.h>
17 : #include <streams.h>
18 : #include <tinyformat.h>
19 : #include <univalue.h>
20 : #include <util/settings.h>
21 : #include <util/system.h>
22 : #include <util/translation.h>
23 :
24 : #include <cstdint>
25 :
26 : namespace {
27 :
28 : class DbNotFoundError : public std::exception
29 : {
30 : using std::exception::exception;
31 : };
32 :
33 : template <typename Stream, typename Data>
34 0 : bool SerializeDB(Stream& stream, const Data& data)
35 : {
36 : // Write and commit header, data
37 : try {
38 0 : HashedSourceWriter hashwriter{stream};
39 0 : hashwriter << Params().MessageStart() << data;
40 0 : stream << hashwriter.GetHash();
41 0 : } catch (const std::exception& e) {
42 0 : return error("%s: Serialize or I/O error - %s", __func__, e.what());
43 0 : }
44 :
45 0 : return true;
46 0 : }
47 :
48 : template <typename Data>
49 0 : bool SerializeFileDB(const std::string& prefix, const fs::path& path, const Data& data, int version)
50 : {
51 : // Generate random temporary filename
52 0 : const uint16_t randv{GetRand<uint16_t>()};
53 0 : std::string tmpfn = strprintf("%s.%04x", prefix, randv);
54 :
55 : // open temp output file, and associate with CAutoFile
56 0 : fs::path pathTmp = gArgs.GetDataDirNet() / fs::u8path(tmpfn);
57 0 : FILE *file = fsbridge::fopen(pathTmp, "wb");
58 0 : CAutoFile fileout(file, SER_DISK, version);
59 0 : if (fileout.IsNull()) {
60 0 : fileout.fclose();
61 0 : remove(pathTmp);
62 0 : return error("%s: Failed to open file %s", __func__, fs::PathToString(pathTmp));
63 : }
64 :
65 : // Serialize
66 0 : if (!SerializeDB(fileout, data)) {
67 0 : fileout.fclose();
68 0 : remove(pathTmp);
69 0 : return false;
70 : }
71 0 : if (!FileCommit(fileout.Get())) {
72 0 : fileout.fclose();
73 0 : remove(pathTmp);
74 0 : return error("%s: Failed to flush file %s", __func__, fs::PathToString(pathTmp));
75 : }
76 0 : fileout.fclose();
77 :
78 : // replace existing file, if any, with new file
79 0 : if (!RenameOver(pathTmp, path)) {
80 0 : remove(pathTmp);
81 0 : return error("%s: Rename-into-place failed", __func__);
82 : }
83 :
84 0 : return true;
85 0 : }
86 :
87 : template <typename Stream, typename Data>
88 2 : void DeserializeDB(Stream& stream, Data& data, bool fCheckSum = true)
89 : {
90 2 : CHashVerifier<Stream> verifier(&stream);
91 : // de-serialize file header (network specific magic number) and ..
92 : unsigned char pchMsgTmp[4];
93 2 : verifier >> pchMsgTmp;
94 : // ... verify the network matches ours
95 2 : if (memcmp(pchMsgTmp, Params().MessageStart(), sizeof(pchMsgTmp))) {
96 0 : throw std::runtime_error{"Invalid network magic number"};
97 : }
98 :
99 : // de-serialize data
100 2 : verifier >> data;
101 :
102 : // verify checksum
103 2 : if (fCheckSum) {
104 0 : uint256 hashTmp;
105 0 : stream >> hashTmp;
106 0 : if (hashTmp != verifier.GetHash()) {
107 0 : throw std::runtime_error{"Checksum mismatch, data corrupted"};
108 : }
109 0 : }
110 2 : }
111 :
112 : template <typename Data>
113 0 : void DeserializeFileDB(const fs::path& path, Data& data, int version)
114 : {
115 : // open input file, and associate with CAutoFile
116 0 : FILE* file = fsbridge::fopen(path, "rb");
117 0 : CAutoFile filein(file, SER_DISK, version);
118 0 : if (filein.IsNull()) {
119 0 : throw DbNotFoundError{};
120 : }
121 0 : DeserializeDB(filein, data);
122 0 : }
123 : } // namespace
124 :
125 362 : CBanDB::CBanDB(fs::path ban_list_path)
126 181 : : m_banlist_dat(ban_list_path + ".dat"),
127 181 : m_banlist_json(ban_list_path + ".json")
128 181 : {
129 362 : }
130 :
131 195 : bool CBanDB::Write(const banmap_t& banSet)
132 : {
133 195 : std::vector<std::string> errors;
134 195 : if (util::WriteSettings(m_banlist_json, {{JSON_KEY, BanMapToJson(banSet)}}, errors)) {
135 195 : return true;
136 : }
137 :
138 0 : for (const auto& err : errors) {
139 0 : error("%s", err);
140 : }
141 0 : return false;
142 195 : }
143 :
144 181 : bool CBanDB::Read(banmap_t& banSet)
145 : {
146 181 : if (fs::exists(m_banlist_dat)) {
147 0 : LogPrintf("banlist.dat ignored because it can only be read by " PACKAGE_NAME " version 19.x. Remove %s to silence this warning.\n", fs::quoted(fs::PathToString(m_banlist_dat)));
148 0 : }
149 : // If the JSON banlist does not exist, then recreate it
150 181 : if (!fs::exists(m_banlist_json)) {
151 178 : return false;
152 : }
153 :
154 3 : std::map<std::string, util::SettingsValue> settings;
155 3 : std::vector<std::string> errors;
156 :
157 3 : if (!util::ReadSettings(m_banlist_json, settings, errors)) {
158 0 : for (const auto& err : errors) {
159 0 : LogPrintf("Cannot load banlist %s: %s\n", fs::PathToString(m_banlist_json), err);
160 : }
161 0 : return false;
162 : }
163 :
164 : try {
165 3 : BanMapFromJson(settings[JSON_KEY], banSet);
166 3 : } catch (const std::runtime_error& e) {
167 0 : LogPrintf("Cannot parse banlist %s: %s\n", fs::PathToString(m_banlist_json), e.what());
168 0 : return false;
169 0 : }
170 :
171 3 : return true;
172 181 : }
173 :
174 0 : bool DumpPeerAddresses(const ArgsManager& args, const AddrMan& addr)
175 : {
176 0 : const auto pathAddr = gArgs.GetDataDirNet() / "peers.dat";
177 0 : return SerializeFileDB("peers", pathAddr, addr, CLIENT_VERSION);
178 0 : }
179 :
180 2 : void ReadFromStream(AddrMan& addr, CDataStream& ssPeers)
181 : {
182 2 : DeserializeDB(ssPeers, addr, false);
183 2 : }
184 :
185 0 : util::Result<std::unique_ptr<AddrMan>> LoadAddrman(const NetGroupManager& netgroupman, const ArgsManager& args)
186 : {
187 0 : auto check_addrman = std::clamp<int32_t>(args.GetIntArg("-checkaddrman", DEFAULT_ADDRMAN_CONSISTENCY_CHECKS), 0, 1000000);
188 0 : auto addrman{std::make_unique<AddrMan>(netgroupman, /*deterministic=*/false, /*consistency_check_ratio=*/check_addrman)};
189 :
190 0 : const auto start{SteadyClock::now()};
191 0 : const auto path_addr{gArgs.GetDataDirNet() / "peers.dat"};
192 : try {
193 0 : DeserializeFileDB(path_addr, *addrman, CLIENT_VERSION);
194 0 : LogPrintf("Loaded %i addresses from peers.dat %dms\n", addrman->Size(), Ticks<std::chrono::milliseconds>(SteadyClock::now() - start));
195 0 : } catch (const DbNotFoundError&) {
196 : // Addrman can be in an inconsistent state after failure, reset it
197 0 : addrman = std::make_unique<AddrMan>(netgroupman, /*deterministic=*/false, /*consistency_check_ratio=*/check_addrman);
198 0 : LogPrintf("Creating peers.dat because the file was not found (%s)\n", fs::quoted(fs::PathToString(path_addr)));
199 0 : DumpPeerAddresses(args, *addrman);
200 0 : } catch (const DbInconsistentError& e) {
201 : // Addrman has shown a tendency to corrupt itself even with graceful shutdowns on known-good
202 : // hardware. As the user would have to delete and recreate a new database regardless to cope
203 : // with frequent corruption, we are restoring old behaviour that does the same, silently.
204 : //
205 : // TODO: Evaluate cause and fix, revert this change at some point.
206 0 : addrman = std::make_unique<AddrMan>(netgroupman, /*deterministic=*/false, /*consistency_check_ratio=*/check_addrman);
207 0 : LogPrintf("Creating peers.dat because of invalid or corrupt file (%s)\n", e.what());
208 0 : DumpPeerAddresses(args, *addrman);
209 0 : } catch (const InvalidAddrManVersionError&) {
210 0 : if (!RenameOver(path_addr, (fs::path)path_addr + ".bak")) {
211 0 : return util::Error{strprintf(_("Failed to rename invalid peers.dat file. Please move or delete it and try again."))};
212 : }
213 : // Addrman can be in an inconsistent state after failure, reset it
214 0 : addrman = std::make_unique<AddrMan>(netgroupman, /*deterministic=*/false, /*consistency_check_ratio=*/check_addrman);
215 0 : LogPrintf("Creating new peers.dat because the file version was not compatible (%s). Original backed up to peers.dat.bak\n", fs::quoted(fs::PathToString(path_addr)));
216 0 : DumpPeerAddresses(args, *addrman);
217 0 : } catch (const std::exception& e) {
218 0 : return util::Error{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."),
219 0 : e.what(), PACKAGE_BUGREPORT, fs::quoted(fs::PathToString(path_addr)))};
220 0 : }
221 0 : return addrman; // std::move should be unneccessary but is temporarily needed to work around clang bug (https://github.com/bitcoin/bitcoin/pull/25977#issuecomment-1561270092)
222 0 : }
223 :
224 0 : void DumpAnchors(const fs::path& anchors_db_path, const std::vector<CAddress>& anchors)
225 : {
226 0 : LOG_TIME_SECONDS(strprintf("Flush %d outbound block-relay-only peer addresses to anchors.dat", anchors.size()));
227 0 : SerializeFileDB("anchors", anchors_db_path, anchors, CLIENT_VERSION | ADDRV2_FORMAT);
228 0 : }
229 :
230 0 : std::vector<CAddress> ReadAnchors(const fs::path& anchors_db_path)
231 : {
232 0 : std::vector<CAddress> anchors;
233 : try {
234 0 : DeserializeFileDB(anchors_db_path, anchors, CLIENT_VERSION | ADDRV2_FORMAT);
235 0 : LogPrintf("Loaded %i addresses from %s\n", anchors.size(), fs::quoted(fs::PathToString(anchors_db_path.filename())));
236 0 : } catch (const std::exception&) {
237 0 : anchors.clear();
238 0 : }
239 :
240 0 : fs::remove(anchors_db_path);
241 0 : return anchors;
242 0 : }
|