mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-02-23 13:59:20 +00:00
There is no way to report a close error from `AutoFile` destructor. Such an error could be serious if the file has been written to because it may mean the file is now corrupted (same as if write fails). So, change all users of `AutoFile` that use it to write data to explicitly close the file and handle a possible error.
247 lines
8.3 KiB
C++
247 lines
8.3 KiB
C++
// Copyright (c) 2009-2010 Satoshi Nakamoto
|
|
// Copyright (c) 2009-2022 The Bitcoin Core developers
|
|
// Distributed under the MIT software license, see the accompanying
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
#include <bitcoin-build-config.h> // IWYU pragma: keep
|
|
|
|
#include <addrdb.h>
|
|
|
|
#include <addrman.h>
|
|
#include <chainparams.h>
|
|
#include <clientversion.h>
|
|
#include <common/args.h>
|
|
#include <common/settings.h>
|
|
#include <cstdint>
|
|
#include <hash.h>
|
|
#include <logging.h>
|
|
#include <logging/timer.h>
|
|
#include <netbase.h>
|
|
#include <netgroup.h>
|
|
#include <random.h>
|
|
#include <streams.h>
|
|
#include <tinyformat.h>
|
|
#include <univalue.h>
|
|
#include <util/fs.h>
|
|
#include <util/fs_helpers.h>
|
|
#include <util/syserror.h>
|
|
#include <util/translation.h>
|
|
|
|
namespace {
|
|
|
|
class DbNotFoundError : public std::exception
|
|
{
|
|
using std::exception::exception;
|
|
};
|
|
|
|
template <typename Stream, typename Data>
|
|
bool SerializeDB(Stream& stream, const Data& data)
|
|
{
|
|
// Write and commit header, data
|
|
try {
|
|
HashedSourceWriter hashwriter{stream};
|
|
hashwriter << Params().MessageStart() << data;
|
|
stream << hashwriter.GetHash();
|
|
} catch (const std::exception& e) {
|
|
LogError("%s: Serialize or I/O error - %s\n", __func__, e.what());
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
template <typename Data>
|
|
bool SerializeFileDB(const std::string& prefix, const fs::path& path, const Data& data)
|
|
{
|
|
// Generate random temporary filename
|
|
const uint16_t randv{FastRandomContext().rand<uint16_t>()};
|
|
std::string tmpfn = strprintf("%s.%04x", prefix, randv);
|
|
|
|
// open temp output file
|
|
fs::path pathTmp = gArgs.GetDataDirNet() / fs::u8path(tmpfn);
|
|
FILE *file = fsbridge::fopen(pathTmp, "wb");
|
|
AutoFile fileout{file};
|
|
if (fileout.IsNull()) {
|
|
remove(pathTmp);
|
|
LogError("%s: Failed to open file %s\n", __func__, fs::PathToString(pathTmp));
|
|
return false;
|
|
}
|
|
|
|
// Serialize
|
|
if (!SerializeDB(fileout, data)) {
|
|
(void)fileout.fclose();
|
|
remove(pathTmp);
|
|
return false;
|
|
}
|
|
if (!fileout.Commit()) {
|
|
(void)fileout.fclose();
|
|
remove(pathTmp);
|
|
LogError("%s: Failed to flush file %s\n", __func__, fs::PathToString(pathTmp));
|
|
return false;
|
|
}
|
|
if (fileout.fclose() != 0) {
|
|
const int errno_save{errno};
|
|
remove(pathTmp);
|
|
LogError("Failed to close file %s after commit: %s", fs::PathToString(pathTmp), SysErrorString(errno_save));
|
|
return false;
|
|
}
|
|
|
|
// replace existing file, if any, with new file
|
|
if (!RenameOver(pathTmp, path)) {
|
|
remove(pathTmp);
|
|
LogError("%s: Rename-into-place failed\n", __func__);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
template <typename Stream, typename Data>
|
|
void DeserializeDB(Stream& stream, Data&& data, bool fCheckSum = true)
|
|
{
|
|
HashVerifier verifier{stream};
|
|
// de-serialize file header (network specific magic number) and ..
|
|
MessageStartChars pchMsgTmp;
|
|
verifier >> pchMsgTmp;
|
|
// ... verify the network matches ours
|
|
if (pchMsgTmp != Params().MessageStart()) {
|
|
throw std::runtime_error{"Invalid network magic number"};
|
|
}
|
|
|
|
// de-serialize data
|
|
verifier >> data;
|
|
|
|
// verify checksum
|
|
if (fCheckSum) {
|
|
uint256 hashTmp;
|
|
stream >> hashTmp;
|
|
if (hashTmp != verifier.GetHash()) {
|
|
throw std::runtime_error{"Checksum mismatch, data corrupted"};
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename Data>
|
|
void DeserializeFileDB(const fs::path& path, Data&& data)
|
|
{
|
|
FILE* file = fsbridge::fopen(path, "rb");
|
|
AutoFile filein{file};
|
|
if (filein.IsNull()) {
|
|
throw DbNotFoundError{};
|
|
}
|
|
DeserializeDB(filein, data);
|
|
}
|
|
} // namespace
|
|
|
|
CBanDB::CBanDB(fs::path ban_list_path)
|
|
: m_banlist_dat(ban_list_path + ".dat"),
|
|
m_banlist_json(ban_list_path + ".json")
|
|
{
|
|
}
|
|
|
|
bool CBanDB::Write(const banmap_t& banSet)
|
|
{
|
|
std::vector<std::string> errors;
|
|
if (common::WriteSettings(m_banlist_json, {{JSON_KEY, BanMapToJson(banSet)}}, errors)) {
|
|
return true;
|
|
}
|
|
|
|
for (const auto& err : errors) {
|
|
LogError("%s\n", err);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CBanDB::Read(banmap_t& banSet)
|
|
{
|
|
if (fs::exists(m_banlist_dat)) {
|
|
LogPrintf("banlist.dat ignored because it can only be read by " CLIENT_NAME " version 22.x. Remove %s to silence this warning.\n", fs::quoted(fs::PathToString(m_banlist_dat)));
|
|
}
|
|
// If the JSON banlist does not exist, then recreate it
|
|
if (!fs::exists(m_banlist_json)) {
|
|
return false;
|
|
}
|
|
|
|
std::map<std::string, common::SettingsValue> settings;
|
|
std::vector<std::string> errors;
|
|
|
|
if (!common::ReadSettings(m_banlist_json, settings, errors)) {
|
|
for (const auto& err : errors) {
|
|
LogPrintf("Cannot load banlist %s: %s\n", fs::PathToString(m_banlist_json), err);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
BanMapFromJson(settings[JSON_KEY], banSet);
|
|
} catch (const std::runtime_error& e) {
|
|
LogPrintf("Cannot parse banlist %s: %s\n", fs::PathToString(m_banlist_json), e.what());
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DumpPeerAddresses(const ArgsManager& args, const AddrMan& addr)
|
|
{
|
|
const auto pathAddr = args.GetDataDirNet() / "peers.dat";
|
|
return SerializeFileDB("peers", pathAddr, addr);
|
|
}
|
|
|
|
void ReadFromStream(AddrMan& addr, DataStream& ssPeers)
|
|
{
|
|
DeserializeDB(ssPeers, addr, false);
|
|
}
|
|
|
|
util::Result<std::unique_ptr<AddrMan>> LoadAddrman(const NetGroupManager& netgroupman, const ArgsManager& args)
|
|
{
|
|
auto check_addrman = std::clamp<int32_t>(args.GetIntArg("-checkaddrman", DEFAULT_ADDRMAN_CONSISTENCY_CHECKS), 0, 1000000);
|
|
bool deterministic = HasTestOption(args, "addrman"); // use a deterministic addrman only for tests
|
|
|
|
auto addrman{std::make_unique<AddrMan>(netgroupman, deterministic, /*consistency_check_ratio=*/check_addrman)};
|
|
|
|
const auto start{SteadyClock::now()};
|
|
const auto path_addr{args.GetDataDirNet() / "peers.dat"};
|
|
try {
|
|
DeserializeFileDB(path_addr, *addrman);
|
|
LogPrintf("Loaded %i addresses from peers.dat %dms\n", addrman->Size(), Ticks<std::chrono::milliseconds>(SteadyClock::now() - start));
|
|
} catch (const DbNotFoundError&) {
|
|
// Addrman can be in an inconsistent state after failure, reset it
|
|
addrman = std::make_unique<AddrMan>(netgroupman, deterministic, /*consistency_check_ratio=*/check_addrman);
|
|
LogPrintf("Creating peers.dat because the file was not found (%s)\n", fs::quoted(fs::PathToString(path_addr)));
|
|
DumpPeerAddresses(args, *addrman);
|
|
} catch (const InvalidAddrManVersionError&) {
|
|
if (!RenameOver(path_addr, (fs::path)path_addr + ".bak")) {
|
|
return util::Error{strprintf(_("Failed to rename invalid peers.dat file. Please move or delete it and try again."))};
|
|
}
|
|
// Addrman can be in an inconsistent state after failure, reset it
|
|
addrman = std::make_unique<AddrMan>(netgroupman, deterministic, /*consistency_check_ratio=*/check_addrman);
|
|
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)));
|
|
DumpPeerAddresses(args, *addrman);
|
|
} catch (const std::exception& e) {
|
|
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."),
|
|
e.what(), CLIENT_BUGREPORT, fs::quoted(fs::PathToString(path_addr)))};
|
|
}
|
|
return addrman;
|
|
}
|
|
|
|
void DumpAnchors(const fs::path& anchors_db_path, const std::vector<CAddress>& anchors)
|
|
{
|
|
LOG_TIME_SECONDS(strprintf("Flush %d outbound block-relay-only peer addresses to anchors.dat", anchors.size()));
|
|
SerializeFileDB("anchors", anchors_db_path, CAddress::V2_DISK(anchors));
|
|
}
|
|
|
|
std::vector<CAddress> ReadAnchors(const fs::path& anchors_db_path)
|
|
{
|
|
std::vector<CAddress> anchors;
|
|
try {
|
|
DeserializeFileDB(anchors_db_path, CAddress::V2_DISK(anchors));
|
|
LogPrintf("Loaded %i addresses from %s\n", anchors.size(), fs::quoted(fs::PathToString(anchors_db_path.filename())));
|
|
} catch (const std::exception&) {
|
|
anchors.clear();
|
|
}
|
|
|
|
fs::remove(anchors_db_path);
|
|
return anchors;
|
|
}
|