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