Compare commits

..

7 Commits

Author SHA1 Message Date
Richard Holland
fd342dabee Merge branch 'dev' into server-definitions-feature-aware 2026-09-23 14:37:38 +10:00
tequ
902ed9b492 Use normal consequences factory for standard transactions (#774)
Replace redundant custom consequence factories that returned normal consequences with the built-in Normal factory.
2026-09-21 21:12:15 +10:00
Richard Holland
318f941ec4 Merge branch 'dev' into server-definitions-feature-aware 2026-09-09 14:01:33 +10:00
Nicholas Dudfield
5450719bdc fix(server_definitions): drop cfg_forced; cover on-ledger enabled
enabled is what this server applies; ledger_enabled is the on-ledger
flag. Config-forced is enabled && !ledger_enabled.
2026-09-01 11:18:22 +07:00
Richard Holland
fb8c29cf38 Merge branch 'dev' into server-definitions-feature-aware 2026-09-01 11:39:23 +10:00
Nicholas Dudfield
3754109e4b test(server_definitions): adapt amendment tests to ledger_enabled/cfg_forced split
jtx's Env enables amendments by inserting them into config.features (the
same presets mechanism as the [features] stanza), so:

- testNoParams / testSomeEnabled assert on `ledger_enabled` (the canonical
  on-ledger flag) instead of `enabled`, which is now the effective value
  (ledger_enabled || cfg_forced).
- testConfigForced uses a single-feature bitset so exactly one amendment is
  config-forced, and verifies it reports enabled:true, ledger_enabled:false,
  cfg_forced:true while the rest report cfg_forced:false and
  enabled == ledger_enabled.

ripple.rpc.ServerDefinitions: 6 cases, 3435 tests, 0 failures.
2026-06-26 14:20:03 +07:00
Nicholas Dudfield
9d125f4d19 feat(server_definitions): distinguish config-forced from ledger-enabled amendments
`server_definitions` sourced each amendment's `enabled` straight from the
on-ledger Amendments object, so amendments force-activated via the
`[features]` config stanza (live in the Rules / tx processing, but never
voted on-ledger) were reported as not-enabled. That makes the output
misleading on any node that uses `[features]` (notably testnets).

Split per-amendment activation into its two real sources and make
`enabled` reflect what the server actually applies:

    enabled        = ledger_enabled || cfg_forced
    ledger_enabled = recorded in the on-ledger Amendments object (canonical)
    cfg_forced     = forced via the [features] config stanza (node-local)

Additive JSON fields. Adds jss::ledger_enabled, jss::cfg_forced and a
ServerDefinitions test covering the config-forced case.
2026-06-26 13:55:48 +07:00
17 changed files with 160 additions and 109 deletions

View File

