addrdb.cpp raw

   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/timer.h>
  18  #include <netbase.h>
  19  #include <netgroup.h>
  20  #include <random.h>
  21  #include <streams.h>
  22  #include <tinyformat.h>
  23  #include <univalue.h>
  24  #include <util/fs.h>
  25  #include <util/fs_helpers.h>
  26  #include <util/log.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  }
 247