mirror of
https://github.com/XRPLF/rippled.git
synced 2026-09-16 04:18:33 +00:00
Compare commits
1 Commits
token-payc
...
dangell7/b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1a9539a6c |
@@ -28,6 +28,7 @@ This section contains changes targeting a future version.
|
||||
|
||||
### Additions
|
||||
|
||||
- `tx`: A validated `Batch` transaction now includes `inner_results`, an array in `RawTransactions` order with one entry per inner transaction the node applied: `hash`, `result` (the transaction result code) and `applied` (whether its changes ended up in the ledger; `false` for an inner transaction that failed with a `ter`/`tem`/`tef` code, and for every inner transaction of a `tfAllOrNothing` batch that rolled back). Inner transactions that fail are not in the ledger, so this is the only record of them. The field is present only when this node built the validated ledger itself and has `[ledger_tx_tables]` enabled; a node that acquired the ledger from the network omits it.
|
||||
- `account_tx`: Added an optional `delegate` request object to filter delegated transactions. The object requires `delegate_filter`, which must be either `actor` for transactions owned by the requested account but signed by another account, or `authorizer` for transactions signed by the requested account on behalf of another account. The optional `counter_party` account narrows the results to a specific signer/delegate for `actor` or a specific owner/delegator for `authorizer`. Malformed `delegate`, `delegate_filter`, and `counter_party` values return standard invalid field errors, and invalid account IDs return `actMalformed`.
|
||||
When paginating delegate-filtered queries, a marker from a delegate-filtered query includes a `delegate` flag and is only valid for follow-up requests that also supply `delegate` (mixing marker conventions returns `invalidParams`). Because filtering is applied after the ledger scan, a page may contain fewer results than `limit` (possibly zero) while still returning a marker, so callers must continue until no marker is present.
|
||||
- `ledger_entry`, `account_objects`: The `Delegate` ledger entry now includes an optional `DestinationNode` field, which stores the index into the authorized account's owner directory. This field is present on entries created after bidirectional directory tracking was introduced and may appear in RPC responses for those entries. ([#6681](https://github.com/XRPLF/rippled/pull/6681))
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/CachedView.h>
|
||||
#include <xrpl/ledger/RawView.h>
|
||||
#include <xrpl/protocol/BatchInnerResult.h>
|
||||
#include <xrpl/protocol/Fees.h>
|
||||
#include <xrpl/protocol/Keylet.h>
|
||||
#include <xrpl/protocol/LedgerHeader.h>
|
||||
@@ -325,6 +326,24 @@ public:
|
||||
bool
|
||||
addSLE(SLE const& sle);
|
||||
|
||||
/**
|
||||
* @brief Inner Batch outcomes observed while this node built the ledger.
|
||||
*
|
||||
* Empty for a ledger acquired from the network or loaded from the database. Not part of
|
||||
* the ledger hash; persisted to the transaction tables when the ledger is saved.
|
||||
*/
|
||||
void
|
||||
setBatchInnerResults(std::vector<BatchInnerResult> results)
|
||||
{
|
||||
batchInnerResults_ = std::move(results);
|
||||
}
|
||||
|
||||
std::vector<BatchInnerResult> const&
|
||||
batchInnerResults() const
|
||||
{
|
||||
return batchInnerResults_;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
void
|
||||
@@ -433,6 +452,7 @@ private:
|
||||
Rules rules_;
|
||||
LedgerHeader header_;
|
||||
beast::Journal j_;
|
||||
std::vector<BatchInnerResult> batchInnerResults_;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
37
include/xrpl/protocol/BatchInnerResult.h
Normal file
37
include/xrpl/protocol/BatchInnerResult.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* @brief Outcome of one inner transaction of a Batch, recorded while the outer Batch was
|
||||
* applied.
|
||||
*
|
||||
* Inner transactions that fail with a ter/tem/tef result are never applied and leave no
|
||||
* metadata in the ledger, and a tfAllOrNothing rollback discards the ones that did apply.
|
||||
* This record is the only place those outcomes survive. It is node-local and not part of
|
||||
* the ledger hash.
|
||||
*/
|
||||
struct BatchInnerResult
|
||||
{
|
||||
uint256 parentBatchId;
|
||||
uint256 innerTxId;
|
||||
|
||||
/**
|
||||
* @brief Position in the outer transaction's RawTransactions.
|
||||
*/
|
||||
std::uint32_t index = 0;
|
||||
|
||||
TER ter = tesSUCCESS;
|
||||
|
||||
/**
|
||||
* @brief True if the inner transaction's changes ended up in the ledger.
|
||||
*/
|
||||
bool applied = false;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -299,6 +299,7 @@ JSS(in); // out: OverlayImpl
|
||||
JSS(inLedger); // out: tx/Transaction
|
||||
JSS(inbound); // out: PeerImp
|
||||
JSS(index); // in: LedgerEntry
|
||||
JSS(inner_results); // out: Tx
|
||||
// out: STLedgerEntry, LedgerEntry, TxHistory, LedgerData
|
||||
JSS(info); // out: ServerInfo, ConsensusInfo, FetchInfo
|
||||
JSS(initial_sync_duration_us); //
|
||||
|
||||
@@ -72,7 +72,7 @@ inline constexpr std::array<char const*, 5> kLgrDbInit{
|
||||
// Transaction database holds transactions and public keys
|
||||
inline constexpr auto kTxDbName{"transaction.db"};
|
||||
|
||||
inline constexpr std::array<char const*, 8> kTxDbInit{
|
||||
inline constexpr std::array<char const*, 10> kTxDbInit{
|
||||
{"BEGIN TRANSACTION;",
|
||||
|
||||
"CREATE TABLE IF NOT EXISTS Transactions ( \
|
||||
@@ -101,6 +101,21 @@ inline constexpr std::array<char const*, 8> kTxDbInit{
|
||||
"CREATE INDEX IF NOT EXISTS AcctLgrIndex ON \
|
||||
AccountTransactions(LedgerSeq, Account, TransID);",
|
||||
|
||||
// Outcome of each inner transaction of a Batch this node applied while building a
|
||||
// validated ledger. Inner transactions that failed are not in the ledger, so this is
|
||||
// the only record of them. See BatchInnerResult.
|
||||
"CREATE TABLE IF NOT EXISTS BatchInnerResults ( \
|
||||
ParentBatchID CHARACTER(64) NOT NULL, \
|
||||
InnerTxnID CHARACTER(64) NOT NULL, \
|
||||
TxnIndex INTEGER NOT NULL, \
|
||||
LedgerSeq BIGINT UNSIGNED NOT NULL, \
|
||||
TERResult INTEGER NOT NULL, \
|
||||
Applied INTEGER NOT NULL, \
|
||||
PRIMARY KEY (ParentBatchID, InnerTxnID) \
|
||||
);",
|
||||
"CREATE INDEX IF NOT EXISTS BatchInnerLgrIndex ON \
|
||||
BatchInnerResults(LedgerSeq);",
|
||||
|
||||
"END TRANSACTION;"}};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/BatchInnerResult.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/LedgerHeader.h>
|
||||
#include <xrpl/protocol/LedgerShortcut.h>
|
||||
@@ -228,6 +229,16 @@ public:
|
||||
virtual std::optional<LedgerIndex>
|
||||
getAccountTransactionsMinLedgerSeq() = 0;
|
||||
|
||||
/**
|
||||
* @brief getBatchInnerResults Returns the recorded inner-transaction outcomes of a
|
||||
* Batch this node applied while building a validated ledger.
|
||||
* @param parentBatchId Hash of the outer Batch transaction.
|
||||
* @return Outcomes in RawTransactions order; empty if the tx tables are disabled or
|
||||
* nothing was recorded (a ledger acquired from the network records nothing).
|
||||
*/
|
||||
virtual std::vector<BatchInnerResult>
|
||||
getBatchInnerResults(uint256 const& parentBatchId) = 0;
|
||||
|
||||
/**
|
||||
* @brief deleteTransactionByLedgerSeq Deletes transactions from the ledger
|
||||
* with the given sequence.
|
||||
|
||||
@@ -4,12 +4,14 @@
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/OpenView.h>
|
||||
#include <xrpl/protocol/BatchInnerResult.h>
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/tx/applySteps.h>
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -142,6 +144,7 @@ enum class ApplyTransactionResult {
|
||||
* Provides more detailed logging and decodes the
|
||||
* correct behavior based on the `TER` type
|
||||
*
|
||||
* @param innerResults If set, receives one entry per inner transaction the outer Batch ran.
|
||||
* @see ApplyTransactionResult
|
||||
*/
|
||||
ApplyTransactionResult
|
||||
@@ -151,6 +154,7 @@ applyTransaction(
|
||||
STTx const& tx,
|
||||
bool retryAssured,
|
||||
ApplyFlags flags,
|
||||
beast::Journal journal);
|
||||
beast::Journal journal,
|
||||
std::vector<BatchInnerResult>* innerResults = nullptr);
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/OpenView.h>
|
||||
#include <xrpl/protocol/BatchInnerResult.h>
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
@@ -17,9 +18,11 @@
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
#include <xrpl/tx/applySteps.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -144,7 +147,8 @@ applyBatchTransactions(
|
||||
ServiceRegistry& registry,
|
||||
OpenView& batchView,
|
||||
STTx const& batchTxn,
|
||||
beast::Journal j)
|
||||
beast::Journal j,
|
||||
std::vector<BatchInnerResult>* innerResults)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
batchTxn.getTxnType() == ttBATCH && !batchTxn.getFieldArray(sfRawTransactions).empty(),
|
||||
@@ -152,8 +156,9 @@ applyBatchTransactions(
|
||||
|
||||
auto const parentBatchId = batchTxn.getTransactionID();
|
||||
auto const mode = batchTxn.getFlags();
|
||||
std::uint32_t index = 0;
|
||||
|
||||
auto applyOneTransaction = [®istry, &j, &parentBatchId, &batchView](STTx const& tx) {
|
||||
auto applyOneTransaction = [&](STTx const& tx) {
|
||||
OpenView perTxBatchView(kBatchView, batchView);
|
||||
|
||||
auto const ret = apply(registry, perTxBatchView, parentBatchId, tx, TapBatch, j);
|
||||
@@ -164,6 +169,17 @@ applyBatchTransactions(
|
||||
JLOG(j.debug()) << "BatchTrace[" << parentBatchId << "]: " << tx.getTransactionID() << " "
|
||||
<< (ret.applied ? "applied" : "failure") << ": " << transToken(ret.ter);
|
||||
|
||||
if (innerResults != nullptr)
|
||||
{
|
||||
innerResults->push_back(
|
||||
{.parentBatchId = parentBatchId,
|
||||
.innerTxId = tx.getTransactionID(),
|
||||
.index = index,
|
||||
.ter = ret.ter,
|
||||
.applied = ret.applied});
|
||||
}
|
||||
++index;
|
||||
|
||||
// If the transaction should be applied push its changes to the
|
||||
// whole-batch view.
|
||||
// NOTE: each inner tx is individually capped at kOversizeMetaDataCap;
|
||||
@@ -176,6 +192,7 @@ applyBatchTransactions(
|
||||
};
|
||||
|
||||
int applied = 0;
|
||||
auto const firstResult = innerResults != nullptr ? innerResults->size() : 0;
|
||||
|
||||
for (auto const& stx : batchTxn.getBatchTransactions())
|
||||
{
|
||||
@@ -190,7 +207,18 @@ applyBatchTransactions(
|
||||
if (!isTesSuccess(result.ter))
|
||||
{
|
||||
if ((mode & tfAllOrNothing) != 0u)
|
||||
{
|
||||
// The whole-batch view is discarded, so nothing recorded so far reached the
|
||||
// ledger.
|
||||
if (innerResults != nullptr)
|
||||
{
|
||||
for (auto i = firstResult; i < innerResults->size(); ++i)
|
||||
{
|
||||
(*innerResults)[i].applied = false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((mode & tfUntilFailure) != 0u)
|
||||
break;
|
||||
@@ -211,7 +239,8 @@ applyTransaction(
|
||||
STTx const& txn,
|
||||
bool retryAssured,
|
||||
ApplyFlags flags,
|
||||
beast::Journal j)
|
||||
beast::Journal j,
|
||||
std::vector<BatchInnerResult>* innerResults)
|
||||
{
|
||||
// Returns false if the transaction has need not be retried.
|
||||
if (retryAssured)
|
||||
@@ -233,7 +262,7 @@ applyTransaction(
|
||||
{
|
||||
OpenView wholeBatchView(kBatchView, view);
|
||||
|
||||
if (applyBatchTransactions(registry, wholeBatchView, txn, j))
|
||||
if (applyBatchTransactions(registry, wholeBatchView, txn, j, innerResults))
|
||||
wholeBatchView.apply(view);
|
||||
}
|
||||
|
||||
|
||||
@@ -5896,6 +5896,97 @@ class Batch_test : public beast::unit_test::Suite
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testInnerResults(FeatureBitset features)
|
||||
{
|
||||
// `tx` on a validated Batch reports each inner transaction's outcome from the
|
||||
// BatchInnerResults table, including inner transactions that never reached the
|
||||
// ledger.
|
||||
testcase("inner results");
|
||||
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env{*this, features};
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
env.fund(XRP(10000), alice, bob);
|
||||
env.close();
|
||||
|
||||
// tfAllOrNothing with a future-sequence inner: the first inner applies to the batch
|
||||
// view and is then rolled back, the second fails with terPRE_SEQ. Neither is in the
|
||||
// ledger; both are recorded, both applied == false.
|
||||
{
|
||||
auto const aliceSeq = env.seq(alice);
|
||||
auto const batchFee = batch::calcBatchFee(env, 1, 2);
|
||||
auto const [txIDs, batchID] = submitBatch(
|
||||
env,
|
||||
tesSUCCESS,
|
||||
batch::outer(alice, aliceSeq, batchFee, tfAllOrNothing),
|
||||
batch::Inner(pay(alice, bob, XRP(10)), aliceSeq + 1),
|
||||
batch::Inner(pay(alice, bob, XRP(5)), aliceSeq + 10));
|
||||
env.close();
|
||||
|
||||
auto const jrr = env.rpc("tx", batchID)[jss::result];
|
||||
BEAST_EXPECT(jrr[jss::validated] == true);
|
||||
if (BEAST_EXPECT(jrr.isMember(jss::inner_results)))
|
||||
{
|
||||
auto const& inner = jrr[jss::inner_results];
|
||||
BEAST_EXPECT(inner.isArray());
|
||||
if (BEAST_EXPECT(inner.size() == 2))
|
||||
{
|
||||
BEAST_EXPECT(inner[0u][jss::hash] == txIDs[0]);
|
||||
BEAST_EXPECT(inner[0u][jss::result] == "tesSUCCESS");
|
||||
BEAST_EXPECT(inner[0u][jss::applied] == false);
|
||||
BEAST_EXPECT(inner[1u][jss::hash] == txIDs[1]);
|
||||
BEAST_EXPECT(inner[1u][jss::result] == "terPRE_SEQ");
|
||||
BEAST_EXPECT(inner[1u][jss::applied] == false);
|
||||
}
|
||||
}
|
||||
// The rolled-back inner transaction is not in the ledger.
|
||||
BEAST_EXPECT(env.rpc("tx", txIDs[0])[jss::result][jss::error] == "txnNotFound");
|
||||
}
|
||||
|
||||
// tfIndependent, both succeed: both applied, in RawTransactions order.
|
||||
{
|
||||
auto const aliceSeq = env.seq(alice);
|
||||
auto const batchFee = batch::calcBatchFee(env, 1, 2);
|
||||
auto const [txIDs, batchID] = submitBatch(
|
||||
env,
|
||||
tesSUCCESS,
|
||||
batch::outer(alice, aliceSeq, batchFee, tfIndependent),
|
||||
batch::Inner(pay(alice, bob, XRP(10)), aliceSeq + 1),
|
||||
batch::Inner(pay(alice, bob, XRP(5)), aliceSeq + 2));
|
||||
env.close();
|
||||
|
||||
auto const jrr = env.rpc("tx", batchID)[jss::result];
|
||||
if (BEAST_EXPECT(jrr.isMember(jss::inner_results)))
|
||||
{
|
||||
auto const& inner = jrr[jss::inner_results];
|
||||
if (BEAST_EXPECT(inner.size() == 2))
|
||||
{
|
||||
for (unsigned i = 0; i < 2; ++i)
|
||||
{
|
||||
BEAST_EXPECT(inner[i][jss::hash] == txIDs[i]);
|
||||
BEAST_EXPECT(inner[i][jss::result] == "tesSUCCESS");
|
||||
BEAST_EXPECT(inner[i][jss::applied] == true);
|
||||
}
|
||||
}
|
||||
}
|
||||
// The inner transactions themselves carry no inner_results.
|
||||
BEAST_EXPECT(!env.rpc("tx", txIDs[0])[jss::result].isMember(jss::inner_results));
|
||||
}
|
||||
|
||||
// A non-Batch transaction never has the field.
|
||||
{
|
||||
auto const jt = env.jt(pay(alice, bob, XRP(1)));
|
||||
env(jt);
|
||||
env.close();
|
||||
auto const jrr = env.rpc("tx", strHex(jt.stx->getTransactionID()))[jss::result];
|
||||
BEAST_EXPECT(jrr[jss::validated] == true);
|
||||
BEAST_EXPECT(!jrr.isMember(jss::inner_results));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testWithFeats(FeatureBitset features)
|
||||
{
|
||||
@@ -5931,6 +6022,7 @@ class Batch_test : public beast::unit_test::Suite
|
||||
testBatchDelegateConsent(features);
|
||||
testValidateRPCResponse(features);
|
||||
testBatchCalculateBaseFee(features);
|
||||
testInnerResults(features);
|
||||
testStandaloneInnerBatchFlag(features);
|
||||
testOuterBinding(features);
|
||||
testUnsortedBatchSigners(features);
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <xrpl/ledger/Ledger.h>
|
||||
#include <xrpl/ledger/OpenView.h>
|
||||
#include <xrpl/nodestore/NodeObject.h>
|
||||
#include <xrpl/protocol/BatchInnerResult.h>
|
||||
#include <xrpl/protocol/Indexes.h> // IWYU pragma: keep
|
||||
#include <xrpl/protocol/LedgerHeader.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
@@ -24,6 +25,8 @@
|
||||
#include <exception>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -89,6 +92,7 @@ buildLedgerImpl(
|
||||
* @param failed set of transactions that failed to apply
|
||||
* @param view ledger to apply to
|
||||
* @param j Journal for logging
|
||||
* @param innerResults receives the inner-transaction outcomes of every Batch applied
|
||||
* @return number of transactions applied; transactions to retry left in txns
|
||||
*/
|
||||
|
||||
@@ -99,7 +103,8 @@ applyTransactions(
|
||||
CanonicalTXSet& txns,
|
||||
std::set<TxID>& failed,
|
||||
OpenView& view,
|
||||
beast::Journal j)
|
||||
beast::Journal j,
|
||||
std::vector<BatchInnerResult>& innerResults)
|
||||
{
|
||||
bool certainRetry = true;
|
||||
std::size_t count = 0;
|
||||
@@ -125,7 +130,8 @@ applyTransactions(
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (applyTransaction(app, view, *it->second, certainRetry, TapNone, j))
|
||||
switch (applyTransaction(
|
||||
app, view, *it->second, certainRetry, TapNone, j, &innerResults))
|
||||
{
|
||||
case ApplyTransactionResult::Success:
|
||||
it = txns.erase(it);
|
||||
@@ -196,7 +202,10 @@ buildLedger(
|
||||
[&](OpenView& accum, std::shared_ptr<Ledger> const& built) {
|
||||
JLOG(j.debug()) << "Attempting to apply " << txns.size() << " transactions";
|
||||
|
||||
auto const applied = applyTransactions(app, built, txns, failedTxns, accum, j);
|
||||
std::vector<BatchInnerResult> innerResults;
|
||||
auto const applied =
|
||||
applyTransactions(app, built, txns, failedTxns, accum, j, innerResults);
|
||||
built->setBatchInnerResults(std::move(innerResults));
|
||||
|
||||
if (!txns.empty() || !failedTxns.empty())
|
||||
{
|
||||
@@ -234,6 +243,7 @@ buildLedger(
|
||||
app,
|
||||
j,
|
||||
[&](OpenView& accum, std::shared_ptr<Ledger> const& built) {
|
||||
std::vector<BatchInnerResult> innerResults;
|
||||
for (auto& tx : replayData.orderedTxns())
|
||||
{
|
||||
// Inner batch transactions are applied as part of their outer
|
||||
@@ -242,8 +252,9 @@ buildLedger(
|
||||
// replay.
|
||||
if (tx.second->isFlag(tfInnerBatchTxn))
|
||||
continue;
|
||||
applyTransaction(app, accum, *tx.second, false, applyFlags, j);
|
||||
applyTransaction(app, accum, *tx.second, false, applyFlags, j, &innerResults);
|
||||
}
|
||||
built->setBatchInnerResults(std::move(innerResults));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <xrpl/basics/RangeSet.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/protocol/BatchInnerResult.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/LedgerHeader.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
@@ -151,6 +152,9 @@ public:
|
||||
void
|
||||
deleteTransactionsBeforeLedgerSeq(LedgerIndex ledgerSeq) override;
|
||||
|
||||
std::vector<BatchInnerResult>
|
||||
getBatchInnerResults(uint256 const& parentBatchId) override;
|
||||
|
||||
/**
|
||||
* @brief deleteAccountTransactionsBeforeLedgerSeq Deletes all account
|
||||
* transactions with a sequence number less than or equal to the
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <xrpl/ledger/PendingSaves.h>
|
||||
#include <xrpl/nodestore/NodeObject.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/BatchInnerResult.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/HashPrefix.h>
|
||||
#include <xrpl/protocol/LedgerHeader.h>
|
||||
@@ -32,6 +33,7 @@
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxMeta.h>
|
||||
#include <xrpl/protocol/TxSearched.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
@@ -81,7 +83,7 @@ namespace xrpl::detail {
|
||||
static std::string
|
||||
toString(TableType type)
|
||||
{
|
||||
static_assert(kTableTypeCount == 3, "Need to modify switch statement if enum is modified");
|
||||
static_assert(kTableTypeCount == 4, "Need to modify switch statement if enum is modified");
|
||||
|
||||
switch (type)
|
||||
{
|
||||
@@ -91,6 +93,8 @@ toString(TableType type)
|
||||
return "Transactions";
|
||||
case TableType::AccountTransactions:
|
||||
return "AccountTransactions";
|
||||
case TableType::BatchInnerResults:
|
||||
return "BatchInnerResults";
|
||||
// LCOV_EXCL_START
|
||||
default:
|
||||
UNREACHABLE("xrpl::detail::toString : invalid TableType");
|
||||
@@ -185,6 +189,43 @@ deleteBeforeLedgerSeq(soci::session& session, TableType type, LedgerIndex ledger
|
||||
session << "DELETE FROM " << toString(type) << " WHERE LedgerSeq < " << ledgerSeq << ";";
|
||||
}
|
||||
|
||||
std::vector<BatchInnerResult>
|
||||
getBatchInnerResults(soci::session& session, uint256 const& parentBatchId)
|
||||
{
|
||||
std::vector<BatchInnerResult> results;
|
||||
|
||||
std::string const parentHex = to_string(parentBatchId);
|
||||
std::string innerHex;
|
||||
int index = 0;
|
||||
int ter = 0;
|
||||
int applied = 0;
|
||||
|
||||
soci::statement st =
|
||||
(session.prepare << "SELECT InnerTxnID, TxnIndex, TERResult, Applied "
|
||||
"FROM BatchInnerResults WHERE ParentBatchID = :parent "
|
||||
"ORDER BY TxnIndex ASC;",
|
||||
soci::use(parentHex),
|
||||
soci::into(innerHex),
|
||||
soci::into(index),
|
||||
soci::into(ter),
|
||||
soci::into(applied));
|
||||
st.execute();
|
||||
|
||||
while (st.fetch())
|
||||
{
|
||||
BatchInnerResult r;
|
||||
r.parentBatchId = parentBatchId;
|
||||
if (!r.innerTxId.parseHex(innerHex))
|
||||
continue;
|
||||
r.index = static_cast<std::uint32_t>(index);
|
||||
r.ter = TER::fromInt(ter);
|
||||
r.applied = applied != 0;
|
||||
results.push_back(r);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
std::size_t
|
||||
getRows(soci::session& session, TableType type)
|
||||
{
|
||||
@@ -285,6 +326,12 @@ saveValidatedLedger(
|
||||
"DELETE FROM AccountTransactions WHERE LedgerSeq = {};";
|
||||
static constexpr char const* kDeleteAcctTrans =
|
||||
"DELETE FROM AccountTransactions WHERE TransID = '{}';";
|
||||
static constexpr char const* kDeleteBatchInner =
|
||||
"DELETE FROM BatchInnerResults WHERE LedgerSeq = {};";
|
||||
static constexpr char const* kAddBatchInner =
|
||||
"INSERT OR REPLACE INTO BatchInnerResults "
|
||||
"(ParentBatchID, InnerTxnID, TxnIndex, LedgerSeq, TERResult, Applied) "
|
||||
"VALUES (:parent, :inner, :idx, :seq, :ter, :applied);";
|
||||
|
||||
{
|
||||
auto db = ldgDB.checkoutDb();
|
||||
@@ -307,6 +354,7 @@ saveValidatedLedger(
|
||||
|
||||
*db << std::format(kDeleteTranS1, seq);
|
||||
*db << std::format(kDeleteTranS2, seq);
|
||||
*db << std::format(kDeleteBatchInner, seq);
|
||||
|
||||
std::string const ledgerSeq(std::to_string(seq));
|
||||
|
||||
@@ -379,6 +427,19 @@ saveValidatedLedger(
|
||||
app.getNetworkIDService().getNetworkID());
|
||||
}
|
||||
|
||||
// Only a ledger this node built carries these; one acquired from the network
|
||||
// has none and writes nothing.
|
||||
for (auto const& r : ledger->batchInnerResults())
|
||||
{
|
||||
std::string const parent = to_string(r.parentBatchId);
|
||||
std::string const inner = to_string(r.innerTxId);
|
||||
int const idx = static_cast<int>(r.index);
|
||||
int const ter = TERtoInt(r.ter);
|
||||
int const applied = r.applied ? 1 : 0;
|
||||
*db << kAddBatchInner, soci::use(parent), soci::use(inner), soci::use(idx),
|
||||
soci::use(seq), soci::use(ter), soci::use(applied);
|
||||
}
|
||||
|
||||
tr.commit();
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <xrpl/core/PerfLog.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/Ledger.h>
|
||||
#include <xrpl/protocol/BatchInnerResult.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/LedgerHeader.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
@@ -32,8 +33,8 @@
|
||||
namespace xrpl::detail {
|
||||
|
||||
/* Need to change TableTypeCount if TableType is modified. */
|
||||
enum class TableType { Ledgers, Transactions, AccountTransactions };
|
||||
constexpr int kTableTypeCount = 3;
|
||||
enum class TableType { Ledgers, Transactions, AccountTransactions, BatchInnerResults };
|
||||
constexpr int kTableTypeCount = 4;
|
||||
|
||||
struct DatabasePairValid
|
||||
{
|
||||
@@ -96,6 +97,15 @@ deleteByLedgerSeq(soci::session& session, TableType type, LedgerIndex ledgerSeq)
|
||||
void
|
||||
deleteBeforeLedgerSeq(soci::session& session, TableType type, LedgerIndex ledgerSeq);
|
||||
|
||||
/**
|
||||
* @brief getBatchInnerResults Returns the recorded inner-transaction outcomes of a Batch.
|
||||
* @param session Session with the database.
|
||||
* @param parentBatchId Hash of the outer Batch transaction.
|
||||
* @return Outcomes in RawTransactions order; empty if none were recorded.
|
||||
*/
|
||||
std::vector<BatchInnerResult>
|
||||
getBatchInnerResults(soci::session& session, uint256 const& parentBatchId);
|
||||
|
||||
/**
|
||||
* @brief getRows Returns number of rows in given table.
|
||||
* @param session Session with database.
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/ledger/Ledger.h>
|
||||
#include <xrpl/protocol/BatchInnerResult.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/LedgerHeader.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
@@ -112,6 +113,7 @@ SQLiteDatabase::deleteTransactionByLedgerSeq(LedgerIndex ledgerSeq)
|
||||
{
|
||||
auto db = checkoutTransaction();
|
||||
detail::deleteByLedgerSeq(*db, detail::TableType::Transactions, ledgerSeq);
|
||||
detail::deleteByLedgerSeq(*db, detail::TableType::BatchInnerResults, ledgerSeq);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -137,6 +139,7 @@ SQLiteDatabase::deleteTransactionsBeforeLedgerSeq(LedgerIndex ledgerSeq)
|
||||
{
|
||||
auto db = checkoutTransaction();
|
||||
detail::deleteBeforeLedgerSeq(*db, detail::TableType::Transactions, ledgerSeq);
|
||||
detail::deleteBeforeLedgerSeq(*db, detail::TableType::BatchInnerResults, ledgerSeq);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -155,6 +158,21 @@ SQLiteDatabase::deleteAccountTransactionsBeforeLedgerSeq(LedgerIndex ledgerSeq)
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<BatchInnerResult>
|
||||
SQLiteDatabase::getBatchInnerResults(uint256 const& parentBatchId)
|
||||
{
|
||||
if (!useTxTables_)
|
||||
return {};
|
||||
|
||||
if (existsTransaction())
|
||||
{
|
||||
auto db = checkoutTransaction();
|
||||
return detail::getBatchInnerResults(*db, parentBatchId);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::size_t
|
||||
SQLiteDatabase::getTransactionCount()
|
||||
{
|
||||
|
||||
@@ -18,10 +18,13 @@
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/core/NetworkIDService.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/BatchInnerResult.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/RPCErr.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STBase.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
#include <xrpl/protocol/TxSearched.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/rdb/RelationalDatabase.h>
|
||||
@@ -34,6 +37,7 @@
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -58,6 +62,7 @@ struct TxResult
|
||||
std::optional<NetClock::time_point> closeTime;
|
||||
std::optional<uint256> ledgerHash;
|
||||
TxSearched searchedAll = TxSearched::Unknown;
|
||||
std::vector<BatchInnerResult> innerResults;
|
||||
};
|
||||
|
||||
struct TxArgs
|
||||
@@ -159,7 +164,14 @@ doTxHelp(rpc::Context& context, TxArgs args)
|
||||
result.validated =
|
||||
isValidated(context.ledgerMaster, ledger->header().seq, ledger->header().hash);
|
||||
if (result.validated)
|
||||
{
|
||||
result.closeTime = context.ledgerMaster.getCloseTimeBySeq(txn->getLedger());
|
||||
if (txn->getSTransaction()->getTxnType() == ttBATCH)
|
||||
{
|
||||
result.innerResults =
|
||||
context.app.getRelationalDatabase().getBatchInnerResults(txn->getID());
|
||||
}
|
||||
}
|
||||
|
||||
// compute outgoing CTID
|
||||
if (meta->getAsObject().isFieldPresent(sfTransactionIndex))
|
||||
@@ -260,6 +272,20 @@ populateJsonResponse(
|
||||
|
||||
if (result.ctid)
|
||||
response[jss::ctid] = *(result.ctid);
|
||||
|
||||
// Present only for a validated Batch whose inner outcomes this node recorded while
|
||||
// building the ledger. In RawTransactions order.
|
||||
if (!result.innerResults.empty())
|
||||
{
|
||||
auto& inner = response[jss::inner_results] = json::ValueType::Array;
|
||||
for (auto const& r : result.innerResults)
|
||||
{
|
||||
json::Value& entry = inner.append(json::ValueType::Object);
|
||||
entry[jss::hash] = to_string(r.innerTxId);
|
||||
entry[jss::result] = transToken(r.ter);
|
||||
entry[jss::applied] = r.applied;
|
||||
}
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user