@@ -116,11 +116,6 @@ message TMTransaction
message TMTransactions
{
repeated TMTransaction transactions = 1;
// Optional opaque request/response correlation ID. If present on the
// TMGetObjectByHash(otTRANSACTIONS) request, the responder copies it
// unchanged. This field is not routing state and does not affect message
// processing, resource charging, duplicate suppression, or consensus.
optional uint64 requestId = 2;
}
@@ -272,10 +267,6 @@ message TMGetObjectByHash
optional bytes ledgerHash = 4; // the hash of the ledger these queries are for
optional bool fat = 5; // return related nodes
repeated TMIndexedObject objects = 6; // the specific objects requested
// Optional opaque request/response correlation ID. Responders copy this
// value unchanged when forming a reply. This is distinct from the existing
// uint32 seq field and has no protocol meaning beyond correlation.
optional uint64 requestId = 7;
}
@@ -315,10 +306,6 @@ message TMGetLedger
optional uint64 requestCookie = 6;
optional TMQueryType queryType = 7;
optional uint32 queryDepth = 8; // How deep to go, number of extra levels
// Optional opaque request/response correlation ID. Responders copy this
// value unchanged into TMLedgerData. This is separate from requestCookie:
// requestCookie is relay routing state; requestId is only correlation.
optional uint64 requestId = 9;
}
enum TMReplyError
@@ -336,9 +323,6 @@ message TMLedgerData
repeated TMLedgerNode nodes = 4;
optional uint32 requestCookie = 5;
optional TMReplyError error = 6;
// Optional opaque request/response correlation ID copied from TMGetLedger.
// Receivers must not use it for relay routing or data validation.
optional uint64 requestId = 7;
}
message TMPing
@@ -351,9 +335,6 @@ message TMPing
optional uint32 seq = 2; // detect stale replies, ensure other side is reading
optional uint64 pingTime = 3; // know when we think we sent the ping
optional uint64 netTime = 4;
// Optional opaque request/response correlation ID copied from ptPING to
// ptPONG. The existing seq field remains the ping liveness cookie.
optional uint64 requestId = 5;
}
message TMSquelch
@@ -374,8 +355,6 @@ message TMProofPathRequest
required bytes key = 1;
required bytes ledgerHash = 2;
required TMLedgerMapType type = 3;
// Optional opaque request/response correlation ID copied into the response.
optional uint64 requestId = 4;
}
message TMProofPathResponse
@@ -386,15 +365,11 @@ message TMProofPathResponse
optional bytes ledgerHeader = 4;
repeated bytes path = 5;
optional TMReplyError error = 6;
// Optional opaque request/response correlation ID copied from the request.
optional uint64 requestId = 7;
}
message TMReplayDeltaRequest
{
required bytes ledgerHash = 1;
// Optional opaque request/response correlation ID copied into the response.
optional uint64 requestId = 2;
}
message TMReplayDeltaResponse
@@ -403,8 +378,6 @@ message TMReplayDeltaResponse
optional bytes ledgerHeader = 2;
repeated bytes transaction = 3;
optional TMReplyError error = 4;
// Optional opaque request/response correlation ID copied from the request.
optional uint64 requestId = 5;
}
message TMHaveTransactions

View File

@@ -292,7 +292,9 @@ JSS(duration_us); // out: NetworkOPs
JSS(effective); // out: ValidatorList
// in: UNL
JSS(elapsed_seconds);
JSS(enabled); // out: AmendmentTable
JSS(enabled); // out: AmendmentTable (on-ledger);
// ServerDefinitions (this server)
JSS(ledger_enabled); // out: ServerDefinitions (on-ledger)
JSS(engine_result); // out: NetworkOPs, TransactionSign, Submit
JSS(engine_result_code); // out: NetworkOPs, TransactionSign, Submit
JSS(engine_result_message); // out: NetworkOPs, TransactionSign, Submit

View File

