Add Formats and Flags to server_definitions (#6321)

This change implements https://github.com/XRPLF/XRPL-Standards/discussions/418: "System XLS: Add Formats and Flags to server_definitions".
This commit is contained in:
Alex Kremer
2026-03-05 16:11:27 +00:00
committed by GitHub
parent 08e734457f
commit dde450784d
29 changed files with 1165 additions and 499 deletions

View File

@@ -1,6 +1,9 @@
#include <test/jtx.h>
#include <xrpl/beast/unit_test.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SOTemplate.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/jss.h>
namespace xrpl {
@@ -81,43 +84,371 @@ public:
BEAST_EXPECT(types["Hash384"].asUInt() == 22);
BEAST_EXPECT(types["Hash512"].asUInt() == 23);
}
}
// test providing the same hash
{
Env env(*this);
auto const firstResult = env.rpc("server_definitions");
auto const hash = firstResult[jss::result][jss::hash].asString();
auto const hashParam = std::string("{ ") + "\"hash\": \"" + hash + "\"}";
// test the properties of the LEDGER_ENTRY_FLAGS section
{
BEAST_EXPECT(result[jss::result].isMember(jss::LEDGER_ENTRY_FLAGS));
Json::Value const& leFlags = result[jss::result][jss::LEDGER_ENTRY_FLAGS];
auto const result = env.rpc("json", "server_definitions", hashParam);
BEAST_EXPECT(!result[jss::result].isMember(jss::error));
BEAST_EXPECT(result[jss::result][jss::status] == "success");
BEAST_EXPECT(!result[jss::result].isMember(jss::FIELDS));
BEAST_EXPECT(!result[jss::result].isMember(jss::LEDGER_ENTRY_TYPES));
BEAST_EXPECT(!result[jss::result].isMember(jss::TRANSACTION_RESULTS));
BEAST_EXPECT(!result[jss::result].isMember(jss::TRANSACTION_TYPES));
BEAST_EXPECT(!result[jss::result].isMember(jss::TYPES));
BEAST_EXPECT(result[jss::result].isMember(jss::hash));
}
// sanity test the mapped value of a few arbitrarily chosen flags
BEAST_EXPECT(leFlags["AccountRoot"]["lsfDisallowXRP"] == 0x00080000);
BEAST_EXPECT(leFlags["AccountRoot"]["lsfDepositAuth"] == 0x01000000);
BEAST_EXPECT(leFlags["AccountRoot"]["lsfAllowTrustLineClawback"] == 0x80000000);
// test providing a different hash
{
Env env(*this);
std::string const hash =
"54296160385A27154BFA70A239DD8E8FD4CC2DB7BA32D970BA3A5B132CF749"
"D1";
auto const hashParam = std::string("{ ") + "\"hash\": \"" + hash + "\"}";
BEAST_EXPECT(leFlags["RippleState"]["lsfHighFreeze"] == 0x00800000);
BEAST_EXPECT(leFlags["RippleState"]["lsfAMMNode"] == 0x01000000);
auto const result = env.rpc("json", "server_definitions", hashParam);
BEAST_EXPECT(!result[jss::result].isMember(jss::error));
BEAST_EXPECT(result[jss::result][jss::status] == "success");
BEAST_EXPECT(result[jss::result].isMember(jss::FIELDS));
BEAST_EXPECT(result[jss::result].isMember(jss::LEDGER_ENTRY_TYPES));
BEAST_EXPECT(result[jss::result].isMember(jss::TRANSACTION_RESULTS));
BEAST_EXPECT(result[jss::result].isMember(jss::TRANSACTION_TYPES));
BEAST_EXPECT(result[jss::result].isMember(jss::TYPES));
BEAST_EXPECT(result[jss::result].isMember(jss::hash));
BEAST_EXPECT(leFlags["DirNode"]["lsfNFTokenBuyOffers"] == 0x00000001);
BEAST_EXPECT(leFlags["MPTokenIssuance"]["lsfMPTCanTrade"] == 0x00000010);
BEAST_EXPECT(leFlags["Credential"]["lsfAccepted"] == 0x00010000);
BEAST_EXPECT(leFlags["Loan"]["lsfLoanImpaired"] == 0x00020000);
BEAST_EXPECT(leFlags["Vault"]["lsfVaultPrivate"] == 0x00010000);
BEAST_EXPECT(leFlags["MPToken"]["lsfMPTAuthorized"] == 0x00000002);
}
// validate the correctness of few chosen transaction flags
{
BEAST_EXPECT(result[jss::result].isMember(jss::TRANSACTION_FLAGS));
Json::Value const& txFlags = result[jss::result][jss::TRANSACTION_FLAGS];
BEAST_EXPECT(txFlags["universal"]["tfFullyCanonicalSig"] == 0x80000000);
BEAST_EXPECT(txFlags["universal"]["tfInnerBatchTxn"] == 0x40000000);
BEAST_EXPECT(txFlags["AccountSet"]["tfRequireAuth"] == 0x00040000);
BEAST_EXPECT(txFlags["AccountSet"]["tfAllowXRP"] == 0x00200000);
BEAST_EXPECT(txFlags["MPTokenIssuanceSet"]["tfMPTLock"] == 0x00000001);
BEAST_EXPECT(txFlags["MPTokenIssuanceSet"]["tfMPTUnlock"] == 0x00000002);
BEAST_EXPECT(txFlags["AMMDeposit"]["tfLPToken"] == 0x00010000);
BEAST_EXPECT(txFlags["AMMDeposit"]["tfLimitLPToken"] == 0x00400000);
}
// validate the correctness of the AccountSpecificFlags section
{
BEAST_EXPECT(result[jss::result].isMember(jss::ACCOUNT_SET_FLAGS));
Json::Value const& asFlags = result[jss::result][jss::ACCOUNT_SET_FLAGS];
BEAST_EXPECT(asFlags["asfDisallowXRP"] == 3);
BEAST_EXPECT(asFlags["asfGlobalFreeze"] == 7);
BEAST_EXPECT(asFlags["asfDisallowIncomingNFTokenOffer"] == 12);
BEAST_EXPECT(asFlags["asfDisallowIncomingTrustline"] == 15);
}
// test the response fields of the TRANSACTION_FORMATS section
{
BEAST_EXPECT(result[jss::result].isMember(jss::TRANSACTION_FORMATS));
Json::Value const& txnFormats = result[jss::result][jss::TRANSACTION_FORMATS];
// first validate the contents of "common"
{
BEAST_EXPECT(txnFormats.isMember("common"));
Json::Value const& section = txnFormats["common"];
BEAST_EXPECT(section[0u][jss::name] == "TransactionType");
BEAST_EXPECT(section[0u][jss::optionality] == soeREQUIRED);
BEAST_EXPECT(section[1u][jss::name] == "Flags");
BEAST_EXPECT(section[1u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(section[2u][jss::name] == "SourceTag");
BEAST_EXPECT(section[2u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(section[3u][jss::name] == "Account");
BEAST_EXPECT(section[3u][jss::optionality] == soeREQUIRED);
BEAST_EXPECT(section[4u][jss::name] == "Sequence");
BEAST_EXPECT(section[4u][jss::optionality] == soeREQUIRED);
BEAST_EXPECT(section[5u][jss::name] == "PreviousTxnID");
BEAST_EXPECT(section[5u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(section[6u][jss::name] == "LastLedgerSequence");
BEAST_EXPECT(section[6u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(section[7u][jss::name] == "AccountTxnID");
BEAST_EXPECT(section[7u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(section[8u][jss::name] == "Fee");
BEAST_EXPECT(section[8u][jss::optionality] == soeREQUIRED);
BEAST_EXPECT(section[9u][jss::name] == "OperationLimit");
BEAST_EXPECT(section[9u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(section[10u][jss::name] == "Memos");
BEAST_EXPECT(section[10u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(section[11u][jss::name] == "SigningPubKey");
BEAST_EXPECT(section[11u][jss::optionality] == soeREQUIRED);
BEAST_EXPECT(section[12u][jss::name] == "TicketSequence");
BEAST_EXPECT(section[12u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(section[13u][jss::name] == "TxnSignature");
BEAST_EXPECT(section[13u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(section[14u][jss::name] == "Signers");
BEAST_EXPECT(section[14u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(section[15u][jss::name] == "NetworkID");
BEAST_EXPECT(section[15u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(section[16u][jss::name] == "Delegate");
BEAST_EXPECT(section[16u][jss::optionality] == soeOPTIONAL);
}
// validate the contents of four arbitrarily selected transactions validate the
// format of the OracleSet transaction
{
BEAST_EXPECT(txnFormats.isMember("OracleSet"));
Json::Value const& section = txnFormats["OracleSet"];
BEAST_EXPECT(section[0u][jss::name] == "OracleDocumentID");
BEAST_EXPECT(section[0u][jss::optionality] == soeREQUIRED);
BEAST_EXPECT(section[1u][jss::name] == "Provider");
BEAST_EXPECT(section[1u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(section[2u][jss::name] == "URI");
BEAST_EXPECT(section[2u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(section[3u][jss::name] == "AssetClass");
BEAST_EXPECT(section[3u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(section[4u][jss::name] == "LastUpdateTime");
BEAST_EXPECT(section[4u][jss::optionality] == soeREQUIRED);
BEAST_EXPECT(section[5u][jss::name] == "PriceDataSeries");
BEAST_EXPECT(section[5u][jss::optionality] == soeREQUIRED);
}
// validate the format of the PermissionedDomainDelete transaction
{
BEAST_EXPECT(txnFormats.isMember("PermissionedDomainDelete"));
Json::Value const& section = txnFormats["PermissionedDomainDelete"];
BEAST_EXPECT(section[0u][jss::name] == "DomainID");
BEAST_EXPECT(section[0u][jss::optionality] == soeREQUIRED);
}
// validate the format of the Clawback transaction
{
BEAST_EXPECT(txnFormats.isMember("Clawback"));
Json::Value const& section = txnFormats["Clawback"];
BEAST_EXPECT(section[0u][jss::name] == "Amount");
BEAST_EXPECT(section[0u][jss::optionality] == soeREQUIRED);
BEAST_EXPECT(section[1u][jss::name] == "Holder");
BEAST_EXPECT(section[1u][jss::optionality] == soeOPTIONAL);
}
// validate the format of the SetFee transaction
{
BEAST_EXPECT(txnFormats.isMember("SetFee"));
Json::Value const& section = txnFormats["SetFee"];
BEAST_EXPECT(section[0u][jss::name] == "LedgerSequence");
BEAST_EXPECT(section[0u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(section[1u][jss::name] == "BaseFee");
BEAST_EXPECT(section[1u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(section[2u][jss::name] == "ReferenceFeeUnits");
BEAST_EXPECT(section[2u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(section[3u][jss::name] == "ReserveBase");
BEAST_EXPECT(section[3u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(section[4u][jss::name] == "ReserveIncrement");
BEAST_EXPECT(section[4u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(section[5u][jss::name] == "BaseFeeDrops");
BEAST_EXPECT(section[5u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(section[6u][jss::name] == "ReserveBaseDrops");
BEAST_EXPECT(section[6u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(section[7u][jss::name] == "ReserveIncrementDrops");
BEAST_EXPECT(section[7u][jss::optionality] == soeOPTIONAL);
}
}
// test the properties of the LEDGER_ENTRY_FORMATS section in server_definitions
// response
{
BEAST_EXPECT(result[jss::result].isMember(jss::LEDGER_ENTRY_FORMATS));
// Note: For the purposes of software maintenance, this test does not exhaustively
// validate all the LEDGER_ENTRY_FORMATS
// check "common" first
{
Json::Value const& observedCommonLedgerEntry =
result[jss::result][jss::LEDGER_ENTRY_FORMATS]["common"];
BEAST_EXPECT(observedCommonLedgerEntry[0u][jss::name] == "LedgerIndex");
BEAST_EXPECT(observedCommonLedgerEntry[0u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(observedCommonLedgerEntry[1u][jss::name] == "LedgerEntryType");
BEAST_EXPECT(observedCommonLedgerEntry[1u][jss::optionality] == soeREQUIRED);
BEAST_EXPECT(observedCommonLedgerEntry[2u][jss::name] == "Flags");
BEAST_EXPECT(observedCommonLedgerEntry[2u][jss::optionality] == soeREQUIRED);
}
// test the contents of an arbitrary ledger-entry (DID)
{
Json::Value const& observedDIDLedgerEntry =
result[jss::result][jss::LEDGER_ENTRY_FORMATS]["DID"];
BEAST_EXPECT(observedDIDLedgerEntry[0u][jss::name] == "Account");
BEAST_EXPECT(observedDIDLedgerEntry[0u][jss::optionality] == soeREQUIRED);
BEAST_EXPECT(observedDIDLedgerEntry[1u][jss::name] == "DIDDocument");
BEAST_EXPECT(observedDIDLedgerEntry[1u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(observedDIDLedgerEntry[2u][jss::name] == "URI");
BEAST_EXPECT(observedDIDLedgerEntry[2u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(observedDIDLedgerEntry[3u][jss::name] == "Data");
BEAST_EXPECT(observedDIDLedgerEntry[3u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(observedDIDLedgerEntry[4u][jss::name] == "OwnerNode");
BEAST_EXPECT(observedDIDLedgerEntry[4u][jss::optionality] == soeREQUIRED);
BEAST_EXPECT(observedDIDLedgerEntry[5u][jss::name] == "PreviousTxnID");
BEAST_EXPECT(observedDIDLedgerEntry[5u][jss::optionality] == soeREQUIRED);
BEAST_EXPECT(observedDIDLedgerEntry[6u][jss::name] == "PreviousTxnLgrSeq");
BEAST_EXPECT(observedDIDLedgerEntry[6u][jss::optionality] == soeREQUIRED);
}
// test the contents of an arbitrary ledger-entry (NegativeUNL)
{
Json::Value const& observedNunlLedgerEntry =
result[jss::result][jss::LEDGER_ENTRY_FORMATS]["NegativeUNL"];
BEAST_EXPECT(observedNunlLedgerEntry[0u][jss::name] == "DisabledValidators");
BEAST_EXPECT(observedNunlLedgerEntry[0u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(observedNunlLedgerEntry[1u][jss::name] == "ValidatorToDisable");
BEAST_EXPECT(observedNunlLedgerEntry[1u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(observedNunlLedgerEntry[2u][jss::name] == "ValidatorToReEnable");
BEAST_EXPECT(observedNunlLedgerEntry[2u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(observedNunlLedgerEntry[3u][jss::name] == "PreviousTxnID");
BEAST_EXPECT(observedNunlLedgerEntry[3u][jss::optionality] == soeOPTIONAL);
BEAST_EXPECT(observedNunlLedgerEntry[4u][jss::name] == "PreviousTxnLgrSeq");
BEAST_EXPECT(observedNunlLedgerEntry[4u][jss::optionality] == soeOPTIONAL);
}
}
// Exhaustive test: verify all transaction flags from getAllTxFlags() appear in the
// output
{
Json::Value const& txFlags = result[jss::result][jss::TRANSACTION_FLAGS];
for (auto const& [txName, flagMap] : getAllTxFlags())
{
BEAST_EXPECT(txFlags.isMember(txName));
if (txFlags.isMember(txName))
{
for (auto const& [flagName, flagValue] : flagMap)
{
BEAST_EXPECT(txFlags[txName].isMember(flagName));
if (txFlags[txName].isMember(flagName))
{
BEAST_EXPECT(txFlags[txName][flagName].asUInt() == flagValue);
}
}
}
}
}
// Exhaustive test: verify all ledger entry flags from getAllLedgerFlags() appear in the
// output
{
Json::Value const& leFlags = result[jss::result][jss::LEDGER_ENTRY_FLAGS];
for (auto const& [ledgerType, flagMap] : getAllLedgerFlags())
{
BEAST_EXPECT(leFlags.isMember(ledgerType));
if (leFlags.isMember(ledgerType))
{
for (auto const& [flagName, flagValue] : flagMap)
{
BEAST_EXPECT(leFlags[ledgerType].isMember(flagName));
if (leFlags[ledgerType].isMember(flagName))
{
BEAST_EXPECT(leFlags[ledgerType][flagName].asUInt() == flagValue);
}
}
}
}
}
// Exhaustive test: verify all AccountSet flags from getAsfFlagMap() appear in the
// output
{
Json::Value const& asFlags = result[jss::result][jss::ACCOUNT_SET_FLAGS];
for (auto const& [flagName, flagValue] : getAsfFlagMap())
{
BEAST_EXPECT(asFlags.isMember(flagName));
if (asFlags.isMember(flagName))
{
BEAST_EXPECT(asFlags[flagName].asInt() == flagValue);
}
}
}
// test providing the same hash
{
Env env(*this);
auto const firstResult = env.rpc("server_definitions");
auto const hash = firstResult[jss::result][jss::hash].asString();
auto const hashParam = std::string("{ ") + "\"hash\": \"" + hash + "\"}";
auto const result = env.rpc("json", "server_definitions", hashParam);
BEAST_EXPECT(!result[jss::result].isMember(jss::error));
BEAST_EXPECT(result[jss::result][jss::status] == "success");
BEAST_EXPECT(!result[jss::result].isMember(jss::FIELDS));
BEAST_EXPECT(!result[jss::result].isMember(jss::LEDGER_ENTRY_TYPES));
BEAST_EXPECT(!result[jss::result].isMember(jss::LEDGER_ENTRY_FLAGS));
BEAST_EXPECT(!result[jss::result].isMember(jss::LEDGER_ENTRY_FORMATS));
BEAST_EXPECT(!result[jss::result].isMember(jss::TRANSACTION_RESULTS));
BEAST_EXPECT(!result[jss::result].isMember(jss::TRANSACTION_TYPES));
BEAST_EXPECT(!result[jss::result].isMember(jss::TRANSACTION_FLAGS));
BEAST_EXPECT(!result[jss::result].isMember(jss::TRANSACTION_FORMATS));
BEAST_EXPECT(!result[jss::result].isMember(jss::TYPES));
BEAST_EXPECT(result[jss::result].isMember(jss::hash));
}
// test providing a different hash
{
Env env(*this);
std::string const hash =
"54296160385A27154BFA70A239DD8E8FD4CC2DB7BA32D970BA3A5B132CF749"
"D1";
auto const hashParam = std::string("{ ") + "\"hash\": \"" + hash + "\"}";
auto const result = env.rpc("json", "server_definitions", hashParam);
BEAST_EXPECT(!result[jss::result].isMember(jss::error));
BEAST_EXPECT(result[jss::result][jss::status] == "success");
BEAST_EXPECT(result[jss::result].isMember(jss::FIELDS));
BEAST_EXPECT(result[jss::result].isMember(jss::LEDGER_ENTRY_TYPES));
BEAST_EXPECT(result[jss::result].isMember(jss::LEDGER_ENTRY_FLAGS));
BEAST_EXPECT(result[jss::result].isMember(jss::LEDGER_ENTRY_FORMATS));
BEAST_EXPECT(result[jss::result].isMember(jss::TRANSACTION_RESULTS));
BEAST_EXPECT(result[jss::result].isMember(jss::TRANSACTION_TYPES));
BEAST_EXPECT(result[jss::result].isMember(jss::TRANSACTION_FLAGS));
BEAST_EXPECT(result[jss::result].isMember(jss::TRANSACTION_FORMATS));
BEAST_EXPECT(result[jss::result].isMember(jss::TYPES));
BEAST_EXPECT(result[jss::result].isMember(jss::hash));
}
}
}