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 <addrdb.h>
7 : :
8 : : #include <addrman.h>
9 : : #include <chainparams.h>
10 : : #include <clientversion.h>
11 : : #include <common/args.h>
12 : : #include <common/settings.h>
13 : : #include <cstdint>
14 : : #include <hash.h>
15 : : #include <logging.h>
16 : : #include <logging/timer.h>
17 : : #include <netbase.h>
18 : : #include <netgroup.h>
19 : : #include <random.h>
20 : : #include <streams.h>
21 : : #include <tinyformat.h>
22 : : #include <univalue.h>
23 : : #include <util/fs.h>
24 : : #include <util/fs_helpers.h>
25 : : #include <util/translation.h>
26 : :
27 : : namespace {
28 : :
29 : 0 : class DbNotFoundError : public std::exception
30 : : {
31 : : using std::exception::exception;
32 : : };
33 : :
34 : : template <typename Stream, typename Data>
35 : 2 : bool SerializeDB(Stream& stream, const Data& data)
36 : : {
37 : : // Write and commit header, data
38 : : try {
39 [ + - ][ + - ]: 2 : HashedSourceWriter hashwriter{stream};
40 [ + - ][ + - ]: 2 : hashwriter << Params().MessageStart() << data;
[ + - ][ + - ]
[ + - ][ + - ]
[ + - ]
41 [ + - ][ + - ]: 2 : stream << hashwriter.GetHash();
[ + - ][ + - ]
42 [ # # ][ # # ]: 2 : } catch (const std::exception& e) {
43 [ # # ][ # # ]: 0 : return error("%s: Serialize or I/O error - %s", __func__, e.what());
44 [ # # ][ # # ]: 0 : }
45 : :
46 : 2 : return true;
47 : 2 : }
48 : :
49 : : template <typename Data>
50 : 2 : bool SerializeFileDB(const std::string& prefix, const fs::path& path, const Data& data)
51 : : {
52 : : // Generate random temporary filename
53 : 2 : const uint16_t randv{GetRand<uint16_t>()};
54 : 2 : std::string tmpfn = strprintf("%s.%04x", prefix, randv);
55 : :
56 : : // open temp output file
57 [ + - ][ + - ]: 2 : fs::path pathTmp = gArgs.GetDataDirNet() / fs::u8path(tmpfn);
[ + - ][ + - ]
[ + - ][ + - ]
[ + - ][ + - ]
58 [ + - ][ + - ]: 2 : FILE *file = fsbridge::fopen(pathTmp, "wb");
59 [ + - ][ + - ]: 2 : AutoFile fileout{file};
60 [ + - ][ - + ]: 2 : if (fileout.IsNull()) {
[ + - ]
61 [ # # ][ # # ]: 0 : fileout.fclose();
62 [ # # ][ # # ]: 0 : remove(pathTmp);
63 [ # # ][ # # ]: 0 : return error("%s: Failed to open file %s", __func__, fs::PathToString(pathTmp));
[ # # ][ # # ]
64 : : }
65 : :
66 : : // Serialize
67 [ + - ][ - + ]: 2 : if (!SerializeDB(fileout, data)) {
[ + - ][ - + ]
68 [ # # ][ # # ]: 0 : fileout.fclose();
69 [ # # ][ # # ]: 0 : remove(pathTmp);
70 : 0 : return false;
71 : : }
72 [ + - ][ + - ]: 2 : if (!FileCommit(fileout.Get())) {
[ - + ][ + - ]
[ - + ]
73 [ # # ][ # # ]: 0 : fileout.fclose();
74 [ # # ][ # # ]: 0 : remove(pathTmp);
75 [ # # ][ # # ]: 0 : return error("%s: Failed to flush file %s", __func__, fs::PathToString(pathTmp));
[ # # ][ # # ]
76 : : }
77 [ + - ][ + - ]: 2 : fileout.fclose();
78 : :
79 : : // replace existing file, if any, with new file
80 [ + - ][ + - ]: 2 : if (!RenameOver(pathTmp, path)) {
[ - + ][ - + ]
[ + - ][ + - ]
[ - + ][ - + ]
81 [ # # ][ # # ]: 0 : remove(pathTmp);
82 [ # # ][ # # ]: 0 : return error("%s: Rename-into-place failed", __func__);
83 : : }
84 : :
85 : 2 : return true;
86 : 2 : }
87 : :
88 : : template <typename Stream, typename Data>
89 : 0 : void DeserializeDB(Stream& stream, Data&& data, bool fCheckSum = true)
90 : : {
91 : 0 : HashVerifier verifier{stream};
92 : : // de-serialize file header (network specific magic number) and ..
93 : : MessageStartChars pchMsgTmp;
94 : 0 : verifier >> pchMsgTmp;
95 : : // ... verify the network matches ours
96 [ # # ][ # # ]: 0 : if (pchMsgTmp != Params().MessageStart()) {
[ # # ]
97 [ # # ][ # # ]: 0 : throw std::runtime_error{"Invalid network magic number"};
[ # # ]
98 : : }
99 : :
100 : : // de-serialize data
101 : 0 : verifier >> data;
102 : :
103 : : // verify checksum
104 [ # # ][ # # ]: 0 : if (fCheckSum) {
[ # # ]
105 : 0 : uint256 hashTmp;
106 : 0 : stream >> hashTmp;
107 [ # # ][ # # ]: 0 : if (hashTmp != verifier.GetHash()) {
[ # # ]
108 [ # # ][ # # ]: 0 : throw std::runtime_error{"Checksum mismatch, data corrupted"};
[ # # ]
109 : : }
110 : 0 : }
111 : 0 : }
112 : :
113 : : template <typename Data>
114 : 1 : void DeserializeFileDB(const fs::path& path, Data&& data)
115 : : {
116 : 1 : FILE* file = fsbridge::fopen(path, "rb");
117 [ # # ][ + - ]: 2 : AutoFile filein{file};
118 [ # # ][ + - ]: 1 : if (filein.IsNull()) {
119 [ # # ][ - + ]: 1 : throw DbNotFoundError{};
120 : : }
121 [ # # ][ # # ]: 0 : DeserializeDB(filein, data);
122 : 1 : }
123 : : } // namespace
124 : :
125 : 1 : CBanDB::CBanDB(fs::path ban_list_path)
126 [ + - ]: 1 : : m_banlist_dat(ban_list_path + ".dat"),
127 [ + - ][ - + ]: 1 : m_banlist_json(ban_list_path + ".json")
128 : : {
129 : 1 : }
130 : :
131 : 1 : bool CBanDB::Write(const banmap_t& banSet)
132 : : {
133 : 1 : std::vector<std::string> errors;
134 [ + - ][ + - ]: 1 : if (common::WriteSettings(m_banlist_json, {{JSON_KEY, BanMapToJson(banSet)}}, errors)) {
[ + - ][ - + ]
[ + - ][ # # ]
135 : 1 : return true;
136 : : }
137 : :
138 [ # # ]: 0 : for (const auto& err : errors) {
139 [ # # ]: 0 : error("%s", err);
140 : : }
141 : 0 : return false;
142 : 1 : }
143 : :
144 : 1 : bool CBanDB::Read(banmap_t& banSet)
145 : : {
146 [ + - ]: 1 : if (fs::exists(m_banlist_dat)) {
147 [ # # ][ # # ]: 0 : LogPrintf("banlist.dat ignored because it can only be read by " PACKAGE_NAME " version 22.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 [ - + ]: 1 : if (!fs::exists(m_banlist_json)) {
151 : 1 : return false;
152 : : }
153 : :
154 : 0 : std::map<std::string, common::SettingsValue> settings;
155 : 0 : std::vector<std::string> errors;
156 : :
157 [ # # ][ # # ]: 0 : if (!common::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 [ # # ][ # # ]: 0 : BanMapFromJson(settings[JSON_KEY], banSet);
[ # # ]
166 [ # # ]: 0 : } 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 : 0 : return true;
172 : 1 : }
173 : :
174 : 1 : bool DumpPeerAddresses(const ArgsManager& args, const AddrMan& addr)
175 : : {
176 [ + - ]: 1 : const auto pathAddr = args.GetDataDirNet() / "peers.dat";
177 [ + - ][ - + ]: 1 : return SerializeFileDB("peers", pathAddr, addr);
178 : 1 : }
179 : :
180 : 0 : void ReadFromStream(AddrMan& addr, DataStream& ssPeers)
181 : : {
182 : 0 : DeserializeDB(ssPeers, addr, false);
183 : 0 : }
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{args.GetDataDirNet() / "peers.dat"};
[ # # ]
192 : : try {
193 [ # # ]: 0 : DeserializeFileDB(path_addr, *addrman);
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 InvalidAddrManVersionError&) {
201 [ # # ][ # # ]: 0 : if (!RenameOver(path_addr, (fs::path)path_addr + ".bak")) {
[ # # ][ # # ]
[ # # ]
202 [ # # ][ # # ]: 0 : return util::Error{strprintf(_("Failed to rename invalid peers.dat file. Please move or delete it and try again."))};
[ # # ]
203 : : }
204 : : // Addrman can be in an inconsistent state after failure, reset it
205 [ # # ]: 0 : addrman = std::make_unique<AddrMan>(netgroupman, /*deterministic=*/false, /*consistency_check_ratio=*/check_addrman);
206 [ # # ][ # # ]: 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)));
[ # # ][ # # ]
[ # # ]
207 [ # # ]: 0 : DumpPeerAddresses(args, *addrman);
208 [ # # ][ # # ]: 0 : } catch (const std::exception& e) {
209 [ # # ][ # # ]: 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."),
[ # # ]
210 [ # # ][ # # ]: 0 : e.what(), PACKAGE_BUGREPORT, fs::quoted(fs::PathToString(path_addr)))};
211 [ # # ][ # # ]: 0 : }
212 [ # # ]: 0 : return {std::move(addrman)}; // std::move should be unnecessary but is temporarily needed to work around clang bug
213 : : // (https://github.com/bitcoin/bitcoin/pull/25977#issuecomment-1561270092)
214 : 0 : }
215 : :
216 : 1 : void DumpAnchors(const fs::path& anchors_db_path, const std::vector<CAddress>& anchors)
217 : : {
218 [ + - ][ + - ]: 1 : LOG_TIME_SECONDS(strprintf("Flush %d outbound block-relay-only peer addresses to anchors.dat", anchors.size()));
[ + - ]
219 [ - + ][ + - ]: 1 : SerializeFileDB("anchors", anchors_db_path, CAddress::V2_DISK(anchors));
[ + - ]
220 : 1 : }
221 : :
222 : 1 : std::vector<CAddress> ReadAnchors(const fs::path& anchors_db_path)
223 : : {
224 : 1 : std::vector<CAddress> anchors;
225 : : try {
226 [ + - ][ - + ]: 1 : DeserializeFileDB(anchors_db_path, CAddress::V2_DISK(anchors));
227 [ # # ][ # # ]: 0 : LogPrintf("Loaded %i addresses from %s\n", anchors.size(), fs::quoted(fs::PathToString(anchors_db_path.filename())));
[ # # ][ # # ]
[ # # ][ # # ]
228 [ + - ]: 1 : } catch (const std::exception&) {
229 : 1 : anchors.clear();
230 [ + - ]: 1 : }
231 : :
232 [ + - ]: 1 : fs::remove(anchors_db_path);
233 : 1 : return anchors;
234 [ + - ]: 2 : }
|