mirror of
https://github.com/Xahau/xahaud.git
synced 2026-10-18 09:48:04 +00:00
Compare commits
1 Commits
ignore-ame
...
rwdb-pin-f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
43df88a5ed |
@@ -1271,79 +1271,6 @@ public:
|
||||
BEAST_EXPECT(table->needValidatedLedger(257));
|
||||
}
|
||||
|
||||
void
|
||||
testIgnoredUnsupported()
|
||||
{
|
||||
testcase("ignore_amendments");
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
weeks constexpr w(1);
|
||||
test::jtx::Env env{*this, makeConfig()};
|
||||
|
||||
static std::vector<AmendmentTable::FeatureInfo> const supported =
|
||||
makeDefaultYes(yes_);
|
||||
|
||||
// Ignore every unsupported_ amendment except the first one.
|
||||
BEAST_EXPECT(unsupported_.size() > 1);
|
||||
std::vector<std::string> const ignoredNames(
|
||||
unsupported_.begin() + 1, unsupported_.end());
|
||||
|
||||
auto table = make_AmendmentTable(
|
||||
env.app(),
|
||||
w,
|
||||
supported,
|
||||
emptySection_,
|
||||
emptySection_,
|
||||
journal_,
|
||||
makeSection(ignoredNames));
|
||||
|
||||
for (auto const& a : ignoredNames)
|
||||
BEAST_EXPECT(table->isSupported(amendmentId(a)));
|
||||
BEAST_EXPECT(!table->isSupported(amendmentId(unsupported_[0])));
|
||||
|
||||
majorityAmendments_t const majority;
|
||||
|
||||
// Enabling only ignored amendments must not block.
|
||||
std::set<uint256> enabled;
|
||||
for (auto const& a : ignoredNames)
|
||||
enabled.insert(amendmentId(a));
|
||||
table->doValidatedLedger(1, enabled, majority);
|
||||
BEAST_EXPECT(!table->hasUnsupportedEnabled());
|
||||
BEAST_EXPECT(table->unsupportedAmendments().empty());
|
||||
|
||||
// An ignored amendment must never be voted for.
|
||||
auto const desired = table->getDesired();
|
||||
for (auto const& a : ignoredNames)
|
||||
BEAST_EXPECT(
|
||||
std::find(desired.begin(), desired.end(), amendmentId(a)) ==
|
||||
desired.end());
|
||||
|
||||
// A non-ignored unsupported amendment still blocks.
|
||||
enabled.insert(amendmentId(unsupported_[0]));
|
||||
table->doValidatedLedger(2, enabled, majority);
|
||||
BEAST_EXPECT(table->hasUnsupportedEnabled());
|
||||
|
||||
// Bad entries are rejected.
|
||||
Section bad("ignore_amendments");
|
||||
bad.append("NotARealFeatureName");
|
||||
try
|
||||
{
|
||||
make_AmendmentTable(
|
||||
env.app(),
|
||||
w,
|
||||
supported,
|
||||
emptySection_,
|
||||
emptySection_,
|
||||
journal_,
|
||||
bad);
|
||||
fail("expected bad [ignore_amendments] entry to throw");
|
||||
}
|
||||
catch (std::runtime_error const&)
|
||||
{
|
||||
pass();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testFeature(FeatureBitset const& feat)
|
||||
{
|
||||
@@ -1367,7 +1294,6 @@ public:
|
||||
testBadConfig();
|
||||
testEnableVeto();
|
||||
testHasUnsupported();
|
||||
testIgnoredUnsupported();
|
||||
testFeature(all - fixMajorityCalc);
|
||||
testFeature(all);
|
||||
}
|
||||
|
||||
@@ -76,6 +76,10 @@ class SHAMapStore_test : public beast::unit_test::suite
|
||||
return cfg;
|
||||
}
|
||||
|
||||
// DatabasePinned with the in-memory RWDB backend as the rotating hot
|
||||
// store and NuDB as the pinned store, over a persistent SQLite
|
||||
// relational database and a file-backed state.db. This is the
|
||||
// production shape for "rwdb + pinned_type".
|
||||
static auto
|
||||
pinnedPersistent(
|
||||
std::unique_ptr<Config> cfg,
|
||||
@@ -96,8 +100,9 @@ class SHAMapStore_test : public beast::unit_test::suite
|
||||
}
|
||||
|
||||
// Online delete with DatabasePinned over in-memory backends. Unlike
|
||||
// pinnedPersistent, the hot store keeps nodes (type=rwdb is the null
|
||||
// node store), so ledgers closed in the test can be fetched. There is
|
||||
// pinnedPersistent, the hot store is the process-shared "memory"
|
||||
// backend and the pinned store is in-memory too, so ledgers closed in
|
||||
// the test can be fetched without touching disk. There is
|
||||
// no database_path, so every SHAMapStoreImp opens its own private
|
||||
// state.db: a second instance built by a test does not see (or trip
|
||||
// over) the rotation state of the Env's own.
|
||||
@@ -936,15 +941,22 @@ public:
|
||||
Config upperCfg;
|
||||
Config nudbCfg;
|
||||
Config emptyCfg;
|
||||
Config pinnedCfg;
|
||||
rwdbCfg.section(ConfigSection::nodeDatabase()).set("type", "rwdb");
|
||||
upperCfg.section(ConfigSection::nodeDatabase()).set("type", "RWDB");
|
||||
nudbCfg.section(ConfigSection::nodeDatabase()).set("type", "NuDB");
|
||||
pinnedCfg.section(ConfigSection::nodeDatabase()).set("type", "rwdb");
|
||||
pinnedCfg.section(ConfigSection::nodeDatabase())
|
||||
.set("pinned_type", "NuDB");
|
||||
|
||||
// Evaluated per Config object, not latched process-wide.
|
||||
BEAST_EXPECT(rwdbCfg.nullBackend());
|
||||
BEAST_EXPECT(upperCfg.nullBackend());
|
||||
BEAST_EXPECT(!nudbCfg.nullBackend());
|
||||
BEAST_EXPECT(!emptyCfg.nullBackend());
|
||||
// pinned_type turns type=rwdb into the rotating hot store under
|
||||
// DatabasePinned, not the null node store.
|
||||
BEAST_EXPECT(!pinnedCfg.nullBackend());
|
||||
BEAST_EXPECT(rwdbCfg.nullBackend());
|
||||
}
|
||||
|
||||
|
||||
@@ -1707,9 +1707,10 @@ r.ripple.com:51235
|
||||
}
|
||||
}
|
||||
|
||||
// Test 5: type=rwdb with pinned_type (should throw, even in
|
||||
// standalone). The rwdb node store is tree-only, so pinned data
|
||||
// would be accepted and then dropped.
|
||||
// Test 5: type=rwdb with pinned_type (should succeed in both
|
||||
// modes). pinned_type opts the rwdb store out of null mode: it
|
||||
// becomes the in-memory rotating hot store under DatabasePinned,
|
||||
// with no [node_db] path required.
|
||||
for (bool const standalone : {false, true})
|
||||
{
|
||||
Config c;
|
||||
@@ -1723,13 +1724,14 @@ r.ripple.com:51235
|
||||
try
|
||||
{
|
||||
c.loadFromString(toLoad);
|
||||
fail("Expected exception for pinned_type with type=rwdb");
|
||||
BEAST_EXPECT(!c.nullBackend());
|
||||
}
|
||||
catch (std::runtime_error const& e)
|
||||
{
|
||||
BEAST_EXPECT(
|
||||
std::string(e.what()).find("type=rwdb") !=
|
||||
std::string::npos);
|
||||
fail(
|
||||
std::string("Should not throw for pinned_type with "
|
||||
"type=rwdb: ") +
|
||||
e.what());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,9 +34,9 @@ namespace test {
|
||||
class DatabasePinned_test : public beast::unit_test::suite
|
||||
{
|
||||
// Build an Env whose nodestore is wired up as DatabasePinnedImp.
|
||||
// The rotating store must persist (type=memory). type=rwdb is
|
||||
// NullFactory and never builds DatabasePinnedImp. pinned_type=rwdb
|
||||
// is allowed in standalone mode.
|
||||
// The rotating store is the process-shared "memory" backend so the
|
||||
// hot side keeps nodes across the test. pinned_type=rwdb is allowed
|
||||
// in standalone mode.
|
||||
static std::unique_ptr<Config>
|
||||
pinnedEnvconfig(std::string const& nodePath)
|
||||
{
|
||||
|
||||
@@ -110,10 +110,10 @@ struct CatalogueEnvOpts
|
||||
// envconfig with pinned_type set so catalogue_load passes the config guard.
|
||||
//
|
||||
// envconfig() selects [node_db] type=rwdb, which is the tree-only null node
|
||||
// store: SHAMapStore never builds DatabasePinned on it, so catalogue_load
|
||||
// would refuse to run. Use the in-memory "memory" store for the hot side
|
||||
// instead. It is shared by path within the process, so each config gets its
|
||||
// own path (SHAMapStore creates the directory; nothing is written to it).
|
||||
// store unless pinned_type is also set. Use the in-memory "memory" store for
|
||||
// the hot side so the Env's node store is the same shape as a persisting
|
||||
// one. It is shared by path within the process, so each config gets its own
|
||||
// path (SHAMapStore creates the directory; nothing is written to it).
|
||||
// The pinned side uses rwdb, an in-memory persistent backend.
|
||||
inline std::unique_ptr<Config>
|
||||
catalogueEnvconfig(CatalogueEnvOpts const& opts = {})
|
||||
@@ -1416,13 +1416,14 @@ public:
|
||||
using namespace test::jtx;
|
||||
using beast::severities::kDisabled;
|
||||
|
||||
// pinned_type is set, but [node_db] type=rwdb is the null node
|
||||
// store, on which SHAMapStore never builds the pinned store.
|
||||
// pinned_type is set, but online_delete is not, so SHAMapStore
|
||||
// builds a plain node store rather than DatabasePinned. (Config
|
||||
// validation rejects this combination from a config file; jtx
|
||||
// sets sections directly and bypasses it.)
|
||||
{
|
||||
auto cfg = test::jtx::envconfig();
|
||||
auto& nodeDb = cfg->section(ConfigSection::nodeDatabase());
|
||||
nodeDb.set("pinned_type", "rwdb");
|
||||
nodeDb.set("online_delete", "256");
|
||||
Env env{*this, std::move(cfg), features, nullptr, kDisabled};
|
||||
|
||||
Json::Value params{Json::objectValue};
|
||||
|
||||
@@ -1260,29 +1260,13 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline)
|
||||
|
||||
Section const& upVoted = config_->section(SECTION_AMENDMENTS);
|
||||
|
||||
Section const& ignored = config_->section(SECTION_IGNORE_AMENDMENTS);
|
||||
|
||||
// [ignore_amendments] exists to keep long-lived test networks syncable
|
||||
// after an amendment they activated is removed from the codebase. It
|
||||
// must never be usable on a production network.
|
||||
if (!ignored.lines().empty() &&
|
||||
(config_->NETWORK_ID == 0 || config_->NETWORK_ID == 21337))
|
||||
{
|
||||
JLOG(m_journal.fatal()) << "[" SECTION_IGNORE_AMENDMENTS
|
||||
"] is not permitted on "
|
||||
"network_id "
|
||||
<< config_->NETWORK_ID;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_amendmentTable = make_AmendmentTable(
|
||||
*this,
|
||||
config().AMENDMENT_MAJORITY_TIME,
|
||||
supported,
|
||||
upVoted,
|
||||
downVoted,
|
||||
logs_->journal("Amendments"),
|
||||
ignored);
|
||||
logs_->journal("Amendments"));
|
||||
}
|
||||
|
||||
Pathfinder::initPathTable();
|
||||
|
||||
@@ -211,8 +211,7 @@ make_AmendmentTable(
|
||||
std::vector<AmendmentTable::FeatureInfo> const& supported,
|
||||
Section const& enabled,
|
||||
Section const& vetoed,
|
||||
beast::Journal journal,
|
||||
Section const& ignored = Section{});
|
||||
beast::Journal journal);
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
|
||||
@@ -207,7 +207,11 @@ SHAMapStoreImp::SHAMapStoreImp(
|
||||
std::to_string(config.LEDGER_HISTORY) + ")");
|
||||
}
|
||||
|
||||
if (!isNullBackend_)
|
||||
// Only on-disk rotating backends keep files under [node_db] path.
|
||||
// The null node store has none, and neither does type=rwdb used
|
||||
// as the rotating hot store under DatabasePinned (pinned_type
|
||||
// set), which was never required to configure a path.
|
||||
if (!isNullBackend_ && !boost::iequals(get(section, "type"), "rwdb"))
|
||||
dbPaths();
|
||||
}
|
||||
}
|
||||
@@ -425,6 +429,16 @@ SHAMapStoreImp::loadPinnedRanges()
|
||||
// is not the one these ranges were loaded into.
|
||||
constexpr std::uint32_t maxTailTrim = 65536;
|
||||
|
||||
// [relational_db] backend=rwdb keeps the SQL tables in memory, so the
|
||||
// seq -> hash index behind hasLedgerData() starts empty on every run
|
||||
// even when the pinned node store still holds every node. An interval
|
||||
// the index cannot resolve is unreachable rather than evidence of a
|
||||
// mismatched pinned store: drop it and have the operator re-run
|
||||
// catalogue_load (node writes de-duplicate; only SQL is rebuilt).
|
||||
auto const& rdb = app_.config().section(SECTION_RELATIONAL_DB);
|
||||
bool const sqlPersistent =
|
||||
rdb.empty() || !boost::iequals(get(rdb, "backend"), "rwdb");
|
||||
|
||||
RangeSet<std::uint32_t> verified;
|
||||
for (auto const& interval : persistedRanges)
|
||||
{
|
||||
@@ -439,7 +453,18 @@ SHAMapStoreImp::loadPinnedRanges()
|
||||
};
|
||||
|
||||
if (!hasLedgerData(lower))
|
||||
Throw<std::runtime_error>(notPresent());
|
||||
{
|
||||
if (sqlPersistent)
|
||||
Throw<std::runtime_error>(notPresent());
|
||||
|
||||
JLOG(journal_.warn())
|
||||
<< "Dropping pinned interval " << lower << "-"
|
||||
<< interval.upper()
|
||||
<< ": [relational_db] backend=rwdb does not persist the "
|
||||
"ledger index across restarts, so it cannot be served. "
|
||||
"Re-run catalogue_load to restore it.";
|
||||
continue;
|
||||
}
|
||||
|
||||
std::uint32_t trimmed = 0;
|
||||
while (upper > lower && !hasLedgerData(upper))
|
||||
@@ -464,7 +489,7 @@ SHAMapStoreImp::loadPinnedRanges()
|
||||
}
|
||||
|
||||
if (verified != persistedRanges)
|
||||
state_db_.setPinnedRanges(to_string(verified));
|
||||
setPinnedRanges(verified);
|
||||
|
||||
JLOG(journal_.info()) << "Loaded pinned ranges from database: "
|
||||
<< to_string(verified);
|
||||
|
||||
@@ -302,15 +302,6 @@ struct AmendmentState
|
||||
/** Indicates an amendment that this server has code support for. */
|
||||
bool supported = false;
|
||||
|
||||
/** Indicates an amendment listed in [ignore_amendments]. The server has
|
||||
no code for it, but must not become amendment blocked if it is (or
|
||||
becomes) enabled on the ledger. Such an amendment is reported as
|
||||
`supported` internally so every blocking check (LedgerMaster,
|
||||
NetworkOPs, Change) passes, but is never voted for and is shown as
|
||||
unsupported + ignored over RPC.
|
||||
*/
|
||||
bool ignored = false;
|
||||
|
||||
/** The name of this amendment, possibly empty. */
|
||||
std::string name;
|
||||
|
||||
@@ -485,8 +476,7 @@ public:
|
||||
std::vector<FeatureInfo> const& supported,
|
||||
Section const& enabled,
|
||||
Section const& vetoed,
|
||||
beast::Journal journal,
|
||||
Section const& ignored);
|
||||
beast::Journal journal);
|
||||
|
||||
uint256
|
||||
find(std::string const& name) const override;
|
||||
@@ -553,8 +543,7 @@ AmendmentTableImpl::AmendmentTableImpl(
|
||||
std::vector<FeatureInfo> const& supported,
|
||||
Section const& enabled,
|
||||
Section const& vetoed,
|
||||
beast::Journal journal,
|
||||
Section const& ignored)
|
||||
beast::Journal journal)
|
||||
: lastUpdateSeq_(0)
|
||||
, majorityTime_(majorityTime)
|
||||
, unsupportedEnabled_(false)
|
||||
@@ -597,59 +586,6 @@ AmendmentTableImpl::AmendmentTableImpl(
|
||||
<< " voted by default if not enabled on the ledger.";
|
||||
}
|
||||
|
||||
// Parse ignored amendments from config. Each line is either a registered
|
||||
// feature name (e.g. "HookOnV2") or "<64 hex amendment ID> [name]". These
|
||||
// are deliberately NOT persisted to wallet.db: removing the line from the
|
||||
// config must restore normal amendment-blocking behaviour.
|
||||
for (auto const& line : ignored.lines())
|
||||
{
|
||||
std::string const trimmed = boost::algorithm::trim_copy(line);
|
||||
if (trimmed.empty())
|
||||
continue;
|
||||
|
||||
std::vector<std::string> parts;
|
||||
boost::algorithm::split(
|
||||
parts,
|
||||
trimmed,
|
||||
boost::algorithm::is_space(),
|
||||
boost::algorithm::token_compress_on);
|
||||
|
||||
uint256 id;
|
||||
std::string name;
|
||||
if (id.parseHex(parts[0]))
|
||||
name = parts.size() > 1 ? parts[1] : std::string{};
|
||||
else if (auto const f = getRegisteredFeature(parts[0]))
|
||||
{
|
||||
id = *f;
|
||||
name = parts[0];
|
||||
}
|
||||
else
|
||||
Throw<std::runtime_error>(
|
||||
"Invalid entry '" + line + "' in [" + ignored.name() +
|
||||
"]: expected a registered feature name or amendment ID");
|
||||
|
||||
AmendmentState& s = add(id, lock);
|
||||
if (s.supported)
|
||||
{
|
||||
JLOG(j_.warn()) << "[" << ignored.name() << "] lists amendment "
|
||||
<< id << " (" << s.name
|
||||
<< ") which this server supports; entry has no "
|
||||
"effect.";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!name.empty())
|
||||
s.name = name;
|
||||
s.supported = true;
|
||||
s.ignored = true;
|
||||
s.vote = AmendmentVote::obsolete;
|
||||
|
||||
JLOG(j_.warn()) << "Amendment " << id << " (" << s.name
|
||||
<< ") is unsupported but listed in [" << ignored.name()
|
||||
<< "]: this server will NOT become amendment blocked "
|
||||
"if it is enabled on the ledger.";
|
||||
}
|
||||
|
||||
hash_set<uint256> detect_conflict;
|
||||
// Parse enabled amendments from config
|
||||
for (std::pair<uint256, std::string> const& a : parseSection(enabled))
|
||||
@@ -839,12 +775,7 @@ AmendmentTableImpl::enable(uint256 const& amendment)
|
||||
|
||||
s.enabled = true;
|
||||
|
||||
if (s.ignored)
|
||||
{
|
||||
JLOG(j_.warn()) << "Ignored unsupported amendment " << amendment << " ("
|
||||
<< s.name << ") is enabled on the ledger.";
|
||||
}
|
||||
else if (!s.supported)
|
||||
if (!s.supported)
|
||||
{
|
||||
JLOG(j_.error()) << "Unsupported amendment " << amendment
|
||||
<< " activated.";
|
||||
@@ -1113,9 +1044,7 @@ AmendmentTableImpl::injectJson(
|
||||
if (!fs.name.empty())
|
||||
v[jss::name] = fs.name;
|
||||
|
||||
v[jss::supported] = fs.supported && !fs.ignored;
|
||||
if (fs.ignored)
|
||||
v["ignored"] = true;
|
||||
v[jss::supported] = fs.supported;
|
||||
if (!fs.enabled && isAdmin)
|
||||
{
|
||||
if (fs.vote == AmendmentVote::obsolete)
|
||||
@@ -1184,11 +1113,10 @@ make_AmendmentTable(
|
||||
std::vector<AmendmentTable::FeatureInfo> const& supported,
|
||||
Section const& enabled,
|
||||
Section const& vetoed,
|
||||
beast::Journal journal,
|
||||
Section const& ignored)
|
||||
beast::Journal journal)
|
||||
{
|
||||
return std::make_unique<AmendmentTableImpl>(
|
||||
app, majorityTime, supported, enabled, vetoed, journal, ignored);
|
||||
app, majorityTime, supported, enabled, vetoed, journal);
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -387,10 +387,14 @@ public:
|
||||
return isMem;
|
||||
}
|
||||
|
||||
/** True when [node_db] type=rwdb.
|
||||
/** True when [node_db] type=rwdb and no pinned_type is configured.
|
||||
|
||||
The app node store is the existing NullFactory (type=none).
|
||||
Nodes stay alive through Ledger and SHAMap pointers.
|
||||
|
||||
With pinned_type set, type=rwdb keeps its older meaning: the
|
||||
in-memory RWDB backend is the rotating hot store under
|
||||
DatabasePinnedImp and the durable pinned store holds history.
|
||||
The result is read from this configuration, so two
|
||||
applications in one process do not share it.
|
||||
*/
|
||||
@@ -399,7 +403,8 @@ public:
|
||||
{
|
||||
auto const& node = section(ConfigSection::nodeDatabase());
|
||||
return !node.empty() &&
|
||||
boost::beast::iequals(get(node, "type"), "rwdb");
|
||||
boost::beast::iequals(get(node, "type"), "rwdb") &&
|
||||
!node.exists("pinned_type");
|
||||
}
|
||||
|
||||
bool
|
||||
|
||||
@@ -102,9 +102,6 @@ struct ConfigSection
|
||||
#define SECTION_VALIDATORS "validators"
|
||||
#define SECTION_VALIDATOR_TOKEN "validator_token"
|
||||
#define SECTION_VETO_AMENDMENTS "veto_amendments"
|
||||
// Testnet-only: amendments enabled on-ledger that this node should not treat
|
||||
// as unsupported (i.e. should not become amendment blocked over).
|
||||
#define SECTION_IGNORE_AMENDMENTS "ignore_amendments"
|
||||
#define SECTION_WORKERS "workers"
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -1087,15 +1087,10 @@ Config::loadFromString(std::string const& fileContents)
|
||||
"type must be specified when using pinned_type");
|
||||
}
|
||||
|
||||
// type=rwdb is the tree-only null node store. SHAMapStore never
|
||||
// builds DatabasePinned for it, so pinned (and catalogue) data
|
||||
// would be accepted and then silently dropped.
|
||||
if (nullBackend())
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
"pinned_type is not supported with [node_db] type=rwdb "
|
||||
"(the rwdb node store does not persist nodes)");
|
||||
}
|
||||
// type=rwdb with pinned_type is not the null node store (see
|
||||
// Config::nullBackend): SHAMapStore builds DatabasePinned with the
|
||||
// in-memory RWDB backend as the rotating hot store, so pinned data
|
||||
// lands in the durable pinned backend as with any other type.
|
||||
|
||||
// Ensure pinned_path is specified for filesystem-backed types.
|
||||
// In-memory backends (rwdb) don't need a path.
|
||||
|
||||
Reference in New Issue
Block a user