@@ -163,7 +163,7 @@ public:
void
testNoParams(FeatureBitset features)
{
testcase("No Params, None Enabled");
testcase("Default Env: config-forced, none on-ledger");
using namespace test::jtx;
Env env{*this};
@@ -178,8 +178,6 @@ public:
{
if (!BEAST_EXPECT(feature.isMember(jss::name)))
return;
// default config - so all should be disabled, and
// supported. Some may be vetoed.
bool expectVeto =
(votes.at(feature[jss::name].asString()) ==
VoteBehavior::DefaultNo);
@@ -188,8 +186,12 @@ public:
VoteBehavior::Obsolete);
BEAST_EXPECTS(
feature.isMember(jss::enabled) &&
!feature[jss::enabled].asBool(),
feature[jss::enabled].asBool(),
feature[jss::name].asString() + " enabled");
BEAST_EXPECTS(
feature.isMember(jss::ledger_enabled) &&
!feature[jss::ledger_enabled].asBool(),
feature[jss::name].asString() + " ledger_enabled");
BEAST_EXPECTS(
feature.isMember(jss::vetoed) &&
feature[jss::vetoed].isBool() == !expectObsolete &&
@@ -208,7 +210,7 @@ public:
void
testSomeEnabled(FeatureBitset features)
{
testcase("No Params, Some Enabled");
testcase("Two config-forced, none on-ledger");
using namespace test::jtx;
Env env{
@@ -228,7 +230,10 @@ public:
(void)id.parseHex(it.key().asString().c_str());
if (!BEAST_EXPECT((*it).isMember(jss::name)))
return;
bool expectEnabled = env.app().getAmendmentTable().isEnabled(id);
bool const expectOnLedger =
env.app().getAmendmentTable().isEnabled(id);
bool const expectForced =
id == featureDepositAuth || id == featureDepositPreauth;
bool expectSupported =
env.app().getAmendmentTable().isSupported(id);
bool expectVeto =
@@ -239,9 +244,14 @@ public:
VoteBehavior::Obsolete);
BEAST_EXPECTS(
(*it).isMember(jss::enabled) &&
(*it)[jss::enabled].asBool() == expectEnabled,
(*it)[jss::enabled].asBool() ==
(expectOnLedger || expectForced),
(*it)[jss::name].asString() + " enabled");
if (expectEnabled)
BEAST_EXPECTS(
(*it).isMember(jss::ledger_enabled) &&
(*it)[jss::ledger_enabled].asBool() == expectOnLedger,
(*it)[jss::name].asString() + " ledger_enabled");
if (expectOnLedger)
BEAST_EXPECTS(
!(*it).isMember(jss::vetoed),
(*it)[jss::name].asString() + " vetoed");
@@ -360,12 +370,122 @@ public:
}
}
void
testConfigForced(FeatureBitset features)
{
testcase("Config-forced features ([features] stanza)");
using namespace test::jtx;
auto const forced = featurePriceOracle;
auto const forcedHex = to_string(forced);
Env env{*this, FeatureBitset(forced)};
auto jrr = env.rpc("server_definitions")[jss::result];
if (!BEAST_EXPECT(jrr.isMember(jss::features)))
return;
bool sawForced = false;
for (auto it = jrr[jss::features].begin();
it != jrr[jss::features].end();
++it)
{
auto const& f = *it;
auto const name = f[jss::name].asString();
if (!BEAST_EXPECTS(
f.isMember(jss::enabled) && f.isMember(jss::ledger_enabled),
name + " enabled/ledger_enabled"))
return;
BEAST_EXPECTS(
!f[jss::ledger_enabled].asBool(), name + " ledger_enabled");
if (it.key().asString() == forcedHex)
{
sawForced = true;
BEAST_EXPECTS(f[jss::enabled].asBool(), name + " enabled");
}
else
{
BEAST_EXPECTS(!f[jss::enabled].asBool(), name + " enabled");
}
}
BEAST_EXPECT(sawForced);
}
void
testOnLedger(FeatureBitset features)
{
testcase("On-ledger plus one config-forced");
using namespace test::jtx;
// Veto XahauGenesis: FRESH would otherwise enable it via pseudo-tx.
auto const forced = featurePriceOracle;
auto const forcedHex = to_string(forced);
Env env{
*this,
envconfig([](std::unique_ptr<Config> cfg) {
cfg->START_UP = Config::FRESH;
cfg->section(SECTION_VETO_AMENDMENTS)
.append(to_string(featureXahauGenesis) + " XahauGenesis");
return cfg;
}),
FeatureBitset(forced)};
auto jrr = env.rpc("server_definitions")[jss::result];
if (!BEAST_EXPECT(jrr.isMember(jss::features)))
return;
bool sawOnLedger = false;
bool sawForced = false;
for (auto it = jrr[jss::features].begin();
it != jrr[jss::features].end();
++it)
{
uint256 id;
(void)id.parseHex(it.key().asString().c_str());
auto const& f = *it;
auto const name = f[jss::name].asString();
if (!BEAST_EXPECTS(
f.isMember(jss::enabled) && f.isMember(jss::ledger_enabled),
name + " enabled/ledger_enabled"))
return;
bool const onLedger = env.app().getAmendmentTable().isEnabled(id);
bool const isForced = it.key().asString() == forcedHex;
BEAST_EXPECTS(
f[jss::ledger_enabled].asBool() == onLedger,
name + " ledger_enabled");
BEAST_EXPECTS(
f[jss::enabled].asBool() == (onLedger || isForced),
name + " enabled");
if (onLedger)
sawOnLedger = true;
if (isForced)
{
sawForced = true;
BEAST_EXPECTS(!onLedger, name + " forced not on-ledger");
}
}
BEAST_EXPECT(sawOnLedger);
BEAST_EXPECT(sawForced);
}
void
testServerFeatures(FeatureBitset features)
{
testNoParams(features);
testSomeEnabled(features);
testWithMajorities(features);
testConfigForced(features);
testOnLedger(features);
}
void

View File

@@ -2439,9 +2439,6 @@ LedgerMaster::makeFetchPack(
if (request->has_seq())
reply.set_seq(request->seq());
if (request->has_requestid())
reply.set_requestid(request->requestid());
reply.set_ledgerhash(request->ledgerhash());
reply.set_type(protocol::TMGetObjectByHash::otFETCH_PACK);

View File

@@ -42,9 +42,6 @@ LedgerReplayMsgHandler::processProofPathRequest(
protocol::TMProofPathRequest& packet = *msg;
protocol::TMProofPathResponse reply;
if (packet.has_requestid())
reply.set_requestid(packet.requestid());
if (!packet.has_key() || !packet.has_ledgerhash() || !packet.has_type() ||
packet.ledgerhash().size() != uint256::size() ||
packet.key().size() != uint256::size() ||
@@ -185,9 +182,6 @@ LedgerReplayMsgHandler::processReplayDeltaRequest(
protocol::TMReplayDeltaRequest& packet = *msg;
protocol::TMReplayDeltaResponse reply;
if (packet.has_requestid())
reply.set_requestid(packet.requestid());
if (!packet.has_ledgerhash() ||
packet.ledgerhash().size() != uint256::size())
{

View File

@@ -30,12 +30,6 @@
namespace ripple {
TxConsequences
ClaimReward::makeTxConsequences(PreflightContext const& ctx)
{
return TxConsequences{ctx.tx, TxConsequences::normal};
}
NotTEC
ClaimReward::preflight(PreflightContext const& ctx)
{

View File

@@ -32,15 +32,12 @@ namespace ripple {
class ClaimReward : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Custom};
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit ClaimReward(ApplyContext& ctx) : Transactor(ctx)
{
}
static TxConsequences
makeTxConsequences(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);

View File

@@ -30,12 +30,6 @@
namespace ripple {
TxConsequences
Cron::makeTxConsequences(PreflightContext const& ctx)
{
return TxConsequences{ctx.tx, TxConsequences::normal};
}
NotTEC
Cron::preflight(PreflightContext const& ctx)
{

View File

@@ -30,7 +30,7 @@ namespace ripple {
class Cron : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Custom};
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit Cron(ApplyContext& ctx) : Transactor(ctx)
{
@@ -39,9 +39,6 @@ public:
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx);
static TxConsequences
makeTxConsequences(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);

View File

@@ -27,12 +27,6 @@
namespace ripple {
TxConsequences
CronSet::makeTxConsequences(PreflightContext const& ctx)
{
return TxConsequences{ctx.tx, TxConsequences::normal};
}
NotTEC
CronSet::preflight(PreflightContext const& ctx)
{

View File

@@ -29,7 +29,7 @@ namespace ripple {
class CronSet : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Custom};
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit CronSet(ApplyContext& ctx) : Transactor(ctx)
{
@@ -38,9 +38,6 @@ public:
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx);
static TxConsequences
makeTxConsequences(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);

View File

@@ -26,12 +26,6 @@
namespace ripple {
TxConsequences
Invoke::makeTxConsequences(PreflightContext const& ctx)
{
return TxConsequences{ctx.tx, TxConsequences::normal};
}
NotTEC
Invoke::preflight(PreflightContext const& ctx)
{

View File

@@ -30,7 +30,7 @@ namespace ripple {
class Invoke : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Custom};
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit Invoke(ApplyContext& ctx) : Transactor(ctx)
{
@@ -39,9 +39,6 @@ public:
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx);
static TxConsequences
makeTxConsequences(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);

View File

@@ -30,12 +30,6 @@
namespace ripple {
TxConsequences
SetRemarks::makeTxConsequences(PreflightContext const& ctx)
{
return TxConsequences{ctx.tx, TxConsequences::normal};
}
NotTEC
SetRemarks::validateRemarks(STArray const& remarks, beast::Journal const& j)
{

View File

@@ -30,7 +30,7 @@ namespace ripple {
class SetRemarks : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Custom};
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit SetRemarks(ApplyContext& ctx) : Transactor(ctx)
{
@@ -39,9 +39,6 @@ public:
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx);
static TxConsequences
makeTxConsequences(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);

View File

@@ -2436,9 +2436,6 @@ PeerImp::onMessage(std::shared_ptr<protocol::TMGetObjectByHash> const& m)
if (packet.has_seq())
reply.set_seq(packet.seq());
if (packet.has_requestid())
reply.set_requestid(packet.requestid());
reply.set_type(packet.type());
if (packet.has_ledgerhash())
@@ -2751,9 +2748,6 @@ PeerImp::doTransactions(
{
protocol::TMTransactions reply;
if (packet->has_requestid())
reply.set_requestid(packet->requestid());
JLOG(p_journal_.trace()) << "received TMGetObjectByHash requesting tx "
<< packet->objects_size();
@@ -3255,12 +3249,6 @@ PeerImp::processLedgerRequest(std::shared_ptr<protocol::TMGetLedger> const& m)
protocol::TMLedgerData ledgerData;
bool fatLeaves{true};
auto const itype{m->itype()};
auto const copyRequestMetadata = [&] {
if (m->has_requestcookie())
ledgerData.set_requestcookie(m->requestcookie());
if (m->has_requestid())
ledgerData.set_requestid(m->requestid());
};
if (itype == protocol::liTS_CANDIDATE)
{
@@ -3272,7 +3260,8 @@ PeerImp::processLedgerRequest(std::shared_ptr<protocol::TMGetLedger> const& m)
ledgerData.set_ledgerseq(0);
ledgerData.set_ledgerhash(m->ledgerhash());
ledgerData.set_type(protocol::liTS_CANDIDATE);
copyRequestMetadata();
if (m->has_requestcookie())
ledgerData.set_requestcookie(m->requestcookie());
// We'll already have most transactions
fatLeaves = false;
@@ -3299,7 +3288,8 @@ PeerImp::processLedgerRequest(std::shared_ptr<protocol::TMGetLedger> const& m)
ledgerData.set_ledgerhash(ledgerHash.begin(), ledgerHash.size());
ledgerData.set_ledgerseq(ledger->info().seq);
ledgerData.set_type(itype);
copyRequestMetadata();
if (m->has_requestcookie())
ledgerData.set_requestcookie(m->requestcookie());
switch (itype)
{

View File

@@ -22,6 +22,7 @@
#include <xrpld/app/main/Application.h>
#include <xrpld/app/misc/AmendmentTable.h>
#include <xrpld/app/misc/NetworkOPs.h>
#include <xrpld/core/Config.h>
#include <xrpld/rpc/detail/TransactionSign.h>
#include <xrpl/json/json_value.h>
#include <xrpl/json/json_writer.h>
@@ -545,6 +546,25 @@ doServerDefinitions(RPC::JsonContext& context)
features[to_string(h)][jss::majority] =
t.time_since_epoch().count();
// getJson's enabled is on-ledger; [features] also apply here.
for (auto const& name : features.getMemberNames())
{
Json::Value& entry = features[name];
entry[jss::ledger_enabled] = entry[jss::enabled].asBool();
}
for (auto const& h : context.app.config().features)
{
Json::Value& entry = features[to_string(h)];
if (!entry.isMember(jss::name))
{
if (auto const fname = featureToName(h); !fname.empty())
entry[jss::name] = fname;
}
if (!entry.isMember(jss::ledger_enabled))
entry[jss::ledger_enabled] = false;
entry[jss::enabled] = true;
}
lastFeatures = features;
{
const std::string out = Json::FastWriter().write(features);