Compare commits

...

5 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
616b3bf6d8 Fix build: include NetworkIDService.h for complete type definition
The build was failing because NetworkIDService was only forward-declared
via ServiceRegistry.h. Added the full header include to resolve the
incomplete type error when calling getNetworkID().

Co-authored-by: mvadari <8029314+mvadari@users.noreply.github.com>
2026-02-20 16:29:12 +00:00
copilot-swe-agent[bot]
7f34e69550 Fix formatting issue in testLedgerAccountsOption
Co-authored-by: mvadari <8029314+mvadari@users.noreply.github.com>
2026-02-20 15:08:25 +00:00
copilot-swe-agent[bot]
9e7a0ebfd7 Remove accidentally committed build artifacts and update .gitignore
Co-authored-by: mvadari <8029314+mvadari@users.noreply.github.com>
2026-02-20 15:07:54 +00:00
copilot-swe-agent[bot]
2083d6cb96 Add CTID to ledger command expanded transactions response
Include CTID (Compact Transaction Identifier) in expanded transaction
objects returned by the 'ledger' RPC command. This applies to all
expanded formats: binary, API v1 non-binary, and API v2 non-binary.

The CTID is computed from the ledger sequence, transaction index, and
network ID - consistent with how it's done in the 'tx' command and
transaction stream subscriptions.

Co-authored-by: mvadari <8029314+mvadari@users.noreply.github.com>
2026-02-20 15:07:39 +00:00
copilot-swe-agent[bot]
53e633dee5 Initial plan 2026-02-20 14:59:00 +00:00
3 changed files with 136 additions and 0 deletions

1
.gitignore vendored
View File

@@ -74,3 +74,4 @@ DerivedData
# clangd cache
/.cache
build_nocmake/

View File

@@ -6,6 +6,7 @@
#include <test/jtx/xchain_bridge.h>
#include <xrpld/app/misc/TxQ.h>
#include <xrpld/rpc/CTID.h>
#include <xrpl/beast/unit_test.h>
#include <xrpl/json/json_value.h>
@@ -671,6 +672,126 @@ class LedgerRPC_test : public beast::unit_test::suite
}
}
void
testLedgerExpandedTransactionsCTID()
{
testcase("Ledger Expanded Transactions Include CTID");
using namespace test::jtx;
// Use a specific network ID so we can verify it in the CTID
constexpr uint32_t NETWORK_ID = 11111;
auto cfg = envconfig([&](std::unique_ptr<Config> cfg) {
cfg->NETWORK_ID = NETWORK_ID;
return cfg;
});
Env env{*this, std::move(cfg)};
uint32_t netID = env.app().getNetworkIDService().getNetworkID();
BEAST_EXPECT(netID == NETWORK_ID);
Account const alice{"alice"};
env.fund(XRP(10000), alice);
env.close();
// Submit a transaction and close the ledger
env(noop(alice));
env.close();
auto const ledgerSeq = env.closed()->header().seq;
// Request ledger with expanded transactions (API v2, non-binary)
{
Json::Value jv;
jv[jss::ledger_index] = ledgerSeq;
jv[jss::transactions] = true;
jv[jss::expand] = true;
jv[jss::api_version] = 2;
auto jrr = env.rpc("json", "ledger", to_string(jv))[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& tx : txns)
{
BEAST_EXPECT(tx.isMember(jss::ctid));
auto const ctid = tx[jss::ctid].asString();
// Verify CTID can be decoded and matches the ledger
auto const decoded = RPC::decodeCTID(ctid);
if (BEAST_EXPECT(decoded.has_value()))
{
auto const [seq, idx, net] = *decoded;
BEAST_EXPECT(seq == ledgerSeq);
BEAST_EXPECT(net == NETWORK_ID);
}
}
}
// Request ledger with expanded transactions (API v1, non-binary)
{
Json::Value jv;
jv[jss::ledger_index] = ledgerSeq;
jv[jss::transactions] = true;
jv[jss::expand] = true;
auto jrr = env.rpc("json", "ledger", to_string(jv))[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& tx : txns)
{
BEAST_EXPECT(tx.isMember(jss::ctid));
auto const ctid = tx[jss::ctid].asString();
auto const decoded = RPC::decodeCTID(ctid);
if (BEAST_EXPECT(decoded.has_value()))
{
auto const [seq, idx, net] = *decoded;
BEAST_EXPECT(seq == ledgerSeq);
BEAST_EXPECT(net == NETWORK_ID);
}
}
}
// Request ledger with expanded transactions (binary)
{
Json::Value jv;
jv[jss::ledger_index] = ledgerSeq;
jv[jss::transactions] = true;
jv[jss::expand] = true;
jv[jss::binary] = true;
auto jrr = env.rpc("json", "ledger", to_string(jv))[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& tx : txns)
{
BEAST_EXPECT(tx.isMember(jss::ctid));
auto const ctid = tx[jss::ctid].asString();
auto const decoded = RPC::decodeCTID(ctid);
if (BEAST_EXPECT(decoded.has_value()))
{
auto const [seq, idx, net] = *decoded;
BEAST_EXPECT(seq == ledgerSeq);
BEAST_EXPECT(net == NETWORK_ID);
}
}
}
// Request ledger with non-expanded transactions (should NOT have CTID)
{
Json::Value jv;
jv[jss::ledger_index] = ledgerSeq;
jv[jss::transactions] = true;
jv[jss::expand] = false;
auto jrr = env.rpc("json", "ledger", to_string(jv))[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& tx : txns)
{
// Non-expanded transactions are just hash strings
BEAST_EXPECT(tx.isString());
}
}
}
public:
void
run() override
@@ -685,6 +806,7 @@ public:
testNoQueue();
testQueue();
testLedgerAccountsOption();
testLedgerExpandedTransactionsCTID();
}
};

View File

@@ -2,11 +2,13 @@
#include <xrpld/app/ledger/LedgerToJson.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/DeliveredAmount.h>
#include <xrpld/rpc/MPTokenIssuanceID.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/core/NetworkIDService.h>
#include <xrpl/protocol/ApiVersion.h>
#include <xrpl/protocol/jss.h>
@@ -166,6 +168,17 @@ fillJsonTx(
}
}
// compute outgoing CTID
if (fill.context && stMeta && stMeta->isFieldPresent(sfTransactionIndex))
{
uint32_t lgrSeq = fill.ledger.seq();
uint32_t txnIdx = stMeta->getFieldU32(sfTransactionIndex);
uint32_t netID = fill.context->app.getNetworkIDService().getNetworkID();
if (auto ctid = RPC::encodeCTID(lgrSeq, txnIdx, netID))
txJson[jss::ctid] = *ctid;
}
if ((fill.options & LedgerFill::ownerFunds) && txn->getTxnType() == ttOFFER_CREATE)
{
auto const account = txn->getAccountID(sfAccount);