Compare commits

..

1 Commits

Author SHA1 Message Date
Richard Holland
3566127427 ignore amendment cfg option init 2026-10-16 18:45:36 +11:00
6 changed files with 178 additions and 8 deletions

View File

@@ -133,6 +133,7 @@ test.rpc > xrpld.rpc
test.rpc > xrpld.shamap
test.rpc > xrpl.hook
test.rpc > xrpl.json
test.rpc > xrpl.proto
test.rpc > xrpl.protocol
test.rpc > xrpl.resource
test.server > test.jtx
@@ -157,6 +158,7 @@ xrpl.hook > xrpl.protocol
xrpl.json > xrpl.basics
xrpl.protocol > xrpl.basics
xrpl.protocol > xrpl.json
xrpl.protocol > xrpl.proto
xrpl.resource > xrpl.basics
xrpl.resource > xrpl.json
xrpl.resource > xrpl.protocol
@@ -170,6 +172,7 @@ xrpld.app > xrpld.consensus
xrpld.app > xrpld.perflog
xrpld.app > xrpl.hook
xrpld.app > xrpl.json
xrpld.app > xrpl.proto
xrpld.app > xrpl.resource
xrpld.conditions > xrpl.basics
xrpld.conditions > xrpl.protocol
@@ -214,6 +217,7 @@ xrpld.rpc > xrpld.nodestore
xrpld.rpc > xrpld.shamap
xrpld.rpc > xrpl.hook
xrpld.rpc > xrpl.json
xrpld.rpc > xrpl.proto
xrpld.rpc > xrpl.protocol
xrpld.rpc > xrpl.resource
xrpld.rpc > xrpl.server

View File

@@ -1271,6 +1271,79 @@ 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)
{
@@ -1294,6 +1367,7 @@ public:
testBadConfig();
testEnableVeto();
testHasUnsupported();
testIgnoredUnsupported();
testFeature(all - fixMajorityCalc);
testFeature(all);
}

View File

@@ -1260,13 +1260,29 @@ 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"));
logs_->journal("Amendments"),
ignored);
}
Pathfinder::initPathTable();

View File

@@ -211,7 +211,8 @@ make_AmendmentTable(
std::vector<AmendmentTable::FeatureInfo> const& supported,
Section const& enabled,
Section const& vetoed,
beast::Journal journal);
beast::Journal journal,
Section const& ignored = Section{});
} // namespace ripple

View File

@@ -302,6 +302,15 @@ 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;
@@ -476,7 +485,8 @@ public:
std::vector<FeatureInfo> const& supported,
Section const& enabled,
Section const& vetoed,
beast::Journal journal);
beast::Journal journal,
Section const& ignored);
uint256
find(std::string const& name) const override;
@@ -543,7 +553,8 @@ AmendmentTableImpl::AmendmentTableImpl(
std::vector<FeatureInfo> const& supported,
Section const& enabled,
Section const& vetoed,
beast::Journal journal)
beast::Journal journal,
Section const& ignored)
: lastUpdateSeq_(0)
, majorityTime_(majorityTime)
, unsupportedEnabled_(false)
@@ -586,6 +597,59 @@ 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))
@@ -775,7 +839,12 @@ AmendmentTableImpl::enable(uint256 const& amendment)
s.enabled = true;
if (!s.supported)
if (s.ignored)
{
JLOG(j_.warn()) << "Ignored unsupported amendment " << amendment << " ("
<< s.name << ") is enabled on the ledger.";
}
else if (!s.supported)
{
JLOG(j_.error()) << "Unsupported amendment " << amendment
<< " activated.";
@@ -1044,7 +1113,9 @@ AmendmentTableImpl::injectJson(
if (!fs.name.empty())
v[jss::name] = fs.name;
v[jss::supported] = fs.supported;
v[jss::supported] = fs.supported && !fs.ignored;
if (fs.ignored)
v["ignored"] = true;
if (!fs.enabled && isAdmin)
{
if (fs.vote == AmendmentVote::obsolete)
@@ -1113,10 +1184,11 @@ make_AmendmentTable(
std::vector<AmendmentTable::FeatureInfo> const& supported,
Section const& enabled,
Section const& vetoed,
beast::Journal journal)
beast::Journal journal,
Section const& ignored)
{
return std::make_unique<AmendmentTableImpl>(
app, majorityTime, supported, enabled, vetoed, journal);
app, majorityTime, supported, enabled, vetoed, journal, ignored);
}
} // namespace ripple

View File

@@ -102,6 +102,9 @@ 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