mirror of
https://github.com/XRPLF/rippled.git
synced 2026-08-28 01:30:58 +00:00
Compare commits
4 Commits
develop
...
dangell7/f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
405c5d57a5 | ||
|
|
92d996cf34 | ||
|
|
9f89fce012 | ||
|
|
aa5987efd0 |
@@ -34,6 +34,7 @@
|
||||
#include <xrpl/protocol/SystemParameters.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
#include <xrpl/protocol/TxMeta.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/server/LoadFeeTrack.h>
|
||||
@@ -905,7 +906,12 @@ Transactor::apply()
|
||||
if (!isTesSuccess(result))
|
||||
return result;
|
||||
|
||||
if (sle->isFieldPresent(sfAccountTxnID))
|
||||
// With fixCleanup3_4_0, a Batch wrapper does not update
|
||||
// sfAccountTxnID: its ID hashes over sfRawTransactions, so a
|
||||
// same-account inner could never carry a matching prior-txn ID. The
|
||||
// inners update the field as they apply.
|
||||
if (sle->isFieldPresent(sfAccountTxnID) &&
|
||||
(ctx_.tx.getTxnType() != ttBATCH || !view().rules().enabled(fixCleanup3_4_0)))
|
||||
sle->setFieldH256(sfAccountTxnID, ctx_.tx.getTransactionID());
|
||||
|
||||
view().update(sle);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/SignerUtils.h>
|
||||
#include <test/jtx/TestHelpers.h>
|
||||
#include <test/jtx/WSClient.h>
|
||||
#include <test/jtx/acctdelete.h>
|
||||
#include <test/jtx/amount.h>
|
||||
#include <test/jtx/balance.h> // IWYU pragma: keep
|
||||
@@ -2972,6 +2973,189 @@ class Batch_test : public beast::unit_test::Suite
|
||||
BEAST_EXPECT(env.balance(bob) == preBob + XRP(1));
|
||||
}
|
||||
|
||||
void
|
||||
testAccountTxnID(FeatureBitset features)
|
||||
{
|
||||
testcase("account txn id");
|
||||
|
||||
using namespace test::jtx;
|
||||
using namespace std::literals;
|
||||
|
||||
// With fixCleanup3_4_0 the Batch wrapper does not stamp
|
||||
// sfAccountTxnID, so an inner can reference the last pre-batch
|
||||
// transaction. Without the fix the wrapper stamps its own ID, which
|
||||
// no inner can carry: the outer's ID hashes over sfRawTransactions.
|
||||
for (bool const withFix : {true, false})
|
||||
{
|
||||
auto const amend = withFix ? features : features - fixCleanup3_4_0;
|
||||
Env env{*this, amend};
|
||||
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
env.fund(XRP(10000), alice, bob);
|
||||
env.close();
|
||||
|
||||
env(fset(alice, asfAccountTxnID));
|
||||
env.close();
|
||||
|
||||
// Arm the tracking field: fset leaves it zero, and the first
|
||||
// transaction after it stamps the first usable prior-txn ID.
|
||||
env(noop(alice));
|
||||
env.close();
|
||||
uint256 const priorID = env.tx()->getTransactionID();
|
||||
|
||||
auto const preBob = env.balance(bob);
|
||||
auto const seq = env.seq(alice);
|
||||
auto const batchFee = batch::calcBatchFee(env, 0, 2);
|
||||
auto tx1 = batch::Inner(pay(alice, bob, XRP(1)), seq + 1);
|
||||
tx1[sfAccountTxnID.jsonName] = strHex(priorID);
|
||||
auto const [txIDs, batchID] = submitBatch(
|
||||
env,
|
||||
tesSUCCESS,
|
||||
batch::outer(alice, seq, batchFee, tfAllOrNothing),
|
||||
tx1,
|
||||
batch::Inner(pay(alice, bob, XRP(2)), seq + 2));
|
||||
env.close();
|
||||
|
||||
auto const sle = env.le(keylet::account(alice));
|
||||
BEAST_EXPECT(sle && sle->isFieldPresent(sfAccountTxnID));
|
||||
if (withFix)
|
||||
{
|
||||
std::vector<TestLedgerData> const testCases = {
|
||||
{.index = 0,
|
||||
.txType = "Batch",
|
||||
.result = "tesSUCCESS",
|
||||
.txHash = batchID,
|
||||
.batchID = std::nullopt},
|
||||
{.index = 1,
|
||||
.txType = "Payment",
|
||||
.result = "tesSUCCESS",
|
||||
.txHash = txIDs[0],
|
||||
.batchID = batchID},
|
||||
{.index = 2,
|
||||
.txType = "Payment",
|
||||
.result = "tesSUCCESS",
|
||||
.txHash = txIDs[1],
|
||||
.batchID = batchID},
|
||||
};
|
||||
validateClosedLedger(env, testCases);
|
||||
|
||||
BEAST_EXPECT(env.seq(alice) == seq + 3);
|
||||
BEAST_EXPECT(env.balance(bob) == preBob + XRP(3));
|
||||
|
||||
// The chain ends at the last applied inner, not the wrapper.
|
||||
BEAST_EXPECT(strHex(sle->getFieldH256(sfAccountTxnID)) == txIDs[1]);
|
||||
|
||||
// A post-batch transaction chains off the last inner.
|
||||
auto jv = pay(alice, bob, XRP(1));
|
||||
jv[sfAccountTxnID.jsonName] = txIDs[1];
|
||||
env(jv);
|
||||
env.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
// tefWRONG_PRIOR on the inner: the wrapper already stamped
|
||||
// its own ID, and tfAllOrNothing reverts every inner.
|
||||
std::vector<TestLedgerData> const testCases = {
|
||||
{.index = 0,
|
||||
.txType = "Batch",
|
||||
.result = "tesSUCCESS",
|
||||
.txHash = batchID,
|
||||
.batchID = std::nullopt},
|
||||
};
|
||||
validateClosedLedger(env, testCases);
|
||||
|
||||
BEAST_EXPECT(env.seq(alice) == seq + 1);
|
||||
BEAST_EXPECT(env.balance(bob) == preBob);
|
||||
|
||||
// The wrapper stamped its own ID.
|
||||
BEAST_EXPECT(strHex(sle->getFieldH256(sfAccountTxnID)) == batchID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testSubscriptions(FeatureBitset features)
|
||||
{
|
||||
testcase("subscriptions");
|
||||
|
||||
using namespace test::jtx;
|
||||
using namespace std::literals;
|
||||
|
||||
Env env{*this, features};
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
env.fund(XRP(10000), alice, bob);
|
||||
env.close();
|
||||
|
||||
auto wsc = makeWSClient(env.app().config());
|
||||
json::Value stream;
|
||||
stream[jss::streams] = json::ValueType::Array;
|
||||
stream[jss::streams].append("transactions");
|
||||
stream[jss::streams].append("transactions_proposed");
|
||||
BEAST_EXPECT(wsc->invoke("subscribe", stream)[jss::status] == "success");
|
||||
|
||||
auto const seq = env.seq(alice);
|
||||
auto const batchFee = batch::calcBatchFee(env, 0, 2);
|
||||
auto const [txIDs, batchID] = submitBatch(
|
||||
env,
|
||||
tesSUCCESS,
|
||||
batch::outer(alice, seq, batchFee, tfAllOrNothing),
|
||||
batch::Inner(pay(alice, bob, XRP(1)), seq + 1),
|
||||
batch::Inner(pay(alice, bob, XRP(2)), seq + 2));
|
||||
env.close();
|
||||
|
||||
// The hash sits under jss::transaction (API v1), jss::tx_json
|
||||
// (API v2), or at the top level, depending on message shape.
|
||||
auto const txHash = [](json::Value const& msg) -> std::string {
|
||||
if (msg.isMember(jss::hash))
|
||||
{
|
||||
return msg[jss::hash].asString();
|
||||
}
|
||||
for (auto const& field : {jss::transaction, jss::tx_json})
|
||||
{
|
||||
if (msg.isMember(field) && msg[field].isMember(jss::hash))
|
||||
{
|
||||
return msg[field][jss::hash].asString();
|
||||
}
|
||||
}
|
||||
return {};
|
||||
};
|
||||
auto const isValidated = [](json::Value const& msg) {
|
||||
return msg.isMember(jss::validated) && msg[jss::validated].asBool();
|
||||
};
|
||||
|
||||
std::vector<json::Value> msgs;
|
||||
while (auto msg = wsc->getMsg(2s))
|
||||
{
|
||||
msgs.push_back(*msg);
|
||||
}
|
||||
|
||||
// Proposed stream: the outer Batch only. pubProposedTransaction
|
||||
// drops tfInnerBatchTxn, so an inner must never appear unvalidated.
|
||||
std::size_t proposed = 0;
|
||||
for (auto const& msg : msgs)
|
||||
{
|
||||
if (!isValidated(msg))
|
||||
{
|
||||
++proposed;
|
||||
BEAST_EXPECT(txHash(msg) == batchID);
|
||||
}
|
||||
}
|
||||
BEAST_EXPECT(proposed == 1);
|
||||
|
||||
// Validated stream: the outer and both inners publish, each with
|
||||
// metadata.
|
||||
for (std::string const& hash : {batchID, txIDs[0], txIDs[1]})
|
||||
{
|
||||
BEAST_EXPECT(std::ranges::any_of(msgs, [&](json::Value const& msg) {
|
||||
return isValidated(msg) && txHash(msg) == hash && msg.isMember(jss::meta);
|
||||
}));
|
||||
}
|
||||
|
||||
BEAST_EXPECT(wsc->invoke("unsubscribe", stream)[jss::status] == "success");
|
||||
}
|
||||
|
||||
void
|
||||
testAccountDelete(FeatureBitset features)
|
||||
{
|
||||
@@ -5914,6 +6098,8 @@ class Batch_test : public beast::unit_test::Suite
|
||||
testAccountActivation(features);
|
||||
testCheckAllSignatures(features);
|
||||
testAccountSet(features);
|
||||
testAccountTxnID(features);
|
||||
testSubscriptions(features);
|
||||
testAccountDelete(features);
|
||||
testLoan(features);
|
||||
testObjectCreateSequence(features);
|
||||
|
||||
Reference in New Issue
Block a user