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