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