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