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