addrdb.cpp raw
1 // Copyright (c) 2009-2010 Satoshi Nakamoto
2 // Copyright (c) 2009-present 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 <bitcoin-build-config.h> // IWYU pragma: keep
7
8 #include <addrdb.h>
9
10 #include <addrman.h>
11 #include <chainparams.h>
12 #include <clientversion.h>
13 #include <common/args.h>
14 #include <common/settings.h>
15 #include <cstdint>
16 #include <hash.h>
17 #include <logging/timer.h>
18 #include <netbase.h>
19 #include <netgroup.h>
20 #include <random.h>
21 #include <streams.h>
22 #include <tinyformat.h>
23 #include <univalue.h>
24 #include <util/fs.h>
25 #include <util/fs_helpers.h>
26 #include <util/log.h>
27 #include <util/syserror.h>
28 #include <util/translation.h>
29
30 namespace {
31
32 class DbNotFoundError : public std::exception
33 {
34 using std::exception::exception;
35 };
36
37 template <typename Stream, typename Data>
38 bool SerializeDB(Stream& stream, const Data& data)
39 {
40 // Write and commit header, data
41 try {
42 HashedSourceWriter hashwriter{stream};
43 hashwriter << Params().MessageStart() << data;
44 stream << hashwriter.GetHash();
45 } catch (const std::exception& e) {
46 LogError("%s: Serialize or I/O error - %s\n", __func__, e.what());
47 return false;
48 }
49
50 return true;
51 }
52
53 template <typename Data>
54 bool SerializeFileDB(const std::string& prefix, const fs::path& path, const Data& data)
55 {
56 // Generate random temporary filename
57 const uint16_t randv{FastRandomContext().rand<uint16_t>()};
58 std::string tmpfn = strprintf("%s.%04x", prefix, randv);
59
60 // open temp output file
61 fs::path pathTmp = gArgs.GetDataDirNet() / fs::u8path(tmpfn);
62 FILE *file = fsbridge::fopen(pathTmp, "wb");
63 AutoFile fileout{file};
64 if (fileout.IsNull()) {
65 remove(pathTmp);
66 LogError("%s: Failed to open file %s\n", __func__, fs::PathToString(pathTmp));
67 return false;
68 }
69
70 // Serialize
71 if (!SerializeDB(fileout, data)) {
72 (void)fileout.fclose();
73 remove(pathTmp);
74 return false;
75 }
76 if (!fileout.Commit()) {
77 (void)fileout.fclose();
78 remove(pathTmp);
79 LogError("%s: Failed to flush file %s\n", __func__, fs::PathToString(pathTmp));
80 return false;
81 }
82 if (fileout.fclose() != 0) {
83 const int errno_save{errno};
84 remove(pathTmp);
85 LogError("Failed to close file %s after commit: %s", fs::PathToString(pathTmp), SysErrorString(errno_save));
86 return false;
87 }
88
89 // replace existing file, if any, with new file
90 if (!RenameOver(pathTmp, path)) {
91 remove(pathTmp);
92 LogError("%s: Rename-into-place failed\n", __func__);
93 return false;
94 }
95
96 return true;
97 }
98
99 template <typename Stream, typename Data>
100 void DeserializeDB(Stream& stream, Data&& data, bool fCheckSum = true)
101 {
102 HashVerifier verifier{stream};
103 // de-serialize file header (network specific magic number) and ..
104 MessageStartChars pchMsgTmp;
105 verifier >> pchMsgTmp;
106 // ... verify the network matches ours
107 if (pchMsgTmp != Params().MessageStart()) {
108 throw std::runtime_error{"Invalid network magic number"};
109 }
110
111 // de-serialize data
112 verifier >> data;
113
114 // verify checksum
115 if (fCheckSum) {
116 uint256 hashTmp;
117 stream >> hashTmp;
118 if (hashTmp != verifier.GetHash()) {
119 throw std::runtime_error{"Checksum mismatch, data corrupted"};
120 }
121 }
122 }
123
124 template <typename Data>
125 void DeserializeFileDB(const fs::path& path, Data&& data)
126 {
127 FILE* file = fsbridge::fopen(path, "rb");
128 AutoFile filein{file};
129 if (filein.IsNull()) {
130 throw DbNotFoundError{};
131 }
132 DeserializeDB(filein, data);
133 }
134 } // namespace
135
136 CBanDB::CBanDB(fs::path ban_list_path)
137 : m_banlist_dat(ban_list_path + ".dat"),
138 m_banlist_json(ban_list_path + ".json")
139 {
140 }
141
142 bool CBanDB::Write(const banmap_t& banSet)
143 {
144 std::vector<std::string> errors;
145 if (common::WriteSettings(m_banlist_json, {{JSON_KEY, BanMapToJson(banSet)}}, errors)) {
146 return true;
147 }
148
149 for (const auto& err : errors) {
150 LogError("%s\n", err);
151 }
152 return false;
153 }
154
155 bool CBanDB::Read(banmap_t& banSet)
156 {
157 if (fs::exists(m_banlist_dat)) {
158 LogWarning("banlist.dat ignored because it can only be read by " CLIENT_NAME " version 22.x. Remove %s to silence this warning.", fs::quoted(fs::PathToString(m_banlist_dat)));
159 }
160 // If the JSON banlist does not exist, then recreate it
161 if (!fs::exists(m_banlist_json)) {
162 return false;
163 }
164
165 std::map<std::string, common::SettingsValue> settings;
166 std::vector<std::string> errors;
167
168 if (!common::ReadSettings(m_banlist_json, settings, errors)) {
169 for (const auto& err : errors) {
170 LogWarning("Cannot load banlist %s: %s", fs::PathToString(m_banlist_json), err);
171 }
172 return false;
173 }
174
175 try {
176 BanMapFromJson(settings[JSON_KEY], banSet);
177 } catch (const std::runtime_error& e) {
178 LogWarning("Cannot parse banlist %s: %s", fs::PathToString(m_banlist_json), e.what());
179 return false;
180 }
181
182 return true;
183 }
184
185 bool DumpPeerAddresses(const ArgsManager& args, const AddrMan& addr)
186 {
187 const auto pathAddr = args.GetDataDirNet() / "peers.dat";
188 return SerializeFileDB("peers", pathAddr, addr);
189 }
190
191 void ReadFromStream(AddrMan& addr, DataStream& ssPeers)
192 {
193 DeserializeDB(ssPeers, addr, false);
194 }
195
196 util::Result<std::unique_ptr<AddrMan>> LoadAddrman(const NetGroupManager& netgroupman, const ArgsManager& args)
197 {
198 auto check_addrman = std::clamp<int32_t>(args.GetIntArg("-checkaddrman", DEFAULT_ADDRMAN_CONSISTENCY_CHECKS), 0, 1000000);
199 bool deterministic = HasTestOption(args, "addrman"); // use a deterministic addrman only for tests
200
201 auto addrman{std::make_unique<AddrMan>(netgroupman, deterministic, /*consistency_check_ratio=*/check_addrman)};
202
203 const auto start{SteadyClock::now()};
204 const auto path_addr{args.GetDataDirNet() / "peers.dat"};
205 try {
206 DeserializeFileDB(path_addr, *addrman);
207 LogInfo("Loaded %i addresses from peers.dat %dms", addrman->Size(), Ticks<std::chrono::milliseconds>(SteadyClock::now() - start));
208 } catch (const DbNotFoundError&) {
209 // Addrman can be in an inconsistent state after failure, reset it
210 addrman = std::make_unique<AddrMan>(netgroupman, deterministic, /*consistency_check_ratio=*/check_addrman);
211 LogInfo("Creating peers.dat because the file was not found (%s)", fs::quoted(fs::PathToString(path_addr)));
212 DumpPeerAddresses(args, *addrman);
213 } catch (const InvalidAddrManVersionError&) {
214 if (!RenameOver(path_addr, (fs::path)path_addr + ".bak")) {
215 return util::Error{strprintf(_("Failed to rename invalid peers.dat file. Please move or delete it and try again."))};
216 }
217 // Addrman can be in an inconsistent state after failure, reset it
218 addrman = std::make_unique<AddrMan>(netgroupman, deterministic, /*consistency_check_ratio=*/check_addrman);
219 LogWarning("Creating new peers.dat because the file version was not compatible (%s). Original backed up to peers.dat.bak", fs::quoted(fs::PathToString(path_addr)));
220 DumpPeerAddresses(args, *addrman);
221 } catch (const std::exception& e) {
222 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."),
223 e.what(), CLIENT_BUGREPORT, fs::quoted(fs::PathToString(path_addr)))};
224 }
225 return addrman;
226 }
227
228 void DumpAnchors(const fs::path& anchors_db_path, const std::vector<CAddress>& anchors)
229 {
230 LOG_TIME_SECONDS(strprintf("Flush %d outbound block-relay-only peer addresses to anchors.dat", anchors.size()));
231 SerializeFileDB("anchors", anchors_db_path, CAddress::V2_DISK(anchors));
232 }
233
234 std::vector<CAddress> ReadAnchors(const fs::path& anchors_db_path)
235 {
236 std::vector<CAddress> anchors;
237 try {
238 DeserializeFileDB(anchors_db_path, CAddress::V2_DISK(anchors));
239 LogInfo("Loaded %i addresses from %s", anchors.size(), fs::quoted(fs::PathToString(anchors_db_path.filename())));
240 } catch (const std::exception&) {
241 anchors.clear();
242 }
243
244 fs::remove(anchors_db_path);
245 return anchors;
246 }
247