From 1a3376e453786c8b2ea09304c62fc716863c6cfa Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Thu, 28 Mar 2024 21:48:21 +0100 Subject: [PATCH] build/test --- .vscode/settings.json | 2 +- src/ripple/app/ledger/impl/LedgerMaster.cpp | 1 + src/ripple/app/ledger/impl/OpenLedger.cpp | 3 + src/ripple/app/misc/NetworkOPs.cpp | 1 + src/ripple/app/misc/impl/TxQ.cpp | 13 + src/ripple/app/tx/impl/ApplyContext.cpp | 4 + src/ripple/app/tx/impl/Batch.cpp | 86 ++++-- src/ripple/app/tx/impl/InvariantCheck.cpp | 28 +- src/ripple/app/tx/impl/Payment.cpp | 1 + src/ripple/app/tx/impl/Transactor.cpp | 53 +++- src/ripple/app/tx/impl/apply.cpp | 3 +- .../container/detail/aged_ordered_container.h | 55 +--- .../detail/aged_unordered_container.h | 55 +--- src/ripple/ledger/ApplyView.h | 2 +- src/ripple/ledger/OpenSandbox.h | 65 ++++ src/ripple/ledger/impl/ApplyViewImpl.cpp | 1 + src/ripple/ledger/impl/OpenView.cpp | 4 + src/ripple/peerfinder/impl/Bootcache.h | 7 - src/ripple/peerfinder/impl/Livecache.h | 11 - src/ripple/protocol/SField.h | 4 +- src/ripple/protocol/impl/SField.cpp | 2 + src/ripple/protocol/impl/TxFormats.cpp | 2 +- src/ripple/protocol/jss.h | 3 +- src/ripple/shamap/impl/SHAMapInnerNode.cpp | 2 +- src/test/app/Batch_test.cpp | 280 ++++++++++++++++-- src/test/app/TxQ_test.cpp | 68 ++++- 26 files changed, 588 insertions(+), 168 deletions(-) create mode 100644 src/ripple/ledger/OpenSandbox.h diff --git a/.vscode/settings.json b/.vscode/settings.json index 9e4544373..1642d6324 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,7 +3,7 @@ "C_Cpp.clang_format_path": ".clang-format", "C_Cpp.clang_format_fallbackStyle": "{ ColumnLimit: 0 }", "[cpp]":{ - "editor.wordBasedSuggestions": false, + "editor.wordBasedSuggestions": "off", "editor.suggest.insertMode": "replace", "editor.semanticHighlighting.enabled": true, "editor.tabSize": 4, diff --git a/src/ripple/app/ledger/impl/LedgerMaster.cpp b/src/ripple/app/ledger/impl/LedgerMaster.cpp index 844e9da48..9f3d6d78a 100644 --- a/src/ripple/app/ledger/impl/LedgerMaster.cpp +++ b/src/ripple/app/ledger/impl/LedgerMaster.cpp @@ -554,6 +554,7 @@ LedgerMaster::applyHeldTransactions() bool any = false; for (auto const& it : mHeldTransactions) { + // std::cout << "applyHeldTransactions: TXQu" << "\n"; ApplyFlags flags = tapNONE; auto const result = app_.getTxQ().apply(app_, view, it.second, flags, j); diff --git a/src/ripple/app/ledger/impl/OpenLedger.cpp b/src/ripple/app/ledger/impl/OpenLedger.cpp index 58d2f3b9b..f81def31e 100644 --- a/src/ripple/app/ledger/impl/OpenLedger.cpp +++ b/src/ripple/app/ledger/impl/OpenLedger.cpp @@ -120,7 +120,10 @@ OpenLedger::accept( f(*next, j_); // Apply local tx for (auto const& item : locals) + { + // std::cout << "OpenLedger::accept: TXQu" << "\n"; app.getTxQ().apply(app, *next, item.second, flags, j_); + } // If we didn't relay this transaction recently, relay it to all peers for (auto const& txpair : next->txs) diff --git a/src/ripple/app/misc/NetworkOPs.cpp b/src/ripple/app/misc/NetworkOPs.cpp index 62da31163..a2d195ac3 100644 --- a/src/ripple/app/misc/NetworkOPs.cpp +++ b/src/ripple/app/misc/NetworkOPs.cpp @@ -1397,6 +1397,7 @@ NetworkOPsImp::apply(std::unique_lock& batchLock) if (e.failType == FailHard::yes) flags |= tapFAIL_HARD; + // std::cout << "NetworkOPsImp::apply: TXQu" << "\n"; auto const result = app_.getTxQ().apply( app_, view, e.transaction->getSTransaction(), flags, j); e.result = result.first; diff --git a/src/ripple/app/misc/impl/TxQ.cpp b/src/ripple/app/misc/impl/TxQ.cpp index c22767c2f..fa86d81f9 100644 --- a/src/ripple/app/misc/impl/TxQ.cpp +++ b/src/ripple/app/misc/impl/TxQ.cpp @@ -858,6 +858,9 @@ TxQ::apply( // If the transaction is intending to replace a transaction in the queue // identify the one that might be replaced. + // std::cout << "accountIsInQueue: " << accountIsInQueue << "\n"; + // std::cout << "txSeqProx: " << txSeqProx << "\n"; + // std::cout << "tx: " << tx->getJson(JsonOptions::none) << "\n"; auto replacedTxIter = [accountIsInQueue, &accountIter, txSeqProx]() -> std::optional { if (accountIsInQueue) @@ -879,6 +882,7 @@ TxQ::apply( // Is there a blocker already in the account's queue? If so, don't // allow additional transactions in the queue. + // std::cout << "acctTxCount: " << acctTxCount << "\n"; if (acctTxCount > 0) { // Allow tx to replace a blocker. Otherwise, if there's a @@ -1849,8 +1853,14 @@ TxQ::tryDirectApply( // Can only directly apply if the transaction sequence matches the // account sequence or if the transaction uses a ticket. + // std::cout << "txSeqProx->isSeq(): " << txSeqProx->isSeq() << "\n"; + // std::cout << "txSeqProx: " << *txSeqProx << "\n"; + // std::cout << "acctSeqProx: " << acctSeqProx << "\n"; if (txSeqProx->isSeq() && *txSeqProx != acctSeqProx) + { + // std::cout << "txSeqProx->isSeq() && *txSeqProx != acctSeqProx" << "\n"; return {}; + } } FeeLevel64 const requiredFeeLevel = @@ -1864,6 +1874,9 @@ TxQ::tryDirectApply( // transaction straight into the ledger. FeeLevel64 const feeLevelPaid = getFeeLevelPaid(view, *tx); + // std::cout << "requiredFeeLevel: " << requiredFeeLevel << "\n"; + // std::cout << "feeLevelPaid: " << feeLevelPaid << "\n"; + if (feeLevelPaid >= requiredFeeLevel) { // Attempt to apply the transaction directly. diff --git a/src/ripple/app/tx/impl/ApplyContext.cpp b/src/ripple/app/tx/impl/ApplyContext.cpp index 272872416..ef56ef961 100644 --- a/src/ripple/app/tx/impl/ApplyContext.cpp +++ b/src/ripple/app/tx/impl/ApplyContext.cpp @@ -56,6 +56,10 @@ ApplyContext::discard() void ApplyContext::apply(TER ter) { + std::cout << "ApplyContext::apply" << "\n"; + std::cout << "tx: " << tx.getTransactionID() << "\n"; + // if (flags_ == tapPREFLIGHT_BATCH) + // return; view_->apply(base_, tx, ter, journal); } diff --git a/src/ripple/app/tx/impl/Batch.cpp b/src/ripple/app/tx/impl/Batch.cpp index 1465a9d80..97cd00a4c 100644 --- a/src/ripple/app/tx/impl/Batch.cpp +++ b/src/ripple/app/tx/impl/Batch.cpp @@ -216,10 +216,13 @@ invoke_preclaim(PreclaimContext const& ctx) if (id != beast::zero) { - TER result = T::checkSeqProxy(ctx.view, ctx.tx, ctx.j); + // TER result = T::checkSeqProxy(ctx.view, ctx.tx, ctx.j); - if (result != tesSUCCESS) - return result; + // if (result != tesSUCCESS) + // return result; + + // Ignore Sequence Validation on ttBATCH txns + TER result = tesSUCCESS; result = T::checkPriorTxAndLastLedger(ctx); @@ -286,7 +289,7 @@ Batch::preflight(PreflightContext const& ctx) auto& tx = ctx.tx; - auto const& txns = tx.getFieldArray(sfEmittedTxns); + auto const& txns = tx.getFieldArray(sfRawTransactions); if (txns.empty()) { JLOG(ctx.j.warn()) << "Batch: txns array empty."; @@ -317,7 +320,7 @@ Batch::preflight(PreflightContext const& ctx) ctx.app, stx, ctx.rules, - ripple::ApplyFlags::tapPREFLIGHT_BATCH, + tapPREFLIGHT_BATCH, ctx.j); auto const response = invoke_preflight(pfctx); preflightResponses.push_back(response.first); @@ -331,12 +334,19 @@ std::vector preclaimResponses; TER Batch::preclaim(PreclaimContext const& ctx) { - if (!ctx.view.rules().enabled(featureHooks)) + if (!ctx.view.rules().enabled(featureBatch)) return temDISABLED; - auto const& txns = ctx.tx.getFieldArray(sfEmittedTxns); + auto const& txns = ctx.tx.getFieldArray(sfRawTransactions); for (std::size_t i = 0; i < txns.size(); ++i) { + // Cannot continue on failed txns + if (preflightResponses[i] != tesSUCCESS) + { + JLOG(ctx.j.warn()) << "Batch: Failed Preflight Response: " << preflightResponses[i]; + continue; + } + auto const& txn = txns[i]; if (!txn.isFieldPresent(sfTransactionType)) { @@ -355,13 +365,27 @@ Batch::preclaim(PreclaimContext const& ctx) preclaimResponses.push_back(response); } + // Handle Atomicity + // for (const auto& response : preclaimResponses) + // { + // if (response != tesSUCCESS) + // { + // return response; + // } + // } + return tesSUCCESS; } + TER Batch::doApply() { - auto const& txns = ctx_.tx.getFieldArray(sfEmittedTxns); + std::cout << "Batch::doApply()" << "\n"; + Sandbox sb(&ctx_.view()); + // Sandbox sb(ctx_.base_.base_, view().flags()); + + auto const& txns = ctx_.tx.getFieldArray(sfRawTransactions); for (std::size_t i = 0; i < txns.size(); ++i) { auto const& txn = txns[i]; @@ -377,15 +401,24 @@ Batch::doApply() auto const stx = STTx(txtype, [&txn](STObject& obj) { obj = std::move(txn); }); + // OpenView(&ctx_.view()) ApplyContext actx( ctx_.app, ctx_.base_, stx, preclaimResponses[i], - XRPAmount(1), + ctx_.view().fees().base, view().flags(), ctx_.journal); auto const result = invoke_apply(actx); + // if (result.first == tesSUCCESS) + // { + // ctx_.base_.apply(tesSUCCESS); + // } + // else + // { + // actx.discard(); + // } ApplyViewImpl& avi = dynamic_cast(ctx_.view()); @@ -396,6 +429,15 @@ Batch::doApply() avi.addBatchExecutionMetaData(std::move(meta)); } + auto const sle = ctx_.base_.read(keylet::account(account_)); + if (!sle) + return tefINTERNAL; + + std::cout << "ACCOUNT SEQ: " << sle->getFieldU32(sfSequence) << "\n"; + std::cout << "ACCOUNT BALANCE: " << mSourceBalance << "\n"; + std::cout << "ACCOUNT BALANCE=: " << sle->getFieldAmount(sfBalance) << "\n"; + mSourceBalance = sle->getFieldAmount(sfBalance); + return tesSUCCESS; } @@ -403,18 +445,20 @@ XRPAmount Batch::calculateBaseFee(ReadView const& view, STTx const& tx) { XRPAmount extraFee{0}; - // if (tx.isFieldPresent(sfEmittedTxns)) - // { - // XRPAmount txFees{0}; - // auto const& txns = tx.getFieldArray(sfEmittedTxns); - // for (auto const& txn : txns) - // { - // txFees += txn.isFieldPresent(sfFee) ? txn.getFieldAmount(sfFee) : - // XRPAmount{0}; - // } - // extraFee += txFees; - // } - return Transactor::calculateBaseFee(view, tx) + extraFee; + if (tx.isFieldPresent(sfRawTransactions)) + { + XRPAmount txFees{0}; + auto const& txns = tx.getFieldArray(sfRawTransactions); + for (auto const& txn : txns) + { + auto const tt = txn.getFieldU16(sfTransactionType); + auto const txtype = safe_cast(tt); + auto const stx = STTx(txtype, [&txn](STObject& obj) { obj = std::move(txn); }); + txFees += Transactor::calculateBaseFee(view, tx); + } + extraFee += txFees; + } + return extraFee; } } // namespace ripple diff --git a/src/ripple/app/tx/impl/InvariantCheck.cpp b/src/ripple/app/tx/impl/InvariantCheck.cpp index db477084d..06824ccd9 100644 --- a/src/ripple/app/tx/impl/InvariantCheck.cpp +++ b/src/ripple/app/tx/impl/InvariantCheck.cpp @@ -241,8 +241,32 @@ XRPNotCreated::finalize( return drops_ == drops; } - if (tt == ttBATCH) - return true; + if (tt == ttBATCH && res == tesSUCCESS) + { + drops_ = -fee.drops(); + // return true; + // auto const& txns = tx.getFieldArray(sfRawTransactions); + // XRPAmount dropsAdded{beast::zero}; + // XRPAmount feeAdded{beast::zero}; + // for (auto const& txn : txns) + // { + // dropsAdded += txn.getFieldAmount(sfAmount).xrp(); + // feeAdded += txn.getFieldAmount(sfFee).xrp(); + // } + + // int64_t drops = dropsAdded.drops() - feeAdded.drops(); + + // std::cout << "fee.drops: " << feeAdded << "\n"; + // std::cout << "dropsAdded: " << dropsAdded.drops() << "\n"; + // std::cout << "drops: " << drops << "\n"; + // std::cout << "drops=: " << drops_ << "\n"; + + // // catch any overflow or funny business + // if (drops > dropsAdded.drops()) + // return false; + + // return drops_ == drops; + } // The net change should never be positive, as this would mean that the // transaction created XRP out of thin air. That's not possible. diff --git a/src/ripple/app/tx/impl/Payment.cpp b/src/ripple/app/tx/impl/Payment.cpp index c46bfe704..a871014a4 100644 --- a/src/ripple/app/tx/impl/Payment.cpp +++ b/src/ripple/app/tx/impl/Payment.cpp @@ -300,6 +300,7 @@ Payment::preclaim(PreclaimContext const& ctx) TER Payment::doApply() { + std::cout << "Payment::doApply()" << "\n"; auto const deliverMin = ctx_.tx[~sfDeliverMin]; // Ripple if source or destination is non-native or if there are paths. diff --git a/src/ripple/app/tx/impl/Transactor.cpp b/src/ripple/app/tx/impl/Transactor.cpp index c30377f16..b80feddb8 100644 --- a/src/ripple/app/tx/impl/Transactor.cpp +++ b/src/ripple/app/tx/impl/Transactor.cpp @@ -419,17 +419,46 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee) return temBAD_FEE; auto const feePaid = ctx.tx[sfFee].xrp(); - std::cout << "feePaid: " << feePaid << "\n"; if (!isLegalAmount(feePaid) || feePaid < beast::zero) return temBAD_FEE; // Only check fee is sufficient when the ledger is open. - if (ctx.view.open()) + if (ctx.view.open() && ctx.tx.getTxnType() == ttBATCH) + { + XRPAmount feeDue = XRPAmount{0}; + auto const& txns = ctx.tx.getFieldArray(sfRawTransactions); + for (std::size_t i = 0; i < txns.size(); ++i) + { + auto const& txn = txns[i]; + if (!txn.isFieldPresent(sfFee)) + { + JLOG(ctx.j.warn()) + << "Batch: sfFee missing in array entry."; + return telINSUF_FEE_P; + } + auto const _fee = txn.getFieldAmount(sfFee); + feeDue += _fee.xrp(); + + // auto const tt = txn.getFieldU16(sfTransactionType); + // auto const txtype = safe_cast(tt); + // auto const stx = STTx(txtype, [&txn](STObject& obj) { obj = std::move(txn); }); + // auto const _fee = Transactor::calculateBaseFee(ctx.view, stx); + // feeDue += _fee; + } + + if (feePaid < feeDue) + { + JLOG(ctx.j.trace()) + << "Insufficient fee paid: " << to_string(feePaid) << "/" + << to_string(feeDue); + return telINSUF_FEE_P; + } + } + if (ctx.view.open() && ctx.tx.getTxnType() != ttBATCH) { auto const feeDue = minimumFee(ctx.app, baseFee, ctx.view.fees(), ctx.flags); - std::cout << "feeDue: " << feeDue << "\n"; if (feePaid < feeDue) { JLOG(ctx.j.trace()) @@ -724,6 +753,12 @@ Transactor::apply() { preCompute(); + auto const tt = ctx_.tx.getTxnType(); + std::cout << "tt: " << tt << "\n"; + std::cout << "id: " << ctx_.tx.getTransactionID() << "\n"; + // if (tt == ttBATCH) + // return doApply(); + // If the transactor requires a valid account and the transaction doesn't // list one, preflight will have already a flagged a failure. auto const sle = view().peek(keylet::account(account_)); @@ -738,16 +773,26 @@ Transactor::apply() if (sle) { + // std::cout << "mSourceBalance: " << mSourceBalance << "\n"; + // std::cout << "mPriorBalance: " << mPriorBalance << "\n"; + // std::cout << "mSourceBalance=: " << (mSourceBalance - mPriorBalance) << "\n"; mPriorBalance = STAmount{(*sle)[sfBalance]}.xrp(); + std::cout << "mPriorBalance: " << mPriorBalance << "\n"; mSourceBalance = mPriorBalance; TER result = consumeSeqProxy(sle); if (result != tesSUCCESS) + { + std::cout << "result: " << result << "\n"; return result; + } result = payFee(); if (result != tesSUCCESS) + { + std::cout << "result: " << result << "\n"; return result; + } if (sle->isFieldPresent(sfAccountTxnID)) sle->setFieldH256(sfAccountTxnID, ctx_.tx.getTransactionID()); @@ -755,6 +800,8 @@ Transactor::apply() view().update(sle); } + std::cout << "Transactor::apply" << "\n"; + return doApply(); } diff --git a/src/ripple/app/tx/impl/apply.cpp b/src/ripple/app/tx/impl/apply.cpp index 50570b701..0c7d13616 100644 --- a/src/ripple/app/tx/impl/apply.cpp +++ b/src/ripple/app/tx/impl/apply.cpp @@ -73,7 +73,7 @@ checkValidity( return {Validity::Valid, ""}; } - if (rules.enabled(featureBatch) && applyFlags == tapPREFLIGHT_BATCH) + if (rules.enabled(featureBatch) && applyFlags & tapPREFLIGHT_BATCH) { // batched transactions do not contain signatures if (tx.isFieldPresent(sfTxnSignature)) @@ -83,6 +83,7 @@ checkValidity( if (!passesLocalChecks(tx, reason)) return {Validity::SigGoodOnly, reason}; + router.setFlags(id, SF_SIGGOOD); return {Validity::Valid, ""}; } diff --git a/src/ripple/beast/container/detail/aged_ordered_container.h b/src/ripple/beast/container/detail/aged_ordered_container.h index 23534a26b..10dca962b 100644 --- a/src/ripple/beast/container/detail/aged_ordered_container.h +++ b/src/ripple/beast/container/detail/aged_ordered_container.h @@ -145,111 +145,78 @@ private: }; // VFALCO TODO This should only be enabled for maps. - class pair_value_compare - : public beast::detail::empty_base_optimization -#ifdef _LIBCPP_VERSION - , - public std::binary_function -#endif + class pair_value_compare : public Compare { public: -#ifndef _LIBCPP_VERSION using first_argument = value_type; using second_argument = value_type; using result_type = bool; -#endif bool operator()(value_type const& lhs, value_type const& rhs) const { - return this->member()(lhs.first, rhs.first); + return Compare::operator()(lhs.first, rhs.first); } pair_value_compare() { } - pair_value_compare(pair_value_compare const& other) - : beast::detail::empty_base_optimization(other) + pair_value_compare(pair_value_compare const& other) : Compare(other) { } private: friend aged_ordered_container; - pair_value_compare(Compare const& compare) - : beast::detail::empty_base_optimization(compare) + pair_value_compare(Compare const& compare) : Compare(compare) { } }; // Compares value_type against element, used in insert_check // VFALCO TODO hoist to remove template argument dependencies - class KeyValueCompare - : public beast::detail::empty_base_optimization -#ifdef _LIBCPP_VERSION - , - public std::binary_function -#endif + class KeyValueCompare : public Compare { public: -#ifndef _LIBCPP_VERSION using first_argument = Key; using second_argument = element; using result_type = bool; -#endif KeyValueCompare() = default; - KeyValueCompare(Compare const& compare) - : beast::detail::empty_base_optimization(compare) + KeyValueCompare(Compare const& compare) : Compare(compare) { } - // VFALCO NOTE WE might want only to enable these overloads - // if Compare has is_transparent -#if 0 - template - bool operator() (K const& k, element const& e) const - { - return this->member() (k, extract (e.value)); - } - - template - bool operator() (element const& e, K const& k) const - { - return this->member() (extract (e.value), k); - } -#endif - bool operator()(Key const& k, element const& e) const { - return this->member()(k, extract(e.value)); + return Compare::operator()(k, extract(e.value)); } bool operator()(element const& e, Key const& k) const { - return this->member()(extract(e.value), k); + return Compare::operator()(extract(e.value), k); } bool operator()(element const& x, element const& y) const { - return this->member()(extract(x.value), extract(y.value)); + return Compare::operator()(extract(x.value), extract(y.value)); } Compare& compare() { - return beast::detail::empty_base_optimization::member(); + return *this; } Compare const& compare() const { - return beast::detail::empty_base_optimization::member(); + return *this; } }; diff --git a/src/ripple/beast/container/detail/aged_unordered_container.h b/src/ripple/beast/container/detail/aged_unordered_container.h index 920e6196b..fcdccd2a6 100644 --- a/src/ripple/beast/container/detail/aged_unordered_container.h +++ b/src/ripple/beast/container/detail/aged_unordered_container.h @@ -148,115 +148,84 @@ private: }; // VFALCO TODO hoist to remove template argument dependencies - class ValueHash : private beast::detail::empty_base_optimization -#ifdef _LIBCPP_VERSION - , - public std::unary_function -#endif + class ValueHash : public Hash { public: -#ifndef _LIBCPP_VERSION using argument_type = element; using result_type = size_t; -#endif ValueHash() { } - ValueHash(Hash const& h) - : beast::detail::empty_base_optimization(h) + ValueHash(Hash const& h) : Hash(h) { } std::size_t operator()(element const& e) const { - return this->member()(extract(e.value)); + return Hash::operator()(extract(e.value)); } Hash& hash_function() { - return this->member(); + return *this; } Hash const& hash_function() const { - return this->member(); + return *this; } }; // Compares value_type against element, used in find/insert_check // VFALCO TODO hoist to remove template argument dependencies - class KeyValueEqual - : private beast::detail::empty_base_optimization -#ifdef _LIBCPP_VERSION - , - public std::binary_function -#endif + class KeyValueEqual : public KeyEqual { public: -#ifndef _LIBCPP_VERSION using first_argument_type = Key; using second_argument_type = element; using result_type = bool; -#endif KeyValueEqual() { } - KeyValueEqual(KeyEqual const& keyEqual) - : beast::detail::empty_base_optimization(keyEqual) + KeyValueEqual(KeyEqual const& keyEqual) : KeyEqual(keyEqual) { } - // VFALCO NOTE WE might want only to enable these overloads - // if KeyEqual has is_transparent -#if 0 - template - bool operator() (K const& k, element const& e) const - { - return this->member() (k, extract (e.value)); - } - - template - bool operator() (element const& e, K const& k) const - { - return this->member() (extract (e.value), k); - } -#endif - bool operator()(Key const& k, element const& e) const { - return this->member()(k, extract(e.value)); + return KeyEqual::operator()(k, extract(e.value)); } bool operator()(element const& e, Key const& k) const { - return this->member()(extract(e.value), k); + return KeyEqual::operator()(extract(e.value), k); } bool operator()(element const& lhs, element const& rhs) const { - return this->member()(extract(lhs.value), extract(rhs.value)); + return KeyEqual::operator()(extract(lhs.value), extract(rhs.value)); } KeyEqual& key_eq() { - return this->member(); + return *this; } KeyEqual const& key_eq() const { - return this->member(); + return *this; } }; diff --git a/src/ripple/ledger/ApplyView.h b/src/ripple/ledger/ApplyView.h index 2e28f302e..56977720a 100644 --- a/src/ripple/ledger/ApplyView.h +++ b/src/ripple/ledger/ApplyView.h @@ -44,7 +44,7 @@ enum ApplyFlags : std::uint32_t { tapPREFLIGHT_EMIT = 0x800, // Transaction is being tested against preflight before emission - tapPREFLIGHT_BATCH = 0x120, + tapPREFLIGHT_BATCH = 0x1200, }; constexpr ApplyFlags diff --git a/src/ripple/ledger/OpenSandbox.h b/src/ripple/ledger/OpenSandbox.h new file mode 100644 index 000000000..87f90be2f --- /dev/null +++ b/src/ripple/ledger/OpenSandbox.h @@ -0,0 +1,65 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_LEDGER_OPENSANDBOX_H_INCLUDED +#define RIPPLE_LEDGER_OPENSANDBOX_H_INCLUDED + +#include +#include + +namespace ripple { + +/** Discardable, editable view to a ledger. + + The sandbox inherits the flags of the base. + + @note Presented as ApplyView to clients. +*/ +class OpenSandbox : public detail::OpenView +{ +public: + OpenSandbox() = delete; + OpenSandbox(OpenSandbox const&) = delete; + OpenSandbox& + operator=(OpenSandbox&&) = delete; + OpenSandbox& + operator=(OpenSandbox const&) = delete; + + OpenSandbox(OpenSandbox&&) = default; + + OpenSandbox(ReadView const* base) : OpenView(ReadView const* base, std::shared_ptr hold = nullptr); + { + } + + OpenSandbox(OpenView const* open) : OpenSandbox(open.base_, nullptr) + { + } + + void + apply(RawView& to) + { + items_.apply(to); + for (auto const& item : txs_) + to.rawTxInsert(item.first, item.second.txn, item.second.meta); + } +}; + +} // namespace ripple + +#endif diff --git a/src/ripple/ledger/impl/ApplyViewImpl.cpp b/src/ripple/ledger/impl/ApplyViewImpl.cpp index 44098efe9..8811b003b 100644 --- a/src/ripple/ledger/impl/ApplyViewImpl.cpp +++ b/src/ripple/ledger/impl/ApplyViewImpl.cpp @@ -31,6 +31,7 @@ ApplyViewImpl::ApplyViewImpl(ReadView const* base, ApplyFlags flags) void ApplyViewImpl::apply(OpenView& to, STTx const& tx, TER ter, beast::Journal j) { + std::cout << "ApplyViewImpl::apply" << "\n"; items_.apply(to, tx, ter, deliver_, batchExecution_, hookExecution_, hookEmission_, j); } diff --git a/src/ripple/ledger/impl/OpenView.cpp b/src/ripple/ledger/impl/OpenView.cpp index fd03d7665..9dc3ee8ef 100644 --- a/src/ripple/ledger/impl/OpenView.cpp +++ b/src/ripple/ledger/impl/OpenView.cpp @@ -129,9 +129,13 @@ OpenView::txCount() const void OpenView::apply(TxsRawView& to) const { + std::cout << "OpenView::apply" << "\n"; items_.apply(to); for (auto const& item : txs_) + { + std::cout << "OpenView::apply: " << to_string(item.first) << "\n"; to.rawTxInsert(item.first, item.second.txn, item.second.meta); + } } //--- diff --git a/src/ripple/peerfinder/impl/Bootcache.h b/src/ripple/peerfinder/impl/Bootcache.h index eb6455879..b48f248ae 100644 --- a/src/ripple/peerfinder/impl/Bootcache.h +++ b/src/ripple/peerfinder/impl/Bootcache.h @@ -91,17 +91,10 @@ private: using value_type = map_type::value_type; struct Transform -#ifdef _LIBCPP_VERSION - : std::unary_function< - map_type::right_map::const_iterator::value_type const&, - beast::IP::Endpoint const&> -#endif { -#ifndef _LIBCPP_VERSION using first_argument_type = map_type::right_map::const_iterator::value_type const&; using result_type = beast::IP::Endpoint const&; -#endif explicit Transform() = default; diff --git a/src/ripple/peerfinder/impl/Livecache.h b/src/ripple/peerfinder/impl/Livecache.h index 12e2373fa..8ecd68e84 100644 --- a/src/ripple/peerfinder/impl/Livecache.h +++ b/src/ripple/peerfinder/impl/Livecache.h @@ -69,14 +69,9 @@ public: public: // Iterator transformation to extract the endpoint from Element struct Transform -#ifdef _LIBCPP_VERSION - : public std::unary_function -#endif { -#ifndef _LIBCPP_VERSION using first_argument = Element; using result_type = Endpoint; -#endif explicit Transform() = default; @@ -239,15 +234,9 @@ public: template struct Transform -#ifdef _LIBCPP_VERSION - : public std:: - unary_function> -#endif { -#ifndef _LIBCPP_VERSION using first_argument = typename lists_type::value_type; using result_type = Hop; -#endif explicit Transform() = default; diff --git a/src/ripple/protocol/SField.h b/src/ripple/protocol/SField.h index 3f17c5ec4..6a6aa89a0 100644 --- a/src/ripple/protocol/SField.h +++ b/src/ripple/protocol/SField.h @@ -590,6 +590,8 @@ extern SField const sfHookGrant; extern SField const sfActiveValidator; extern SField const sfImportVLKey; extern SField const sfHookEmission; +extern SField const sfBatchExecution; +extern SField const sfRawTransaction; // array of objects (common) // ARRAY/1 is reserved for end of array @@ -619,7 +621,7 @@ extern SField const sfActiveValidators; extern SField const sfImportVLKeys; extern SField const sfHookEmissions; extern SField const sfBatchExecutions; -extern SField const sfBatchExecution; +extern SField const sfRawTransactions; //------------------------------------------------------------------------------ diff --git a/src/ripple/protocol/impl/SField.cpp b/src/ripple/protocol/impl/SField.cpp index b03b7b10a..ced2237c8 100644 --- a/src/ripple/protocol/impl/SField.cpp +++ b/src/ripple/protocol/impl/SField.cpp @@ -342,6 +342,7 @@ CONSTRUCT_UNTYPED_SFIELD(sfHookExecution, "HookExecution", OBJECT, CONSTRUCT_UNTYPED_SFIELD(sfHookDefinition, "HookDefinition", OBJECT, 22); CONSTRUCT_UNTYPED_SFIELD(sfHookParameter, "HookParameter", OBJECT, 23); CONSTRUCT_UNTYPED_SFIELD(sfHookGrant, "HookGrant", OBJECT, 24); +CONSTRUCT_UNTYPED_SFIELD(sfRawTransaction, "RawTransaction", OBJECT, 99); CONSTRUCT_UNTYPED_SFIELD(sfBatchExecution, "BatchExecution", OBJECT, 97); CONSTRUCT_UNTYPED_SFIELD(sfGenesisMint, "GenesisMint", OBJECT, 96); CONSTRUCT_UNTYPED_SFIELD(sfActiveValidator, "ActiveValidator", OBJECT, 95); @@ -367,6 +368,7 @@ CONSTRUCT_UNTYPED_SFIELD(sfDisabledValidators, "DisabledValidators", ARRAY, CONSTRUCT_UNTYPED_SFIELD(sfHookExecutions, "HookExecutions", ARRAY, 18); CONSTRUCT_UNTYPED_SFIELD(sfHookParameters, "HookParameters", ARRAY, 19); CONSTRUCT_UNTYPED_SFIELD(sfHookGrants, "HookGrants", ARRAY, 20); +CONSTRUCT_UNTYPED_SFIELD(sfRawTransactions, "RawTransactions", ARRAY, 99); CONSTRUCT_UNTYPED_SFIELD(sfBatchExecutions, "BatchExecutions", ARRAY, 98); CONSTRUCT_UNTYPED_SFIELD(sfEmittedTxns, "EmittedTxns", ARRAY, 97); CONSTRUCT_UNTYPED_SFIELD(sfGenesisMints, "GenesisMints", ARRAY, 96); diff --git a/src/ripple/protocol/impl/TxFormats.cpp b/src/ripple/protocol/impl/TxFormats.cpp index 8c5db62b5..be56a1533 100644 --- a/src/ripple/protocol/impl/TxFormats.cpp +++ b/src/ripple/protocol/impl/TxFormats.cpp @@ -445,7 +445,7 @@ TxFormats::TxFormats() add(jss::Batch, ttBATCH, { - {sfEmittedTxns, soeOPTIONAL}, + {sfRawTransactions, soeOPTIONAL}, }, commonFields); } diff --git a/src/ripple/protocol/jss.h b/src/ripple/protocol/jss.h index 2d10a7c29..57487e35b 100644 --- a/src/ripple/protocol/jss.h +++ b/src/ripple/protocol/jss.h @@ -51,7 +51,8 @@ JSS(Amendments); // ledger type. JSS(Amount); // in: TransactionSign; field. JSS(Authorize); // field JSS(Batch); // transaction type. -JSS(Blob); +JSS(RawTransaction); // in: Batch +JSS(Blob); // field. JSS(Check); // ledger type. JSS(CheckCancel); // transaction type. JSS(CheckCash); // transaction type. diff --git a/src/ripple/shamap/impl/SHAMapInnerNode.cpp b/src/ripple/shamap/impl/SHAMapInnerNode.cpp index 6ea6f47eb..1cac616b0 100644 --- a/src/ripple/shamap/impl/SHAMapInnerNode.cpp +++ b/src/ripple/shamap/impl/SHAMapInnerNode.cpp @@ -398,7 +398,7 @@ SHAMapInnerNode::canonicalizeChild( void SHAMapInnerNode::invariants(bool is_root) const { - unsigned count = 0; + [[maybe_unused]] unsigned count = 0; auto [numAllocated, hashes, children] = hashesAndChildren_.getHashesAndChildren(); diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index 1cd675fa3..8086edb5b 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -27,6 +27,28 @@ namespace test { class Batch_test : public beast::unit_test::suite { + + struct TestBatchData + { + std::string result; + std::string txType; + }; + + void + validateBatchTxns( + Json::Value meta, + std::array batchResults) + { + size_t index = 0; + for (auto const& _batchTxn : meta[sfBatchExecutions.jsonName]) + { + auto const batchTxn = _batchTxn[sfBatchExecution.jsonName]; + BEAST_EXPECT(batchTxn[sfTransactionResult.jsonName] == batchResults[index].result); + BEAST_EXPECT(batchTxn[sfTransactionType.jsonName] == batchResults[index].txType); + ++index; + } + } + void testBatch(FeatureBitset features) { @@ -35,11 +57,15 @@ class Batch_test : public beast::unit_test::suite using namespace test::jtx; using namespace std::literals; - // test::jtx::Env env{*this, network::makeNetworkConfig(21337)}; - Env env{*this, envconfig(), features, nullptr, - // beast::severities::kWarning - beast::severities::kTrace - }; + test::jtx::Env env{*this, envconfig()}; + auto const feeDrops = env.current()->fees().base; + // Env env{ + // *this, + // envconfig(), + // features, + // nullptr, + // // beast::severities::kWarning + // beast::severities::kTrace}; auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -48,30 +74,227 @@ class Batch_test : public beast::unit_test::suite env.close(); auto const seq = env.seq("alice"); + + // ttBATCH Json::Value jv; jv[jss::TransactionType] = jss::Batch; jv[jss::Account] = alice.human(); - jv[sfEmittedTxns.jsonName] = Json::Value{Json::arrayValue}; - jv[sfEmittedTxns.jsonName][0U] = Json::Value{}; - jv[sfEmittedTxns.jsonName][0U][jss::EmittedTxn] = Json::Value{}; - jv[sfEmittedTxns.jsonName][0U][jss::EmittedTxn][jss::TransactionType] = jss::Invoke; - jv[sfEmittedTxns.jsonName][0U][jss::EmittedTxn][sfAccount.jsonName] = alice.human(); - jv[sfEmittedTxns.jsonName][0U][jss::EmittedTxn][sfDestination.jsonName] = bob.human(); - jv[sfEmittedTxns.jsonName][0U][jss::EmittedTxn][sfFee.jsonName] = "10"; - jv[sfEmittedTxns.jsonName][0U][jss::EmittedTxn][jss::Sequence] = seq; - jv[sfEmittedTxns.jsonName][0U][jss::EmittedTxn][jss::SigningPubKey] = strHex(alice.pk()); - jv[sfEmittedTxns.jsonName][1U] = Json::Value{}; - jv[sfEmittedTxns.jsonName][1U][jss::EmittedTxn] = Json::Value{}; - jv[sfEmittedTxns.jsonName][1U][jss::EmittedTxn][jss::TransactionType] = jss::Payment; - jv[sfEmittedTxns.jsonName][1U][jss::EmittedTxn][sfAccount.jsonName] = alice.human(); - jv[sfEmittedTxns.jsonName][1U][jss::EmittedTxn][sfDestination.jsonName] = carol.human(); - jv[sfEmittedTxns.jsonName][1U][jss::EmittedTxn][sfAmount.jsonName] = "1000000"; - jv[sfEmittedTxns.jsonName][1U][jss::EmittedTxn][sfFee.jsonName] = "10"; - jv[sfEmittedTxns.jsonName][1U][jss::EmittedTxn][jss::Sequence] = seq; - jv[sfEmittedTxns.jsonName][1U][jss::EmittedTxn][jss::SigningPubKey] = strHex(alice.pk()); - env(jv, fee(drops(10)), ter(tesSUCCESS)); + + // Batch Transactions + jv[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue}; + + // Tx 1 + jv[sfRawTransactions.jsonName][0U] = Json::Value{}; + jv[sfRawTransactions.jsonName][0U][jss::RawTransaction] = Json::Value{}; + jv[sfRawTransactions.jsonName][0U][jss::RawTransaction] + [jss::TransactionType] = jss::AccountSet; + jv[sfRawTransactions.jsonName][0U][jss::RawTransaction] + [sfAccount.jsonName] = alice.human(); + jv[sfRawTransactions.jsonName][0U][jss::RawTransaction][sfFee.jsonName] = + to_string(feeDrops); + jv[sfRawTransactions.jsonName][0U][jss::RawTransaction][jss::Sequence] = + seq + 1; + jv[sfRawTransactions.jsonName][0U][jss::RawTransaction] + [jss::SigningPubKey] = strHex(alice.pk()); + + // Tx 2 + jv[sfRawTransactions.jsonName][1U] = Json::Value{}; + jv[sfRawTransactions.jsonName][1U][jss::RawTransaction] = Json::Value{}; + jv[sfRawTransactions.jsonName][1U][jss::RawTransaction] + [jss::TransactionType] = jss::AccountSet; + jv[sfRawTransactions.jsonName][1U][jss::RawTransaction] + [sfAccount.jsonName] = alice.human(); + jv[sfRawTransactions.jsonName][1U][jss::RawTransaction][sfFee.jsonName] = + to_string(feeDrops); + jv[sfRawTransactions.jsonName][1U][jss::RawTransaction][jss::Sequence] = + seq + 2; + jv[sfRawTransactions.jsonName][1U][jss::RawTransaction] + [jss::SigningPubKey] = strHex(alice.pk()); + + env(jv, fee(drops((2 * feeDrops) + (2 * feeDrops))), ter(tesSUCCESS)); env.close(); } + + void + testSuccess(FeatureBitset features) + { + testcase("batch success"); + + using namespace test::jtx; + using namespace std::literals; + + test::jtx::Env env{*this, envconfig()}; + // Env env{ + // *this, + // envconfig(), + // features, + // nullptr, + // // beast::severities::kWarning + // beast::severities::kTrace}; + + auto const feeDrops = env.current()->fees().base; + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + env.fund(XRP(1000), alice, bob, carol); + env.close(); + + auto const seq = env.seq("alice"); + // ttBATCH + Json::Value jv; + jv[jss::TransactionType] = jss::Batch; + jv[jss::Account] = alice.human(); + + // Batch Transactions + jv[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue}; + + // Tx 1 + jv[sfRawTransactions.jsonName][0U] = Json::Value{}; + jv[sfRawTransactions.jsonName][0U][jss::RawTransaction] = Json::Value{}; + jv[sfRawTransactions.jsonName][0U][jss::RawTransaction] + [jss::TransactionType] = jss::Payment; + jv[sfRawTransactions.jsonName][0U][jss::RawTransaction] + [sfAccount.jsonName] = alice.human(); + jv[sfRawTransactions.jsonName][0U][jss::RawTransaction] + [sfDestination.jsonName] = bob.human(); + jv[sfRawTransactions.jsonName][0U][jss::RawTransaction] + [sfAmount.jsonName] = "1000000"; + jv[sfRawTransactions.jsonName][0U][jss::RawTransaction][sfFee.jsonName] = + to_string(feeDrops); + jv[sfRawTransactions.jsonName][0U][jss::RawTransaction][jss::Sequence] = + seq + 1; + jv[sfRawTransactions.jsonName][0U][jss::RawTransaction] + [jss::SigningPubKey] = strHex(alice.pk()); + + // Tx 2 + jv[sfRawTransactions.jsonName][1U] = Json::Value{}; + jv[sfRawTransactions.jsonName][1U][jss::RawTransaction] = Json::Value{}; + jv[sfRawTransactions.jsonName][1U][jss::RawTransaction] + [jss::TransactionType] = jss::Payment; + jv[sfRawTransactions.jsonName][1U][jss::RawTransaction] + [sfAccount.jsonName] = alice.human(); + jv[sfRawTransactions.jsonName][1U][jss::RawTransaction] + [sfDestination.jsonName] = bob.human(); + jv[sfRawTransactions.jsonName][1U][jss::RawTransaction] + [sfAmount.jsonName] = "1000000"; + jv[sfRawTransactions.jsonName][1U][jss::RawTransaction][sfFee.jsonName] = + to_string(feeDrops); + jv[sfRawTransactions.jsonName][1U][jss::RawTransaction][jss::Sequence] = + seq + 2; + jv[sfRawTransactions.jsonName][1U][jss::RawTransaction] + [jss::SigningPubKey] = strHex(alice.pk()); + + env(jv, fee(feeDrops * 2), ter(tesSUCCESS)); + env.close(); + + std::array testCases = {{ + {"tesSUCCESS", "Payment"}, + {"tesSUCCESS", "Payment"}, + }}; + + Json::Value params; + params[jss::transaction] = env.tx()->getJson(JsonOptions::none)[jss::hash]; + auto const jrr = env.rpc("json", "tx", to_string(params)); + std::cout << "jrr: " << jrr << "\n"; + auto const meta = jrr[jss::result][jss::meta]; + validateBatchTxns(meta, testCases); + + BEAST_EXPECT(env.seq(alice) == 4); + BEAST_EXPECT(env.balance(alice) == XRP(1000) - XRP(2) - (feeDrops * 2)); + BEAST_EXPECT(env.balance(bob) == XRP(1000) + XRP(2)); + } + + void + testSingle(FeatureBitset features) + { + testcase("batch single"); + + using namespace test::jtx; + using namespace std::literals; + + test::jtx::Env env{*this, envconfig()}; + // Env env{ + // *this, + // envconfig(), + // features, + // nullptr, + // // beast::severities::kWarning + // beast::severities::kTrace}; + + auto const feeDrops = env.current()->fees().base; + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + env.fund(XRP(1000), alice, bob, carol); + env.close(); + + auto const seq = env.seq("alice"); + // ttBATCH + Json::Value jv; + jv[jss::TransactionType] = jss::Batch; + jv[jss::Account] = alice.human(); + jv[jss::Sequence] = seq; + + // Batch Transactions + jv[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue}; + + // Tx 1 + jv[sfRawTransactions.jsonName][0U] = Json::Value{}; + jv[sfRawTransactions.jsonName][0U][jss::RawTransaction] = Json::Value{}; + jv[sfRawTransactions.jsonName][0U][jss::RawTransaction] + [jss::TransactionType] = jss::Payment; + jv[sfRawTransactions.jsonName][0U][jss::RawTransaction] + [sfAccount.jsonName] = alice.human(); + jv[sfRawTransactions.jsonName][0U][jss::RawTransaction] + [sfDestination.jsonName] = bob.human(); + jv[sfRawTransactions.jsonName][0U][jss::RawTransaction] + [sfAmount.jsonName] = "1000000"; + jv[sfRawTransactions.jsonName][0U][jss::RawTransaction][sfFee.jsonName] = + to_string(feeDrops); + jv[sfRawTransactions.jsonName][0U][jss::RawTransaction][jss::Sequence] = + seq + 0; + jv[sfRawTransactions.jsonName][0U][jss::RawTransaction] + [jss::SigningPubKey] = strHex(alice.pk()); + + // Tx 2 + jv[sfRawTransactions.jsonName][1U] = Json::Value{}; + jv[sfRawTransactions.jsonName][1U][jss::RawTransaction] = Json::Value{}; + jv[sfRawTransactions.jsonName][1U][jss::RawTransaction] + [jss::TransactionType] = jss::Payment; + jv[sfRawTransactions.jsonName][1U][jss::RawTransaction] + [sfAccount.jsonName] = alice.human(); + jv[sfRawTransactions.jsonName][1U][jss::RawTransaction] + [sfDestination.jsonName] = bob.human(); + jv[sfRawTransactions.jsonName][1U][jss::RawTransaction] + [sfAmount.jsonName] = "999900000"; + jv[sfRawTransactions.jsonName][1U][jss::RawTransaction][sfFee.jsonName] = + to_string(feeDrops); + jv[sfRawTransactions.jsonName][1U][jss::RawTransaction][jss::Sequence] = + seq + 1; + jv[sfRawTransactions.jsonName][1U][jss::RawTransaction] + [jss::SigningPubKey] = strHex(alice.pk()); + + env(jv, fee((feeDrops * 2)), ter(tesSUCCESS)); + env.close(); + + std::array testCases = {{ + {"tesSUCCESS", "Payment"}, + {"tecUNFUNDED_PAYMENT", "Payment"}, + }}; + + Json::Value params; + params[jss::transaction] = env.tx()->getJson(JsonOptions::none)[jss::hash]; + auto const jrr = env.rpc("json", "tx", to_string(params)); + std::cout << "jrr: " << jrr << "\n"; + auto const meta = jrr[jss::result][jss::meta]; + validateBatchTxns(meta, testCases); + + BEAST_EXPECT(env.seq(alice) == 4); + BEAST_EXPECT(env.balance(alice) == XRP(1000) - XRP(1) - (feeDrops * 2)); + BEAST_EXPECT(env.balance(bob) == XRP(1000) + XRP(1)); + } void testInvalidBatch(FeatureBitset features) @@ -81,7 +304,7 @@ class Batch_test : public beast::unit_test::suite using namespace test::jtx; using namespace std::literals; - test::jtx::Env env{*this, network::makeNetworkConfig(21337)}; + test::jtx::Env env{*this}; auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -89,9 +312,8 @@ class Batch_test : public beast::unit_test::suite env.fund(XRP(1000), alice, bob, carol); env.close(); - auto const seq = env.seq("alice"); Json::Value jv; - jv[jss::TransactionType] = jss::Invoke; + jv[jss::TransactionType] = jss::AccountSet; jv[jss::Account] = alice.human(); jv[jss::Destination] = bob.human(); jv[sfFee.jsonName] = "10"; @@ -103,7 +325,9 @@ class Batch_test : public beast::unit_test::suite void testWithFeats(FeatureBitset features) { - testBatch(features); + // testBatch(features); + testSuccess(features); + // testSingle(features); // testInvalidBatch(features); } diff --git a/src/test/app/TxQ_test.cpp b/src/test/app/TxQ_test.cpp index 8628d2f02..72f62b80d 100644 --- a/src/test/app/TxQ_test.cpp +++ b/src/test/app/TxQ_test.cpp @@ -5035,11 +5035,75 @@ public: checkMetrics(__LINE__, env, 0, 10, 2, 5, 256); } + void + testEmittedTxns(FeatureBitset features) + { + testcase("emitted txns"); + using namespace jtx; + + Env env( + *this, + envconfig(), + features); + + auto const alice = Account("alice"); + + auto const queued = ter(terQUEUED); + + BEAST_EXPECT(env.current()->fees().base == 10); + + checkMetrics(__LINE__, env, 0, std::nullopt, 0, 3, 256); + + // Create account + env.fund(XRP(50000), noripple(alice)); + checkMetrics(__LINE__, env, 0, std::nullopt, 1, 3, 256); + + fillQueue(env, alice); + checkMetrics(__LINE__, env, 0, std::nullopt, 4, 3, 256); + + // Queue a transaction + auto const aliceSeq = env.seq(alice); + env(noop(alice), queued); + checkMetrics(__LINE__, env, 1, std::nullopt, 4, 3, 256); + + // Now, apply a (different) transaction directly + // to the open ledger, bypassing the queue + // (This requires calling directly into the open ledger, + // which won't work if unit tests are separated to only + // be callable via RPC.) + env.app().openLedger().modify([&](OpenView& view, beast::Journal j) { + auto const tx = + env.jt(noop(alice), seq(aliceSeq), openLedgerFee(env)); + auto const result = + ripple::apply(env.app(), view, *tx.stx, tapUNLIMITED, j); + BEAST_EXPECT(result.first == tesSUCCESS && result.second); + return result.second; + }); + // the queued transaction is still there + checkMetrics(__LINE__, env, 1, std::nullopt, 5, 3, 256); + + // The next transaction should be able to go into the open + // ledger, even though aliceSeq is queued. In earlier incarnations + // of the TxQ this would cause an assert. + env(noop(alice), seq(aliceSeq + 1), openLedgerFee(env)); + checkMetrics(__LINE__, env, 1, std::nullopt, 6, 3, 256); + // Now queue a couple more transactions to make sure + // they succeed despite aliceSeq being queued + env(noop(alice), seq(aliceSeq + 2), queued); + env(noop(alice), seq(aliceSeq + 3), queued); + checkMetrics(__LINE__, env, 3, std::nullopt, 6, 3, 256); + + // Now close the ledger. One of the queued transactions + // (aliceSeq) should be dropped. + env.close(); + checkMetrics(__LINE__, env, 0, 12, 2, 6, 256); + } + void run() override { using namespace test::jtx; - FeatureBitset const all{supported_amendments() - featureXahauGenesis}; + FeatureBitset const all{supported_amendments()}; testQueueSeq(all); testQueueTicket(all); testTecResult(all); @@ -5063,7 +5127,7 @@ public: run2() { using namespace test::jtx; - FeatureBitset const all{supported_amendments() - featureXahauGenesis}; + FeatureBitset const all{supported_amendments()}; testAcctInQueueButEmpty(all); testRPC(all); testExpirationReplacement(all);