addrdb.cpp
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.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/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 }