fix: Add CTID to ledger command expanded transactions (#6401)

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Mayukha Vadari <mvadari@gmail.com>
Co-authored-by: Mayukha Vadari <mvadari@ripple.com>
Co-authored-by: xrplf-ai-reviewer[bot] <266832837+xrplf-ai-reviewer[bot]@users.noreply.github.com>
Co-authored-by: Timur Yalymov <36795566+tyalymov@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Vito Tumas <5780819+Tapanito@users.noreply.github.com>
Co-authored-by: Ayaz Salikhov <mathbunnyru@users.noreply.github.com>
This commit is contained in:
Copilot
2026-09-22 20:19:05 +00:00
committed by GitHub
parent d8870162eb
commit 8f4e9c25d8
2 changed files with 110 additions and 0 deletions

View File

@@ -13,17 +13,21 @@
#include <test/jtx/ter.h>
#include <xrpld/app/misc/TxQ.h>
#include <xrpld/rpc/CTID.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/config/Constants.h>
#include <xrpl/core/NetworkIDService.h>
#include <xrpl/json/json_value.h>
#include <xrpl/json/to_string.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/jss.h>
#include <cstdint>
#include <memory>
#include <string>
#include <utility>
@@ -807,6 +811,95 @@ class LedgerRPC_test : public beast::unit_test::Suite
}
}
void
testLedgerExpandedTransactionsCTID()
{
testcase("Expanded Transactions CTID");
using namespace test::jtx;
Env env{*this};
Account const alice{"alice"};
env.fund(XRP(10000), alice);
env.close();
uint32_t const netID = env.app().getNetworkIDService().getNetworkID();
// API v2 non-binary: CTID present
{
json::Value jvParams;
jvParams[jss::ledger_index] = "validated";
jvParams[jss::transactions] = true;
jvParams[jss::expand] = true;
jvParams[jss::api_version] = 2;
auto const jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::status] == "success");
auto const& txns = jrr[jss::ledger][jss::transactions];
BEAST_EXPECT(txns.isArray() && txns.size() > 0);
for (auto const& txn : txns)
{
BEAST_EXPECT(txn.isMember(jss::ctid));
auto const expectedCtid = rpc::encodeCTID(
jrr[jss::ledger][jss::ledger_index].asUInt(),
txn[jss::meta][sfTransactionIndex.jsonName].asUInt(),
netID);
// NOLINTBEGIN(bugprone-unchecked-optional-access)
if (BEAST_EXPECT(expectedCtid.has_value()))
BEAST_EXPECT(txn[jss::ctid] == expectedCtid.value());
// NOLINTEND(bugprone-unchecked-optional-access)
}
}
// API v1 non-binary: CTID present
{
json::Value jvParams;
jvParams[jss::ledger_index] = "validated";
jvParams[jss::transactions] = true;
jvParams[jss::expand] = true;
auto const jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::status] == "success");
auto const& txns = jrr[jss::ledger][jss::transactions];
BEAST_EXPECT(txns.isArray() && txns.size() > 0);
for (auto const& txn : txns)
{
BEAST_EXPECT(txn.isMember(jss::ctid));
}
}
// Binary expanded: CTID present
{
json::Value jvParams;
jvParams[jss::ledger_index] = "validated";
jvParams[jss::transactions] = true;
jvParams[jss::expand] = true;
jvParams[jss::binary] = true;
jvParams[jss::api_version] = 2;
auto const jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::status] == "success");
auto const& txns = jrr[jss::ledger][jss::transactions];
BEAST_EXPECT(txns.isArray() && txns.size() > 0);
for (auto const& txn : txns)
{
BEAST_EXPECT(txn.isMember(jss::ctid));
}
}
// Non-expanded: transactions are plain hash strings, no CTID
{
json::Value jvParams;
jvParams[jss::ledger_index] = "validated";
jvParams[jss::transactions] = true;
jvParams[jss::api_version] = 2;
auto const jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::status] == "success");
auto const& txns = jrr[jss::ledger][jss::transactions];
BEAST_EXPECT(txns.isArray() && txns.size() > 0);
for (auto const& txn : txns)
{
BEAST_EXPECT(txn.isString());
}
}
}
public:
void
run() override
@@ -822,6 +915,7 @@ public:
testNoQueue();
testQueue();
testLedgerAccountsOption();
testLedgerExpandedTransactionsCTID();
}
};

View File

@@ -3,6 +3,7 @@
#include <xrpld/app/ledger/LedgerMaster.h>
#include <xrpld/app/misc/DeliverMax.h>
#include <xrpld/app/misc/TxQ.h>
#include <xrpld/rpc/CTID.h>
#include <xrpld/rpc/Context.h>
#include <xrpld/rpc/detail/SyntheticFields.h>
@@ -11,6 +12,7 @@
#include <xrpl/basics/chrono.h>
#include <xrpl/basics/strHex.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/core/NetworkIDService.h>
#include <xrpl/json/json_value.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
@@ -27,6 +29,7 @@
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol/serialize.h>
#include <cstdint>
#include <exception>
#include <memory>
#include <string>
@@ -179,6 +182,19 @@ fillJsonTx(
}
}
// compute outgoing CTID
if (stMeta && stMeta->isFieldPresent(sfTransactionIndex))
{
uint32_t const lgrSeq = fill.ledger.seq();
uint32_t const txnIdx = stMeta->getFieldU32(sfTransactionIndex);
uint32_t netID = fill.context->app.getNetworkIDService().getNetworkID();
if (txn->isFieldPresent(sfNetworkID))
netID = txn->getFieldU32(sfNetworkID);
if (auto ctid = rpc::encodeCTID(lgrSeq, txnIdx, netID))
txJson[jss::ctid] = *ctid;
}
if (((fill.options & static_cast<int>(LedgerFill::Options::OwnerFunds)) != 0) &&
txn->getTxnType() == ttOFFER_CREATE)
{