mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-11-10 22:18:54 +01:00
banman: save the banlist in a JSON format on disk
Save the banlist in `banlist.json` instead of `banlist.dat`. This makes it possible to store Tor v3 entries in the banlist on disk (and any other addresses that cannot be serialized in addrv1 format). Only read `banlist.dat` if it exists and `banlist.json` does not exist (first start after an upgrade). Supersedes https://github.com/bitcoin/bitcoin/pull/20904 Resolves https://github.com/bitcoin/bitcoin/issues/19748
This commit is contained in:
103
src/addrdb.cpp
103
src/addrdb.cpp
@@ -11,13 +11,72 @@
|
||||
#include <cstdint>
|
||||
#include <hash.h>
|
||||
#include <logging/timer.h>
|
||||
#include <netbase.h>
|
||||
#include <random.h>
|
||||
#include <streams.h>
|
||||
#include <tinyformat.h>
|
||||
#include <univalue.h>
|
||||
#include <util/settings.h>
|
||||
#include <util/system.h>
|
||||
|
||||
CBanEntry::CBanEntry(const UniValue& json)
|
||||
: nVersion(json["version"].get_int()), nCreateTime(json["ban_created"].get_int64()),
|
||||
nBanUntil(json["banned_until"].get_int64())
|
||||
{
|
||||
}
|
||||
|
||||
UniValue CBanEntry::ToJson() const
|
||||
{
|
||||
UniValue json(UniValue::VOBJ);
|
||||
json.pushKV("version", nVersion);
|
||||
json.pushKV("ban_created", nCreateTime);
|
||||
json.pushKV("banned_until", nBanUntil);
|
||||
return json;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
static const char* BANMAN_JSON_ADDR_KEY = "address";
|
||||
|
||||
/**
|
||||
* Convert a `banmap_t` object to a JSON array.
|
||||
* @param[in] bans Bans list to convert.
|
||||
* @return a JSON array, similar to the one returned by the `listbanned` RPC. Suitable for
|
||||
* passing to `BanMapFromJson()`.
|
||||
*/
|
||||
UniValue BanMapToJson(const banmap_t& bans)
|
||||
{
|
||||
UniValue bans_json(UniValue::VARR);
|
||||
for (const auto& it : bans) {
|
||||
const auto& address = it.first;
|
||||
const auto& ban_entry = it.second;
|
||||
UniValue j = ban_entry.ToJson();
|
||||
j.pushKV(BANMAN_JSON_ADDR_KEY, address.ToString());
|
||||
bans_json.push_back(j);
|
||||
}
|
||||
return bans_json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a JSON array to a `banmap_t` object.
|
||||
* @param[in] bans_json JSON to convert, must be as returned by `BanMapToJson()`.
|
||||
* @param[out] bans Bans list to create from the JSON.
|
||||
* @throws std::runtime_error if the JSON does not have the expected fields or they contain
|
||||
* unparsable values.
|
||||
*/
|
||||
void BanMapFromJson(const UniValue& bans_json, banmap_t& bans)
|
||||
{
|
||||
for (const auto& ban_entry_json : bans_json.getValues()) {
|
||||
CSubNet subnet;
|
||||
const auto& subnet_str = ban_entry_json[BANMAN_JSON_ADDR_KEY].get_str();
|
||||
if (!LookupSubNet(subnet_str, subnet)) {
|
||||
throw std::runtime_error(
|
||||
strprintf("Cannot parse banned address or subnet: %s", subnet_str));
|
||||
}
|
||||
bans.insert_or_assign(subnet, CBanEntry{ban_entry_json});
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Stream, typename Data>
|
||||
bool SerializeDB(Stream& stream, const Data& data)
|
||||
{
|
||||
@@ -119,18 +178,54 @@ bool DeserializeFileDB(const fs::path& path, Data& data, int version)
|
||||
}
|
||||
} // namespace
|
||||
|
||||
CBanDB::CBanDB(fs::path ban_list_path) : m_ban_list_path(std::move(ban_list_path))
|
||||
CBanDB::CBanDB(fs::path ban_list_path)
|
||||
: m_banlist_dat(ban_list_path.string() + ".dat"),
|
||||
m_banlist_json(ban_list_path.string() + ".json")
|
||||
{
|
||||
}
|
||||
|
||||
bool CBanDB::Write(const banmap_t& banSet)
|
||||
{
|
||||
return SerializeFileDB("banlist", m_ban_list_path, banSet, CLIENT_VERSION);
|
||||
std::vector<std::string> errors;
|
||||
if (util::WriteSettings(m_banlist_json, {{JSON_KEY, BanMapToJson(banSet)}}, errors)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const auto& err : errors) {
|
||||
error("%s", err);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CBanDB::Read(banmap_t& banSet)
|
||||
bool CBanDB::Read(banmap_t& banSet, bool& dirty)
|
||||
{
|
||||
return DeserializeFileDB(m_ban_list_path, banSet, CLIENT_VERSION);
|
||||
// If the JSON banlist does not exist, then try to read the non-upgraded banlist.dat.
|
||||
if (!fs::exists(m_banlist_json)) {
|
||||
// If this succeeds then we need to flush to disk in order to create the JSON banlist.
|
||||
dirty = true;
|
||||
return DeserializeFileDB(m_banlist_dat, banSet, CLIENT_VERSION);
|
||||
}
|
||||
|
||||
dirty = false;
|
||||
|
||||
std::map<std::string, util::SettingsValue> settings;
|
||||
std::vector<std::string> errors;
|
||||
|
||||
if (!util::ReadSettings(m_banlist_json, settings, errors)) {
|
||||
for (const auto& err : errors) {
|
||||
LogPrintf("Cannot load banlist %s: %s\n", m_banlist_json.string(), err);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
BanMapFromJson(settings[JSON_KEY], banSet);
|
||||
} catch (const std::runtime_error& e) {
|
||||
LogPrintf("Cannot parse banlist %s: %s\n", m_banlist_json.string(), e.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CAddrDB::CAddrDB()
|
||||
|
||||
Reference in New Issue
Block a user