mirror of
https://github.com/Xahau/xahaud.git
synced 2026-04-25 21:47:47 +00:00
Compare commits
14 Commits
fix-warnin
...
ttBATCH
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
86fbddaa2e | ||
|
|
d67a93dcf7 | ||
|
|
d596ceca4a | ||
|
|
7a4e2500bb | ||
|
|
175f436974 | ||
|
|
8a8822cb57 | ||
|
|
43fce18099 | ||
|
|
aa438663e8 | ||
|
|
cbe8c6869c | ||
|
|
a1da3ab477 | ||
|
|
1a3376e453 | ||
|
|
a8ff49739d | ||
|
|
e326e11a6f | ||
|
|
e26d4164a9 |
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@@ -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,
|
||||
|
||||
@@ -428,6 +428,7 @@ target_sources (rippled PRIVATE
|
||||
src/ripple/app/rdb/impl/Vacuum.cpp
|
||||
src/ripple/app/rdb/impl/Wallet.cpp
|
||||
src/ripple/app/tx/impl/ApplyContext.cpp
|
||||
src/ripple/app/tx/impl/Batch.cpp
|
||||
src/ripple/app/tx/impl/BookTip.cpp
|
||||
src/ripple/app/tx/impl/CancelCheck.cpp
|
||||
src/ripple/app/tx/impl/CancelOffer.cpp
|
||||
@@ -704,6 +705,7 @@ if (tests)
|
||||
src/test/app/AccountDelete_test.cpp
|
||||
src/test/app/AccountTxPaging_test.cpp
|
||||
src/test/app/AmendmentTable_test.cpp
|
||||
src/test/app/Batch_test.cpp
|
||||
src/test/app/BaseFee_test.cpp
|
||||
src/test/app/Check_test.cpp
|
||||
src/test/app/ClaimReward_test.cpp
|
||||
|
||||
@@ -22,6 +22,8 @@ struct HookContext;
|
||||
struct HookResult;
|
||||
bool
|
||||
isEmittedTxn(ripple::STTx const& tx);
|
||||
bool
|
||||
isBatchTxn(ripple::STTx const& tx);
|
||||
|
||||
// This map type acts as both a read and write cache for hook execution
|
||||
// and is preserved across the execution of the set of hook chains
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
#include <ripple/app/hook/applyHook.h>
|
||||
#include <ripple/app/ledger/OpenLedger.h>
|
||||
#include <ripple/app/ledger/TransactionMaster.h>
|
||||
#include <ripple/app/misc/HashRouter.h>
|
||||
#include <ripple/app/misc/NetworkOPs.h>
|
||||
#include <ripple/app/misc/Transaction.h>
|
||||
#include <ripple/app/misc/TxQ.h>
|
||||
#include <ripple/app/tx/impl/Import.h>
|
||||
#include <ripple/app/tx/impl/details/NFTokenUtils.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/basics/Slice.h>
|
||||
#include <ripple/protocol/ErrorCodes.h>
|
||||
#include <ripple/protocol/TxFlags.h>
|
||||
#include <ripple/protocol/st.h>
|
||||
#include <ripple/protocol/tokens.h>
|
||||
#include <boost/multiprecision/cpp_dec_float.hpp>
|
||||
#include <any>
|
||||
@@ -891,6 +896,12 @@ hook::isEmittedTxn(ripple::STTx const& tx)
|
||||
return tx.isFieldPresent(ripple::sfEmitDetails);
|
||||
}
|
||||
|
||||
bool
|
||||
hook::isBatchTxn(ripple::STTx const& tx)
|
||||
{
|
||||
return tx.isFieldPresent(ripple::sfBatchTxn);
|
||||
}
|
||||
|
||||
int64_t
|
||||
hook::computeExecutionFee(uint64_t instructionCount)
|
||||
{
|
||||
|
||||
@@ -59,6 +59,7 @@ buildLedgerImpl(
|
||||
OpenView accum(&*built);
|
||||
assert(!accum.open());
|
||||
applyTxs(accum, built);
|
||||
std::cout << "BuildLedger::buildLedgerImpl: " << "\n";
|
||||
accum.apply(*built);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -120,7 +120,10 @@ OpenLedger::accept(
|
||||
f(*next, j_);
|
||||
// Apply local tx
|
||||
for (auto const& item : locals)
|
||||
{
|
||||
JLOG(j_.trace()) << "OpenLedger::accept: getTxQ" << "\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)
|
||||
@@ -131,6 +134,10 @@ OpenLedger::accept(
|
||||
// skip emitted txns
|
||||
if (tx->isFieldPresent(sfEmitDetails))
|
||||
continue;
|
||||
|
||||
// // skip batch txns
|
||||
// if (tx->isFieldPresent(sfBatchTxn))
|
||||
// continue;
|
||||
|
||||
if (auto const toSkip = app.getHashRouter().shouldRelay(txId))
|
||||
{
|
||||
|
||||
@@ -1218,6 +1218,21 @@ NetworkOPsImp::processTransaction(
|
||||
return;
|
||||
}
|
||||
|
||||
// // This function is called by several different parts of the codebase
|
||||
// // under no circumstances will we ever accept an emitted txn from the
|
||||
// // network. Emitted txns are *always* and *only* inserted by TxQ::accept,
|
||||
// // and only arise from processing ltEMITTED_TXN out of the EMITTED_DIR. This
|
||||
// // isn't always an error because a fetch pack etc might include an emitted
|
||||
// // txn and if this is a deliberate attempt to send an emitted txn over the
|
||||
// // network it was already billed in PeerImp
|
||||
// if (view->rules().enabled(featureBatch) &&
|
||||
// hook::isBatchTxn(*transaction->getSTransaction()))
|
||||
// {
|
||||
// // RH NOTE: cannot set SF_BAD because if the tx will be generated by a
|
||||
// // hook we are about to execute
|
||||
// return;
|
||||
// }
|
||||
|
||||
auto const newFlags = app_.getHashRouter().getFlags(transaction->getID());
|
||||
|
||||
if ((newFlags & SF_BAD) != 0)
|
||||
@@ -1280,6 +1295,18 @@ NetworkOPsImp::doTransactionAsync(
|
||||
return;
|
||||
}
|
||||
|
||||
// // Enforce Network bar for batch txn
|
||||
// if (view->rules().enabled(featureBatch) &&
|
||||
// hook::isBatchTxn(*transaction->getSTransaction()))
|
||||
// {
|
||||
// JLOG(m_journal.info())
|
||||
// << "Transaction received over network has BatchTxn, discarding.";
|
||||
// // RH NOTE: cannot set SF_BAD because if the tx will be generated by a
|
||||
// // hook we are about to execute then this would poison consensus for
|
||||
// // that emitted tx
|
||||
// return;
|
||||
// }
|
||||
|
||||
if (transaction->getApplying())
|
||||
return;
|
||||
|
||||
@@ -1318,6 +1345,15 @@ NetworkOPsImp::doTransactionSync(
|
||||
// that emitted tx
|
||||
return;
|
||||
}
|
||||
|
||||
// // Enforce Network bar for batch txn
|
||||
// if (view->rules().enabled(featureBatch) &&
|
||||
// hook::isBatchTxn(*transaction->getSTransaction()))
|
||||
// {
|
||||
// JLOG(m_journal.info())
|
||||
// << "Transaction received over network has BatchTxn, discarding.";
|
||||
// return;
|
||||
// }
|
||||
|
||||
if (!transaction->getApplying())
|
||||
{
|
||||
@@ -1397,6 +1433,7 @@ NetworkOPsImp::apply(std::unique_lock<std::mutex>& batchLock)
|
||||
if (e.failType == FailHard::yes)
|
||||
flags |= tapFAIL_HARD;
|
||||
|
||||
std::cout << "NetworkOPsImp::apply: getTxQ" << "\n";
|
||||
auto const result = app_.getTxQ().apply(
|
||||
app_, view, e.transaction->getSTransaction(), flags, j);
|
||||
e.result = result.first;
|
||||
@@ -1523,7 +1560,10 @@ NetworkOPsImp::apply(std::unique_lock<std::mutex>& batchLock)
|
||||
|
||||
bool const isEmitted =
|
||||
hook::isEmittedTxn(*(e.transaction->getSTransaction()));
|
||||
// bool const isBatch =
|
||||
// hook::isBatchTxn(*(e.transaction->getSTransaction()));
|
||||
|
||||
// if (toSkip && !isEmitted || !isBatch)
|
||||
if (toSkip && !isEmitted)
|
||||
{
|
||||
protocol::TMTransaction tx;
|
||||
@@ -2753,6 +2793,10 @@ NetworkOPsImp::pubProposedTransaction(
|
||||
if (hook::isEmittedTxn(*transaction))
|
||||
return;
|
||||
|
||||
// never publish emitted txns
|
||||
if (hook::isBatchTxn(*transaction))
|
||||
return;
|
||||
|
||||
Json::Value jvObj = transJson(*transaction, result, false, ledger);
|
||||
|
||||
{
|
||||
|
||||
@@ -865,6 +865,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<TxQAccount::TxMap::iterator> {
|
||||
if (accountIsInQueue)
|
||||
@@ -886,6 +889,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
|
||||
@@ -1882,8 +1886,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 =
|
||||
@@ -1897,6 +1907,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.
|
||||
|
||||
@@ -56,6 +56,20 @@ ApplyContext::discard()
|
||||
void
|
||||
ApplyContext::apply(TER ter)
|
||||
{
|
||||
std::cout << "ApplyContext::apply: " << ter << "\n";
|
||||
std::cout << "ApplyContext::apply: " << (flags_ & tapPREFLIGHT_BATCH) << "\n";
|
||||
// std::cout << "tx: " << tx.getTransactionID() << "\n";
|
||||
// if (flags_ == tapPREFLIGHT_BATCH)
|
||||
// return;
|
||||
// if (ter == tecBATCH_FAILURE || flags_ == tapPREFLIGHT_BATCH)
|
||||
// {
|
||||
// std::cout << "ApplyContext::apply: " << "FAILURE" << "\n";
|
||||
// return;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// view_->apply(base_, tx, ter, journal);
|
||||
// }
|
||||
view_->apply(base_, tx, ter, journal);
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ public:
|
||||
TER const preclaimResult;
|
||||
XRPAmount const baseFee;
|
||||
beast::Journal const journal;
|
||||
OpenView& base_;
|
||||
|
||||
ApplyView&
|
||||
view()
|
||||
@@ -122,6 +123,12 @@ public:
|
||||
return tx.isFieldPresent(sfEmitDetails);
|
||||
}
|
||||
|
||||
bool
|
||||
isBatchTxn()
|
||||
{
|
||||
return tx.isFieldPresent(sfBatchTxn);
|
||||
}
|
||||
|
||||
ApplyFlags const&
|
||||
flags()
|
||||
{
|
||||
@@ -139,7 +146,7 @@ private:
|
||||
XRPAmount const fee,
|
||||
std::index_sequence<Is...>);
|
||||
|
||||
OpenView& base_;
|
||||
// OpenView& base_;
|
||||
ApplyFlags flags_;
|
||||
std::optional<ApplyViewImpl> view_;
|
||||
};
|
||||
|
||||
565
src/ripple/app/tx/impl/Batch.cpp
Normal file
565
src/ripple/app/tx/impl/Batch.cpp
Normal file
@@ -0,0 +1,565 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <ripple/app/main/Application.h>
|
||||
#include <ripple/app/tx/applySteps.h>
|
||||
#include <ripple/app/tx/impl/ApplyContext.h>
|
||||
#include <ripple/app/tx/impl/Batch.h>
|
||||
#include <ripple/app/tx/impl/CancelCheck.h>
|
||||
#include <ripple/app/tx/impl/CancelOffer.h>
|
||||
#include <ripple/app/tx/impl/CashCheck.h>
|
||||
#include <ripple/app/tx/impl/Change.h>
|
||||
#include <ripple/app/tx/impl/ClaimReward.h>
|
||||
#include <ripple/app/tx/impl/CreateCheck.h>
|
||||
#include <ripple/app/tx/impl/CreateOffer.h>
|
||||
#include <ripple/app/tx/impl/CreateTicket.h>
|
||||
#include <ripple/app/tx/impl/DeleteAccount.h>
|
||||
#include <ripple/app/tx/impl/DepositPreauth.h>
|
||||
#include <ripple/app/tx/impl/Escrow.h>
|
||||
#include <ripple/app/tx/impl/GenesisMint.h>
|
||||
#include <ripple/app/tx/impl/Import.h>
|
||||
#include <ripple/app/tx/impl/Invoke.h>
|
||||
#include <ripple/app/tx/impl/NFTokenAcceptOffer.h>
|
||||
#include <ripple/app/tx/impl/NFTokenBurn.h>
|
||||
#include <ripple/app/tx/impl/NFTokenCancelOffer.h>
|
||||
#include <ripple/app/tx/impl/NFTokenCreateOffer.h>
|
||||
#include <ripple/app/tx/impl/NFTokenMint.h>
|
||||
#include <ripple/app/tx/impl/PayChan.h>
|
||||
#include <ripple/app/tx/impl/Payment.h>
|
||||
#include <ripple/app/tx/impl/SetAccount.h>
|
||||
#include <ripple/app/tx/impl/SetHook.h>
|
||||
#include <ripple/app/tx/impl/SetRegularKey.h>
|
||||
#include <ripple/app/tx/impl/SetSignerList.h>
|
||||
#include <ripple/app/tx/impl/SetTrust.h>
|
||||
#include <ripple/app/tx/impl/URIToken.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/ledger/View.h>
|
||||
#include <ripple/protocol/Feature.h>
|
||||
#include <ripple/protocol/Indexes.h>
|
||||
#include <ripple/protocol/TER.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
namespace {
|
||||
|
||||
struct UnknownTxnType : std::exception
|
||||
{
|
||||
TxType txnType;
|
||||
UnknownTxnType(TxType t) : txnType{t}
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
// Call a lambda with the concrete transaction type as a template parameter
|
||||
// throw an "UnknownTxnType" exception on error
|
||||
template <class F>
|
||||
auto
|
||||
with_txn_type(TxType txnType, F&& f)
|
||||
{
|
||||
switch (txnType)
|
||||
{
|
||||
case ttACCOUNT_DELETE:
|
||||
return f.template operator()<DeleteAccount>();
|
||||
case ttACCOUNT_SET:
|
||||
return f.template operator()<SetAccount>();
|
||||
case ttCHECK_CANCEL:
|
||||
return f.template operator()<CancelCheck>();
|
||||
case ttCHECK_CASH:
|
||||
return f.template operator()<CashCheck>();
|
||||
case ttCHECK_CREATE:
|
||||
return f.template operator()<CreateCheck>();
|
||||
case ttDEPOSIT_PREAUTH:
|
||||
return f.template operator()<DepositPreauth>();
|
||||
case ttOFFER_CANCEL:
|
||||
return f.template operator()<CancelOffer>();
|
||||
case ttOFFER_CREATE:
|
||||
return f.template operator()<CreateOffer>();
|
||||
case ttESCROW_CREATE:
|
||||
return f.template operator()<EscrowCreate>();
|
||||
case ttESCROW_FINISH:
|
||||
return f.template operator()<EscrowFinish>();
|
||||
case ttESCROW_CANCEL:
|
||||
return f.template operator()<EscrowCancel>();
|
||||
case ttPAYCHAN_CLAIM:
|
||||
return f.template operator()<PayChanClaim>();
|
||||
case ttPAYCHAN_CREATE:
|
||||
return f.template operator()<PayChanCreate>();
|
||||
case ttPAYCHAN_FUND:
|
||||
return f.template operator()<PayChanFund>();
|
||||
case ttPAYMENT:
|
||||
return f.template operator()<Payment>();
|
||||
case ttREGULAR_KEY_SET:
|
||||
return f.template operator()<SetRegularKey>();
|
||||
case ttSIGNER_LIST_SET:
|
||||
return f.template operator()<SetSignerList>();
|
||||
case ttTICKET_CREATE:
|
||||
return f.template operator()<CreateTicket>();
|
||||
case ttTRUST_SET:
|
||||
return f.template operator()<SetTrust>();
|
||||
case ttAMENDMENT:
|
||||
case ttFEE:
|
||||
case ttUNL_MODIFY:
|
||||
case ttUNL_REPORT:
|
||||
case ttEMIT_FAILURE:
|
||||
return f.template operator()<Change>();
|
||||
case ttHOOK_SET:
|
||||
return f.template operator()<SetHook>();
|
||||
case ttNFTOKEN_MINT:
|
||||
return f.template operator()<NFTokenMint>();
|
||||
case ttNFTOKEN_BURN:
|
||||
return f.template operator()<NFTokenBurn>();
|
||||
case ttNFTOKEN_CREATE_OFFER:
|
||||
return f.template operator()<NFTokenCreateOffer>();
|
||||
case ttNFTOKEN_CANCEL_OFFER:
|
||||
return f.template operator()<NFTokenCancelOffer>();
|
||||
case ttNFTOKEN_ACCEPT_OFFER:
|
||||
return f.template operator()<NFTokenAcceptOffer>();
|
||||
case ttCLAIM_REWARD:
|
||||
return f.template operator()<ClaimReward>();
|
||||
case ttGENESIS_MINT:
|
||||
return f.template operator()<GenesisMint>();
|
||||
case ttIMPORT:
|
||||
return f.template operator()<Import>();
|
||||
case ttINVOKE:
|
||||
return f.template operator()<Invoke>();
|
||||
case ttURITOKEN_MINT:
|
||||
case ttURITOKEN_BURN:
|
||||
case ttURITOKEN_BUY:
|
||||
case ttURITOKEN_CREATE_SELL_OFFER:
|
||||
case ttURITOKEN_CANCEL_SELL_OFFER:
|
||||
return f.template operator()<URIToken>();
|
||||
default:
|
||||
throw UnknownTxnType(txnType);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// clang-format off
|
||||
// Current formatter for rippled is based on clang-10, which does not handle `requires` clauses
|
||||
template <class T>
|
||||
requires(T::ConsequencesFactory == Transactor::Normal)
|
||||
TxConsequences
|
||||
consequences_helper(PreflightContext const& ctx)
|
||||
{
|
||||
return TxConsequences(ctx.tx);
|
||||
};
|
||||
|
||||
// For Transactor::Blocker
|
||||
template <class T>
|
||||
requires(T::ConsequencesFactory == Transactor::Blocker)
|
||||
TxConsequences
|
||||
consequences_helper(PreflightContext const& ctx)
|
||||
{
|
||||
return TxConsequences(ctx.tx, TxConsequences::blocker);
|
||||
};
|
||||
|
||||
// For Transactor::Custom
|
||||
template <class T>
|
||||
requires(T::ConsequencesFactory == Transactor::Custom)
|
||||
TxConsequences
|
||||
consequences_helper(PreflightContext const& ctx)
|
||||
{
|
||||
return T::makeTxConsequences(ctx);
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
static std::pair<NotTEC, TxConsequences>
|
||||
invoke_preflight(PreflightContext const& ctx)
|
||||
{
|
||||
try
|
||||
{
|
||||
return with_txn_type(ctx.tx.getTxnType(), [&]<typename T>() {
|
||||
auto const tec = T::preflight(ctx);
|
||||
return std::make_pair(
|
||||
tec,
|
||||
isTesSuccess(tec) ? consequences_helper<T>(ctx)
|
||||
: TxConsequences{tec});
|
||||
});
|
||||
}
|
||||
catch (UnknownTxnType const& e)
|
||||
{
|
||||
// Should never happen
|
||||
JLOG(ctx.j.fatal())
|
||||
<< "Unknown transaction type in preflight: " << e.txnType;
|
||||
assert(false);
|
||||
return {temUNKNOWN, TxConsequences{temUNKNOWN}};
|
||||
}
|
||||
}
|
||||
|
||||
static TER
|
||||
invoke_preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
try
|
||||
{
|
||||
// use name hiding to accomplish compile-time polymorphism of static
|
||||
// class functions for Transactor and derived classes.
|
||||
return with_txn_type(ctx.tx.getTxnType(), [&]<typename T>() {
|
||||
// If the transactor requires a valid account and the transaction
|
||||
// doesn't list one, preflight will have already a flagged a
|
||||
// failure.
|
||||
auto const id = ctx.tx.getAccountID(sfAccount);
|
||||
|
||||
if (id != beast::zero)
|
||||
{
|
||||
// TER result = T::checkSeqProxy(ctx.view, ctx.tx, ctx.j);
|
||||
|
||||
// if (result != tesSUCCESS)
|
||||
// return result;
|
||||
|
||||
TER result = tesSUCCESS;
|
||||
|
||||
result = T::checkPriorTxAndLastLedger(ctx);
|
||||
|
||||
if (result != tesSUCCESS)
|
||||
return result;
|
||||
|
||||
// result = T::checkFee(ctx, calculateBaseFee(ctx.view, ctx.tx));
|
||||
|
||||
// if (result != tesSUCCESS)
|
||||
// return result;
|
||||
|
||||
result = T::checkSign(ctx);
|
||||
|
||||
if (result != tesSUCCESS)
|
||||
return result;
|
||||
}
|
||||
|
||||
return T::preclaim(ctx);
|
||||
});
|
||||
}
|
||||
catch (UnknownTxnType const& e)
|
||||
{
|
||||
// Should never happen
|
||||
JLOG(ctx.j.fatal())
|
||||
<< "Unknown transaction type in preclaim: " << e.txnType;
|
||||
assert(false);
|
||||
return temUNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
static std::pair<TER, bool>
|
||||
invoke_apply(ApplyContext& ctx)
|
||||
{
|
||||
try
|
||||
{
|
||||
return with_txn_type(ctx.tx.getTxnType(), [&]<typename T>() {
|
||||
T p(ctx);
|
||||
return p();
|
||||
});
|
||||
}
|
||||
catch (UnknownTxnType const& e)
|
||||
{
|
||||
// Should never happen
|
||||
JLOG(ctx.journal.fatal())
|
||||
<< "Unknown transaction type in apply: " << e.txnType;
|
||||
assert(false);
|
||||
return {temUNKNOWN, false};
|
||||
}
|
||||
}
|
||||
|
||||
TxConsequences
|
||||
Batch::makeTxConsequences(PreflightContext const& ctx)
|
||||
{
|
||||
return TxConsequences{ctx.tx, TxConsequences::normal};
|
||||
}
|
||||
|
||||
std::vector<NotTEC> preflightResponses;
|
||||
|
||||
NotTEC
|
||||
Batch::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
auto& tx = ctx.tx;
|
||||
|
||||
auto const& txns = tx.getFieldArray(sfRawTransactions);
|
||||
if (txns.empty())
|
||||
{
|
||||
JLOG(ctx.j.error()) << "Batch: txns array empty.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
if (txns.size() > 8)
|
||||
{
|
||||
JLOG(ctx.j.error()) << "Batch: txns array exceeds 12 entries.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
for (auto const& txn : txns)
|
||||
{
|
||||
if (!txn.isFieldPresent(sfTransactionType))
|
||||
{
|
||||
JLOG(ctx.j.error())
|
||||
<< "Batch: TransactionType missing in array entry.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
auto const tt = txn.getFieldU16(sfTransactionType);
|
||||
auto const txtype = safe_cast<TxType>(tt);
|
||||
auto const account = txn.getAccountID(sfAccount);
|
||||
auto const stx =
|
||||
STTx(txtype, [&txn](STObject& obj) { obj = std::move(txn); });
|
||||
PreflightContext const pfctx(
|
||||
ctx.app,
|
||||
stx,
|
||||
ctx.rules,
|
||||
tapPREFLIGHT_BATCH,
|
||||
ctx.j);
|
||||
auto const response = invoke_preflight(pfctx);
|
||||
preflightResponses.push_back(response.first);
|
||||
}
|
||||
|
||||
return preflight2(ctx);
|
||||
}
|
||||
|
||||
std::vector<TER> preclaimResponses;
|
||||
|
||||
TER
|
||||
Batch::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
if (!ctx.view.rules().enabled(featureBatch))
|
||||
return temDISABLED;
|
||||
|
||||
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.error()) << "Batch: Failed Preflight Response: " << preflightResponses[i];
|
||||
preclaimResponses.push_back(TER(preflightResponses[i]));
|
||||
continue;
|
||||
}
|
||||
|
||||
auto const& txn = txns[i];
|
||||
if (!txn.isFieldPresent(sfTransactionType))
|
||||
{
|
||||
JLOG(ctx.j.error())
|
||||
<< "Batch: TransactionType missing in array entry.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
auto const tt = txn.getFieldU16(sfTransactionType);
|
||||
auto const txtype = safe_cast<TxType>(tt);
|
||||
auto const stx =
|
||||
STTx(txtype, [&txn](STObject& obj) { obj = std::move(txn); });
|
||||
PreclaimContext const pcctx(
|
||||
ctx.app, ctx.view, preflightResponses[i], stx, ctx.flags, ctx.j);
|
||||
auto const response = invoke_preclaim(pcctx);
|
||||
preclaimResponses.push_back(response);
|
||||
}
|
||||
|
||||
for (auto const& response : preclaimResponses)
|
||||
{
|
||||
if (response != tesSUCCESS)
|
||||
{
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
// void
|
||||
// Batch::updateAccount(Sandbox& sb)
|
||||
// {
|
||||
// auto const sle = ctx_.base_.read(keylet::account(account_));
|
||||
// if (!sle)
|
||||
// return tefINTERNAL;
|
||||
|
||||
// auto const sleSrcAcc = sb.peek(keylet::account(account_));
|
||||
// if (!sleSrcAcc)
|
||||
// return tefINTERNAL;
|
||||
|
||||
// auto const feePaid = ctx_.tx[sfFee].xrp();
|
||||
// sleSrcAcc->setFieldAmount(sfBalance, sle->getFieldAmount(sfBalance).xrp() - feePaid);
|
||||
// sb.update(sleSrcAcc);
|
||||
// sb.apply(ctx_.rawView());
|
||||
// }
|
||||
|
||||
TER
|
||||
Batch::doApply()
|
||||
{
|
||||
std::cout << "Batch::doApply()" << "\n";
|
||||
Sandbox sb(&ctx_.view());
|
||||
|
||||
uint32_t flags = ctx_.tx.getFlags();
|
||||
if (flags & tfBatchMask)
|
||||
return temINVALID_FLAG;
|
||||
|
||||
// SANITIZE
|
||||
std::vector<STTx> stxTxns;
|
||||
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(sfTransactionType))
|
||||
{
|
||||
JLOG(ctx_.journal.error())
|
||||
<< "Batch: TransactionType missing in array entry.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
auto const tt = txn.getFieldU16(sfTransactionType);
|
||||
auto const txtype = safe_cast<TxType>(tt);
|
||||
auto const stx = STTx(txtype, [&txn](STObject& obj) { obj = std::move(txn); });
|
||||
stxTxns.push_back(stx);
|
||||
}
|
||||
|
||||
// DRY RUN
|
||||
// std::cout << "DRYRUN::size(): " << stxTxns.size() << "\n";
|
||||
// std::vector<std::pair<std::uint16_t, TER>> dryVector;
|
||||
// for (std::size_t i = 0; i < stxTxns.size(); ++i)
|
||||
// {
|
||||
// auto const& stx = stxTxns[i];
|
||||
// ApplyContext actx(
|
||||
// ctx_.app,
|
||||
// ctx_.base_,
|
||||
// stx,
|
||||
// preclaimResponses[i],
|
||||
// ctx_.view().fees().base,
|
||||
// tapPREFLIGHT_BATCH,
|
||||
// ctx_.journal);
|
||||
// auto const result = invoke_apply(actx);
|
||||
// dryVector.emplace_back(stx.getTxnType(), result.first);
|
||||
// std::cout << "ApplyContext::size(): " << actx.size() << "\n";
|
||||
// actx.discard();
|
||||
// }
|
||||
|
||||
ApplyViewImpl& avi = dynamic_cast<ApplyViewImpl&>(ctx_.view());
|
||||
// for (auto const& dryRun : dryVector)
|
||||
// {
|
||||
// STObject meta{sfBatchExecution};
|
||||
// meta.setFieldU8(sfTransactionResult, TERtoInt(dryRun.second));
|
||||
// meta.setFieldU16(sfTransactionType, dryRun.first);
|
||||
// avi.addBatchExecutionMetaData(std::move(meta));
|
||||
|
||||
// // tfBatchAtomic
|
||||
// if (dryRun.second != tesSUCCESS && flags & tfBatchAtomic)
|
||||
// {
|
||||
// std::cout << "tfBatchAtomic::Failed" << "\n";
|
||||
// sb.apply(ctx_.rawView());
|
||||
// return tecBATCH_FAILURE;
|
||||
// }
|
||||
// }
|
||||
|
||||
// ctx_.discard();
|
||||
|
||||
// // reset avi
|
||||
// std::vector<STObject> executions;
|
||||
// std::vector<STObject> emissions;
|
||||
// std::vector<STObject> batch;
|
||||
// avi.setHookMetaData(std::move(executions), std::move(emissions), std::move(batch));
|
||||
|
||||
// WET RUN
|
||||
TER result = tesSUCCESS;
|
||||
for (std::size_t i = 0; i < stxTxns.size(); ++i)
|
||||
{
|
||||
STObject meta{sfBatchExecution};
|
||||
|
||||
auto const& stx = stxTxns[i];
|
||||
ApplyContext actx(
|
||||
ctx_.app,
|
||||
ctx_.base_,
|
||||
stx,
|
||||
preclaimResponses[i],
|
||||
ctx_.view().fees().base,
|
||||
view().flags(),
|
||||
ctx_.journal);
|
||||
auto const _result = invoke_apply(actx);
|
||||
|
||||
meta.setFieldU8(sfTransactionResult, TERtoInt(_result.first));
|
||||
meta.setFieldU16(sfTransactionType, stx.getTxnType());
|
||||
meta.setFieldH256(sfTransactionHash, stx.getTransactionID());
|
||||
|
||||
avi.addBatchExecutionMetaData(std::move(meta));
|
||||
|
||||
std::cout << "tfAllOrNothing: " << (flags & tfAllOrNothing) << "\n";
|
||||
std::cout << "tfOnlyOne: " << (flags & tfOnlyOne) << "\n";
|
||||
std::cout << "tfUntilFailure: " << (flags & tfUntilFailure) << "\n";
|
||||
std::cout << "tfIndependent: " << (flags & tfIndependent) << "\n";
|
||||
std::cout << "tfBatchAtomic: " << _result.first << "\n";
|
||||
|
||||
if (_result.first != tesSUCCESS)
|
||||
{
|
||||
if (flags & tfUntilFailure)
|
||||
{
|
||||
actx.discard();
|
||||
result = tecBATCH_FAILURE;
|
||||
break;
|
||||
}
|
||||
if (flags & tfOnlyOne)
|
||||
{
|
||||
actx.discard();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (_result.first == tesSUCCESS && flags & tfOnlyOne)
|
||||
{
|
||||
result = tecBATCH_FAILURE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto const sleBase = ctx_.base_.read(keylet::account(account_));
|
||||
if (!sleBase)
|
||||
return tefINTERNAL;
|
||||
|
||||
auto const sleSrcAcc = sb.peek(keylet::account(account_));
|
||||
if (!sleSrcAcc)
|
||||
return tefINTERNAL;
|
||||
|
||||
// std::cout << "ACCOUNT BASE SEQ: " << sleBase->getFieldU32(sfSequence) << "\n";
|
||||
// std::cout << "ACCOUNT BASE BALANCE: " << sleBase->getFieldAmount(sfBalance) << "\n";
|
||||
// std::cout << "ACCOUNT SEQ: " << sleSrcAcc->getFieldU32(sfSequence) << "\n";
|
||||
// std::cout << "ACCOUNT BALANCE: " << sleSrcAcc->getFieldAmount(sfBalance) << "\n";
|
||||
|
||||
auto const feePaid = ctx_.tx[sfFee].xrp();
|
||||
// auto const& txns = ctx_.tx.getFieldArray(sfRawTransactions);
|
||||
sleSrcAcc->setFieldU32(sfSequence, ctx_.tx.getFieldU32(sfSequence) + txns.size() + 1);
|
||||
sleSrcAcc->setFieldAmount(sfBalance, sleBase->getFieldAmount(sfBalance).xrp() - feePaid);
|
||||
sb.update(sleSrcAcc);
|
||||
sb.apply(ctx_.rawView());
|
||||
return result;
|
||||
}
|
||||
|
||||
XRPAmount
|
||||
Batch::calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
XRPAmount extraFee{0};
|
||||
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<TxType>(tt);
|
||||
auto const stx = STTx(txtype, [&txn](STObject& obj) { obj = std::move(txn); });
|
||||
txFees += Transactor::calculateBaseFee(view, tx);
|
||||
}
|
||||
extraFee += txFees;
|
||||
}
|
||||
return extraFee;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
57
src/ripple/app/tx/impl/Batch.h
Normal file
57
src/ripple/app/tx/impl/Batch.h
Normal file
@@ -0,0 +1,57 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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_TX_BATCH_H_INCLUDED
|
||||
#define RIPPLE_TX_BATCH_H_INCLUDED
|
||||
|
||||
#include <ripple/app/tx/impl/Transactor.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/core/Config.h>
|
||||
#include <ripple/protocol/Indexes.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class Batch : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Custom};
|
||||
|
||||
explicit Batch(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static XRPAmount
|
||||
calculateBaseFee(ReadView const& view, STTx const& tx);
|
||||
|
||||
static TxConsequences
|
||||
makeTxConsequences(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -241,6 +241,33 @@ XRPNotCreated::finalize(
|
||||
return drops_ == drops;
|
||||
}
|
||||
|
||||
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.
|
||||
if (drops_ > 0)
|
||||
|
||||
@@ -39,7 +39,7 @@ Invoke::preflight(PreflightContext const& ctx)
|
||||
|
||||
auto& tx = ctx.tx;
|
||||
|
||||
if (tx.getFieldVL(sfBlob).size() > (128 * 1024))
|
||||
if (tx.isFieldPresent(sfBlob) && tx.getFieldVL(sfBlob).size() > (128 * 1024))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Invoke: blob was more than 128kib "
|
||||
<< tx.getTransactionID();
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -440,7 +440,38 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee)
|
||||
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<TxType>(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);
|
||||
@@ -500,6 +531,7 @@ TER
|
||||
Transactor::payFee()
|
||||
{
|
||||
auto const feePaid = ctx_.tx[sfFee].xrp();
|
||||
std::cout << "feePaid: " << feePaid << "\n";
|
||||
|
||||
auto const sle = view().peek(keylet::account(account_));
|
||||
// RH NOTE: we don't need to check for ttIMPORT here because this function
|
||||
@@ -510,7 +542,9 @@ Transactor::payFee()
|
||||
// Deduct the fee, so it's not available during the transaction.
|
||||
// Will only write the account back if the transaction succeeds.
|
||||
|
||||
std::cout << "mSourceBalance1.1: " << mSourceBalance << "\n";
|
||||
mSourceBalance -= feePaid;
|
||||
std::cout << "mSourceBalance1.2: " << mSourceBalance << "\n";
|
||||
sle->setFieldAmount(sfBalance, mSourceBalance);
|
||||
|
||||
// VFALCO Should we call view().rawDestroyXRP() here as well?
|
||||
@@ -526,9 +560,12 @@ Transactor::checkSeqProxy(
|
||||
{
|
||||
auto const id = tx.getAccountID(sfAccount);
|
||||
|
||||
auto const tt = tx.getTxnType();
|
||||
|
||||
auto const sle = view.read(keylet::account(id));
|
||||
|
||||
SeqProxy const t_seqProx = tx.getSeqProxy();
|
||||
std::cout << "Transactor::checkSeqProxy: " << t_seqProx.value() << "\n";
|
||||
if (!sle)
|
||||
{
|
||||
if (view.rules().enabled(featureImport) &&
|
||||
@@ -559,6 +596,12 @@ Transactor::checkSeqProxy(
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
// // // pass all emitted tx provided their seq is 0
|
||||
// if (view.rules().enabled(featureBatch) && hook::isBatchTxn(tx))
|
||||
// {
|
||||
// return tesSUCCESS;
|
||||
// }
|
||||
|
||||
// reserved for emitted tx only at this time
|
||||
if (tx.isFieldPresent(sfFirstLedgerSequence))
|
||||
return tefINTERNAL;
|
||||
@@ -582,6 +625,7 @@ Transactor::checkSeqProxy(
|
||||
return terPRE_SEQ;
|
||||
}
|
||||
// It's an already-used sequence number.
|
||||
JLOG(j.trace()) << "applyTransaction: " << tt;
|
||||
JLOG(j.trace()) << "applyTransaction: has past sequence number "
|
||||
<< "a_seq=" << a_seq << " t_seq=" << t_seqProx;
|
||||
return tefPAST_SEQ;
|
||||
@@ -700,6 +744,10 @@ Transactor::consumeSeqProxy(SLE::pointer const& sleAccount)
|
||||
SeqProxy const seqProx = ctx_.tx.getSeqProxy();
|
||||
if (seqProx.isSeq())
|
||||
{
|
||||
// do not update sequence of sfAccountTxnID for batch tx
|
||||
if (ctx_.isBatchTxn())
|
||||
return tesSUCCESS;
|
||||
|
||||
// Note that if this transaction is a TicketCreate, then
|
||||
// the transaction will modify the account root sfSequence
|
||||
// yet again.
|
||||
@@ -776,6 +824,10 @@ Transactor::apply()
|
||||
{
|
||||
preCompute();
|
||||
|
||||
auto const tt = ctx_.tx.getTxnType();
|
||||
std::cout << "tt: " << tt << "\n";
|
||||
std::cout << "id: " << ctx_.tx.getTransactionID() << "\n";
|
||||
|
||||
// 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_));
|
||||
@@ -806,7 +858,6 @@ Transactor::apply()
|
||||
|
||||
view().update(sle);
|
||||
}
|
||||
|
||||
return doApply();
|
||||
}
|
||||
|
||||
@@ -1125,10 +1176,11 @@ Transactor::reset(XRPAmount fee)
|
||||
ApplyViewImpl& avi = dynamic_cast<ApplyViewImpl&>(ctx_.view());
|
||||
std::vector<STObject> executions;
|
||||
std::vector<STObject> emissions;
|
||||
avi.copyHookMetaData(executions, emissions);
|
||||
std::vector<STObject> batch;
|
||||
avi.copyHookMetaData(executions, emissions, batch);
|
||||
ctx_.discard();
|
||||
ApplyViewImpl& avi2 = dynamic_cast<ApplyViewImpl&>(ctx_.view());
|
||||
avi2.setHookMetaData(std::move(executions), std::move(emissions));
|
||||
avi2.setHookMetaData(std::move(executions), std::move(emissions), std::move(batch));
|
||||
|
||||
auto const txnAcct =
|
||||
view().peek(keylet::account(ctx_.tx.getAccountID(sfAccount)));
|
||||
@@ -1716,6 +1768,12 @@ Transactor::operator()()
|
||||
}
|
||||
#endif
|
||||
|
||||
// if (ctx_.isBatchTxn())
|
||||
// {
|
||||
// JLOG(j_.trace()) << "BAD BATCH: " << ctx_.tx.getTransactionID();
|
||||
// return {tecINTERNAL, false};
|
||||
// }
|
||||
|
||||
// Enforce an absolute bar to applying emitted transactions which are either
|
||||
// explicitly in preflight test mode, or somehow managed to make their way
|
||||
// here despite not being emitted here by a hook here.
|
||||
@@ -1806,7 +1864,8 @@ Transactor::operator()()
|
||||
if (ctx_.size() > oversizeMetaDataCap)
|
||||
result = tecOVERSIZE;
|
||||
|
||||
if (isTecClaim(result) && (view().flags() & tapFAIL_HARD))
|
||||
if ((isTecClaim(result) && (view().flags() & tapFAIL_HARD)) ||
|
||||
view().flags() & tapPREFLIGHT_BATCH)
|
||||
{
|
||||
// If the tapFAIL_HARD flag is set, a tec result
|
||||
// must not do anything
|
||||
@@ -2040,6 +2099,8 @@ Transactor::operator()()
|
||||
ctx_.destroyXRP(fee);
|
||||
|
||||
// Once we call apply, we will no longer be able to look at view()
|
||||
std::cout << "Transaction::apply" << "\n";
|
||||
std::cout << "Transaction::apply: " << (view().flags() & tapPREFLIGHT_BATCH) << "\n";
|
||||
ctx_.apply(result);
|
||||
}
|
||||
|
||||
|
||||
@@ -73,6 +73,20 @@ checkValidity(
|
||||
return {Validity::Valid, ""};
|
||||
}
|
||||
|
||||
if (rules.enabled(featureBatch) && applyFlags & tapPREFLIGHT_BATCH)
|
||||
{
|
||||
// batched transactions do not contain signatures
|
||||
if (tx.isFieldPresent(sfTxnSignature))
|
||||
return {Validity::SigBad, "Batch txn contains signature."};
|
||||
|
||||
std::string reason;
|
||||
if (!passesLocalChecks(tx, reason))
|
||||
return {Validity::SigGoodOnly, reason};
|
||||
|
||||
router.setFlags(id, SF_SIGGOOD);
|
||||
return {Validity::Valid, ""};
|
||||
}
|
||||
|
||||
if (flags & SF_SIGBAD)
|
||||
// Signature is known bad
|
||||
return {Validity::SigBad, "Transaction has bad signature."};
|
||||
@@ -147,6 +161,9 @@ apply(
|
||||
STAmountSO stAmountSO{view.rules().enabled(fixSTAmountCanonicalize)};
|
||||
NumberSO stNumberSO{view.rules().enabled(fixUniversalNumber)};
|
||||
|
||||
if (tx.isFieldPresent(sfBatchTxn))
|
||||
return {tesSUCCESS, false};
|
||||
|
||||
auto pfresult = preflight(app, view.rules(), tx, flags, j);
|
||||
auto pcresult = preclaim(pfresult, app, view);
|
||||
return doApply(pcresult, app, view);
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <ripple/app/tx/applySteps.h>
|
||||
#include <ripple/app/tx/impl/ApplyContext.h>
|
||||
#include <ripple/app/tx/impl/Batch.h>
|
||||
#include <ripple/app/tx/impl/CancelCheck.h>
|
||||
#include <ripple/app/tx/impl/CancelOffer.h>
|
||||
#include <ripple/app/tx/impl/CashCheck.h>
|
||||
@@ -104,6 +105,8 @@ invoke_preflight(PreflightContext const& ctx)
|
||||
return invoke_preflight_helper<DeleteAccount>(ctx);
|
||||
case ttACCOUNT_SET:
|
||||
return invoke_preflight_helper<SetAccount>(ctx);
|
||||
case ttBATCH:
|
||||
return invoke_preflight_helper<Batch>(ctx);
|
||||
case ttCHECK_CANCEL:
|
||||
return invoke_preflight_helper<CancelCheck>(ctx);
|
||||
case ttCHECK_CASH:
|
||||
@@ -190,6 +193,11 @@ invoke_preclaim(PreclaimContext const& ctx)
|
||||
// list one, preflight will have already a flagged a failure.
|
||||
auto const id = ctx.tx.getAccountID(sfAccount);
|
||||
|
||||
std::cout << "invoke_preclaim: " << ctx.tx.getTxnType() << "\n";
|
||||
|
||||
// if (ctx.tx.isFieldPresent(sfBatchTxn))
|
||||
// return tesSUCCESS;
|
||||
|
||||
if (id != beast::zero)
|
||||
{
|
||||
TER result = T::checkSeqProxy(ctx.view, ctx.tx, ctx.j);
|
||||
@@ -225,6 +233,8 @@ invoke_preclaim(PreclaimContext const& ctx)
|
||||
return invoke_preclaim<DeleteAccount>(ctx);
|
||||
case ttACCOUNT_SET:
|
||||
return invoke_preclaim<SetAccount>(ctx);
|
||||
case ttBATCH:
|
||||
return invoke_preclaim<Batch>(ctx);
|
||||
case ttCHECK_CANCEL:
|
||||
return invoke_preclaim<CancelCheck>(ctx);
|
||||
case ttCHECK_CASH:
|
||||
@@ -308,6 +318,8 @@ invoke_calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
return DeleteAccount::calculateBaseFee(view, tx);
|
||||
case ttACCOUNT_SET:
|
||||
return SetAccount::calculateBaseFee(view, tx);
|
||||
case ttBATCH:
|
||||
return Batch::calculateBaseFee(view, tx);
|
||||
case ttCHECK_CANCEL:
|
||||
return CancelCheck::calculateBaseFee(view, tx);
|
||||
case ttCHECK_CASH:
|
||||
@@ -433,6 +445,10 @@ invoke_apply(ApplyContext& ctx)
|
||||
SetAccount p(ctx);
|
||||
return p();
|
||||
}
|
||||
case ttBATCH: {
|
||||
Batch p(ctx);
|
||||
return p();
|
||||
}
|
||||
case ttCHECK_CANCEL: {
|
||||
CancelCheck p(ctx);
|
||||
return p();
|
||||
@@ -671,6 +687,9 @@ doApply(PreclaimResult const& preclaimResult, Application& app, OpenView& view)
|
||||
if (!preclaimResult.likelyToClaimFee)
|
||||
return {preclaimResult.ter, false};
|
||||
|
||||
// if (preclaimResult.tx.isFieldPresent(sfBatchTxn))
|
||||
// return tesSUCCESS;
|
||||
|
||||
ApplyContext ctx(
|
||||
app,
|
||||
view,
|
||||
|
||||
@@ -30,7 +30,7 @@ mulDiv(std::uint64_t value, std::uint64_t mul, std::uint64_t div)
|
||||
{
|
||||
using namespace boost::multiprecision;
|
||||
|
||||
uint128_t result;
|
||||
boost::multiprecision::uint128_t result;
|
||||
result = multiply(result, value, mul);
|
||||
|
||||
result /= div;
|
||||
|
||||
@@ -145,111 +145,78 @@ private:
|
||||
};
|
||||
|
||||
// VFALCO TODO This should only be enabled for maps.
|
||||
class pair_value_compare
|
||||
: public beast::detail::empty_base_optimization<Compare>
|
||||
#ifdef _LIBCPP_VERSION
|
||||
,
|
||||
public std::binary_function<value_type, value_type, bool>
|
||||
#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<Compare>(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>(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<Compare>
|
||||
#ifdef _LIBCPP_VERSION
|
||||
,
|
||||
public std::binary_function<Key, element, bool>
|
||||
#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>(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 <class K>
|
||||
bool operator() (K const& k, element const& e) const
|
||||
{
|
||||
return this->member() (k, extract (e.value));
|
||||
}
|
||||
|
||||
template <class K>
|
||||
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<Compare>::member();
|
||||
return *this;
|
||||
}
|
||||
|
||||
Compare const&
|
||||
compare() const
|
||||
{
|
||||
return beast::detail::empty_base_optimization<Compare>::member();
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -148,115 +148,84 @@ private:
|
||||
};
|
||||
|
||||
// VFALCO TODO hoist to remove template argument dependencies
|
||||
class ValueHash : private beast::detail::empty_base_optimization<Hash>
|
||||
#ifdef _LIBCPP_VERSION
|
||||
,
|
||||
public std::unary_function<element, std::size_t>
|
||||
#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<Hash>(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<KeyEqual>
|
||||
#ifdef _LIBCPP_VERSION
|
||||
,
|
||||
public std::binary_function<Key, element, bool>
|
||||
#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>(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 <class K>
|
||||
bool operator() (K const& k, element const& e) const
|
||||
{
|
||||
return this->member() (k, extract (e.value));
|
||||
}
|
||||
|
||||
template <class K>
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -42,6 +42,9 @@ enum ApplyFlags : std::uint32_t {
|
||||
|
||||
// Transaction is being tested against preflight before emission
|
||||
tapPREFLIGHT_EMIT = 0x800,
|
||||
|
||||
// Transaction is being tested against preflight before emission
|
||||
tapPREFLIGHT_BATCH = 0x1200,
|
||||
};
|
||||
|
||||
constexpr ApplyFlags
|
||||
|
||||
@@ -78,6 +78,15 @@ public:
|
||||
STTx const& tx,
|
||||
beast::Journal j);
|
||||
|
||||
/* Set hook metadata for a hook execution
|
||||
* Takes ownership / use std::move
|
||||
*/
|
||||
void
|
||||
addBatchExecutionMetaData(STObject&& batchExecution)
|
||||
{
|
||||
batchExecution_.push_back(std::move(batchExecution));
|
||||
}
|
||||
|
||||
/* Set hook metadata for a hook execution
|
||||
* Takes ownership / use std::move
|
||||
*/
|
||||
@@ -96,16 +105,19 @@ public:
|
||||
void
|
||||
setHookMetaData(
|
||||
std::vector<STObject>&& executions,
|
||||
std::vector<STObject>&& emissions)
|
||||
std::vector<STObject>&& emissions,
|
||||
std::vector<STObject>&& batch)
|
||||
{
|
||||
hookExecution_ = std::move(executions);
|
||||
hookEmission_ = std::move(emissions);
|
||||
batchExecution_ = std::move(batch);
|
||||
}
|
||||
|
||||
void
|
||||
copyHookMetaData(
|
||||
std::vector<STObject>& execution /* in */,
|
||||
std::vector<STObject>& emission /* in */)
|
||||
std::vector<STObject>& emission /* in */,
|
||||
std::vector<STObject>& batch /* in */)
|
||||
{
|
||||
std::copy(
|
||||
hookExecution_.begin(),
|
||||
@@ -115,6 +127,10 @@ public:
|
||||
hookEmission_.begin(),
|
||||
hookEmission_.end(),
|
||||
std::back_inserter(emission));
|
||||
std::copy(
|
||||
batchExecution_.begin(),
|
||||
batchExecution_.end(),
|
||||
std::back_inserter(batch));
|
||||
}
|
||||
|
||||
uint16_t
|
||||
@@ -141,6 +157,7 @@ public:
|
||||
|
||||
private:
|
||||
std::optional<STAmount> deliver_;
|
||||
std::vector<STObject> batchExecution_;
|
||||
std::vector<STObject> hookExecution_;
|
||||
std::vector<STObject> hookEmission_;
|
||||
};
|
||||
|
||||
65
src/ripple/ledger/OpenSandbox.h
Normal file
65
src/ripple/ledger/OpenSandbox.h
Normal file
@@ -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 <ripple/ledger/RawView.h>
|
||||
#include <ripple/ledger/OpenView.h>
|
||||
|
||||
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<void const> 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
|
||||
@@ -54,6 +54,7 @@ public:
|
||||
void
|
||||
apply(RawView& to)
|
||||
{
|
||||
std::cout << "Sandbox::apply: " << "\n";
|
||||
items_.apply(to);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -71,6 +71,7 @@ public:
|
||||
OpenView const& to,
|
||||
STTx const& tx,
|
||||
std::optional<STAmount> const& deliver,
|
||||
std::vector<STObject> const& batchExecution,
|
||||
std::vector<STObject> const& hookExecution,
|
||||
std::vector<STObject> const& hookEmission,
|
||||
beast::Journal j);
|
||||
@@ -81,6 +82,7 @@ public:
|
||||
STTx const& tx,
|
||||
TER ter,
|
||||
std::optional<STAmount> const& deliver,
|
||||
std::vector<STObject> const& batchExecution,
|
||||
std::vector<STObject> const& hookExecution,
|
||||
std::vector<STObject> const& hookEmission,
|
||||
beast::Journal j);
|
||||
|
||||
@@ -116,6 +116,7 @@ ApplyStateTable::generateTxMeta(
|
||||
OpenView const& to,
|
||||
STTx const& tx,
|
||||
std::optional<STAmount> const& deliver,
|
||||
std::vector<STObject> const& batchExecution,
|
||||
std::vector<STObject> const& hookExecution,
|
||||
std::vector<STObject> const& hookEmission,
|
||||
beast::Journal j)
|
||||
@@ -124,6 +125,9 @@ ApplyStateTable::generateTxMeta(
|
||||
if (deliver)
|
||||
meta.setDeliveredAmount(*deliver);
|
||||
|
||||
if (!batchExecution.empty())
|
||||
meta.setBatchExecutions(STArray{batchExecution, sfBatchExecutions});
|
||||
|
||||
if (!hookExecution.empty())
|
||||
meta.setHookExecutions(STArray{hookExecution, sfHookExecutions});
|
||||
|
||||
@@ -263,6 +267,7 @@ ApplyStateTable::apply(
|
||||
STTx const& tx,
|
||||
TER ter,
|
||||
std::optional<STAmount> const& deliver,
|
||||
std::vector<STObject> const& batchExecution,
|
||||
std::vector<STObject> const& hookExecution,
|
||||
std::vector<STObject> const& hookEmission,
|
||||
beast::Journal j)
|
||||
@@ -275,7 +280,7 @@ ApplyStateTable::apply(
|
||||
{
|
||||
// generate meta
|
||||
auto [meta, newMod] =
|
||||
generateTxMeta(to, tx, deliver, hookExecution, hookEmission, j);
|
||||
generateTxMeta(to, tx, deliver, batchExecution, hookExecution, hookEmission, j);
|
||||
|
||||
// add any new modified nodes to the modification set
|
||||
for (auto& mod : newMod)
|
||||
|
||||
@@ -31,7 +31,8 @@ ApplyViewImpl::ApplyViewImpl(ReadView const* base, ApplyFlags flags)
|
||||
void
|
||||
ApplyViewImpl::apply(OpenView& to, STTx const& tx, TER ter, beast::Journal j)
|
||||
{
|
||||
items_.apply(to, tx, ter, deliver_, hookExecution_, hookEmission_, j);
|
||||
std::cout << "ApplyViewImpl::apply " << "\n";
|
||||
items_.apply(to, tx, ter, deliver_, batchExecution_, hookExecution_, hookEmission_, j);
|
||||
}
|
||||
|
||||
TxMeta
|
||||
@@ -41,7 +42,7 @@ ApplyViewImpl::generateProvisionalMeta(
|
||||
beast::Journal j)
|
||||
{
|
||||
auto [meta, _] = items_.generateTxMeta(
|
||||
to, tx, deliver_, hookExecution_, hookEmission_, j);
|
||||
to, tx, deliver_, batchExecution_, hookExecution_, hookEmission_, j);
|
||||
|
||||
return meta;
|
||||
}
|
||||
|
||||
@@ -129,9 +129,15 @@ OpenView::txCount() const
|
||||
void
|
||||
OpenView::apply(TxsRawView& to) const
|
||||
{
|
||||
// std::cout << "OpenView::apply" << "\n";
|
||||
// std::cout << "OpenView::apply: " << items_.size() << "\n";
|
||||
std::cout << "OpenView::apply: " << txs_.size() << "\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);
|
||||
}
|
||||
}
|
||||
|
||||
//---
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -69,14 +69,9 @@ public:
|
||||
public:
|
||||
// Iterator transformation to extract the endpoint from Element
|
||||
struct Transform
|
||||
#ifdef _LIBCPP_VERSION
|
||||
: public std::unary_function<Element, Endpoint>
|
||||
#endif
|
||||
{
|
||||
#ifndef _LIBCPP_VERSION
|
||||
using first_argument = Element;
|
||||
using result_type = Endpoint;
|
||||
#endif
|
||||
|
||||
explicit Transform() = default;
|
||||
|
||||
@@ -239,15 +234,9 @@ public:
|
||||
|
||||
template <bool IsConst>
|
||||
struct Transform
|
||||
#ifdef _LIBCPP_VERSION
|
||||
: public std::
|
||||
unary_function<typename lists_type::value_type, Hop<IsConst>>
|
||||
#endif
|
||||
{
|
||||
#ifndef _LIBCPP_VERSION
|
||||
using first_argument = typename lists_type::value_type;
|
||||
using result_type = Hop<IsConst>;
|
||||
#endif
|
||||
|
||||
explicit Transform() = default;
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ namespace detail {
|
||||
// Feature.cpp. Because it's only used to reserve storage, and determine how
|
||||
// large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than
|
||||
// the actual number of amendments. A LogicError on startup will verify this.
|
||||
static constexpr std::size_t numFeatures = 70;
|
||||
static constexpr std::size_t numFeatures = 71;
|
||||
|
||||
/** Amendments that this server supports and the default voting behavior.
|
||||
Whether they are enabled depends on the Rules defined in the validated
|
||||
@@ -358,6 +358,7 @@ extern uint256 const fixXahauV2;
|
||||
extern uint256 const featureRemit;
|
||||
extern uint256 const featureZeroB2M;
|
||||
extern uint256 const fixNSDelete;
|
||||
extern uint256 const featureBatch;
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
|
||||
@@ -341,6 +341,7 @@ extern SF_UINT8 const sfTransactionResult;
|
||||
extern SF_UINT8 const sfTickSize;
|
||||
extern SF_UINT8 const sfUNLModifyDisabling;
|
||||
extern SF_UINT8 const sfHookResult;
|
||||
extern SF_UINT8 const sfBatchIndex;
|
||||
|
||||
// 16-bit integers (common)
|
||||
extern SF_UINT16 const sfLedgerEntryType;
|
||||
@@ -410,6 +411,7 @@ extern SF_UINT32 const sfRewardLgrLast;
|
||||
extern SF_UINT32 const sfFirstNFTokenSequence;
|
||||
extern SF_UINT32 const sfImportSequence;
|
||||
extern SF_UINT32 const sfXahauActivationLgrSeq;
|
||||
extern SF_UINT32 const sfOuterSequence;
|
||||
|
||||
// 64-bit integers (common)
|
||||
extern SF_UINT64 const sfIndexNext;
|
||||
@@ -580,6 +582,7 @@ extern SField const sfSignerEntry;
|
||||
extern SField const sfNFToken;
|
||||
extern SField const sfEmitDetails;
|
||||
extern SField const sfHook;
|
||||
extern SField const sfBatchTxn;
|
||||
|
||||
extern SField const sfSigner;
|
||||
extern SField const sfMajority;
|
||||
@@ -594,6 +597,8 @@ extern SField const sfImportVLKey;
|
||||
extern SField const sfHookEmission;
|
||||
extern SField const sfMintURIToken;
|
||||
extern SField const sfAmountEntry;
|
||||
extern SField const sfBatchExecution;
|
||||
extern SField const sfRawTransaction;
|
||||
|
||||
// array of objects (common)
|
||||
// ARRAY/1 is reserved for end of array
|
||||
@@ -608,6 +613,7 @@ extern SField const sfMemos;
|
||||
extern SField const sfNFTokens;
|
||||
extern SField const sfHooks;
|
||||
extern SField const sfGenesisMint;
|
||||
extern SField const sfEmittedTxns;
|
||||
|
||||
// array of objects (uncommon)
|
||||
extern SField const sfMajorities;
|
||||
@@ -622,6 +628,8 @@ extern SField const sfActiveValidators;
|
||||
extern SField const sfImportVLKeys;
|
||||
extern SField const sfHookEmissions;
|
||||
extern SField const sfAmounts;
|
||||
extern SField const sfBatchExecutions;
|
||||
extern SField const sfRawTransactions;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -340,6 +340,7 @@ enum TECcodes : TERUnderlyingType {
|
||||
tecXCHAIN_SELF_COMMIT = 185, // RESERVED - XCHAIN
|
||||
tecXCHAIN_BAD_PUBLIC_KEY_ACCOUNT_PAIR = 186, // RESERVED - XCHAIN
|
||||
tecINSUF_RESERVE_SELLER = 187,
|
||||
tecBATCH_FAILURE = 188,
|
||||
tecLAST_POSSIBLE_ENTRY = 255,
|
||||
};
|
||||
|
||||
|
||||
@@ -168,6 +168,16 @@ constexpr std::uint32_t const tfURITokenNonMintMask = ~tfUniversal;
|
||||
// ClaimReward flags:
|
||||
constexpr std::uint32_t const tfOptOut = 0x00000001;
|
||||
|
||||
enum BatchFlags : std::uint32_t {
|
||||
tfAllOrNothing = 0x00000001,
|
||||
tfOnlyOne = 0x00000002,
|
||||
tfUntilFailure = 0x00000004,
|
||||
tfIndependent = 0x00000008,
|
||||
};
|
||||
|
||||
constexpr std::uint32_t const tfBatchMask =
|
||||
~(tfUniversal | tfAllOrNothing | tfOnlyOne | tfUntilFailure | tfIndependent);
|
||||
|
||||
// clang-format on
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -185,6 +185,8 @@ enum TxType : std::uint16_t
|
||||
ttUNL_MODIFY = 102,
|
||||
ttEMIT_FAILURE = 103,
|
||||
ttUNL_REPORT = 104,
|
||||
|
||||
ttBATCH = 105,
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
|
||||
@@ -116,6 +116,12 @@ public:
|
||||
mDelivered = delivered;
|
||||
}
|
||||
|
||||
STArray const&
|
||||
getBatchExecutions() const
|
||||
{
|
||||
return *mBatchExecutions;
|
||||
}
|
||||
|
||||
STArray const&
|
||||
getHookExecutions() const
|
||||
{
|
||||
@@ -128,6 +134,12 @@ public:
|
||||
return *mHookEmissions;
|
||||
}
|
||||
|
||||
void
|
||||
setBatchExecutions(STArray const& batchExecutions)
|
||||
{
|
||||
mBatchExecutions = batchExecutions;
|
||||
}
|
||||
|
||||
void
|
||||
setHookExecutions(STArray const& hookExecutions)
|
||||
{
|
||||
@@ -140,6 +152,12 @@ public:
|
||||
mHookEmissions = hookEmissions;
|
||||
}
|
||||
|
||||
bool
|
||||
hasBatchExecutions() const
|
||||
{
|
||||
return static_cast<bool>(mBatchExecutions);
|
||||
}
|
||||
|
||||
bool
|
||||
hasHookExecutions() const
|
||||
{
|
||||
@@ -172,6 +190,7 @@ private:
|
||||
int mResult;
|
||||
|
||||
std::optional<STAmount> mDelivered;
|
||||
std::optional<STArray> mBatchExecutions;
|
||||
std::optional<STArray> mHookExecutions;
|
||||
std::optional<STArray> mHookEmissions;
|
||||
|
||||
|
||||
@@ -464,6 +464,7 @@ REGISTER_FIX (fixXahauV2, Supported::yes, VoteBehavior::De
|
||||
REGISTER_FEATURE(Remit, Supported::yes, VoteBehavior::DefaultNo);
|
||||
REGISTER_FEATURE(ZeroB2M, Supported::yes, VoteBehavior::DefaultNo);
|
||||
REGISTER_FIX (fixNSDelete, Supported::yes, VoteBehavior::DefaultNo);
|
||||
REGISTER_FEATURE(Batch, Supported::yes, VoteBehavior::DefaultNo);
|
||||
|
||||
// The following amendments are obsolete, but must remain supported
|
||||
// because they could potentially get enabled.
|
||||
|
||||
@@ -62,6 +62,12 @@ InnerObjectFormats::InnerObjectFormats()
|
||||
{sfFirstLedgerSequence, soeREQUIRED},
|
||||
});
|
||||
|
||||
add(sfBatchExecution.jsonName.c_str(),
|
||||
sfBatchExecution.getCode(),
|
||||
{{sfTransactionType, soeREQUIRED},
|
||||
{sfTransactionResult, soeREQUIRED},
|
||||
{sfTransactionHash, soeOPTIONAL}});
|
||||
|
||||
add(sfHookExecution.jsonName.c_str(),
|
||||
sfHookExecution.getCode(),
|
||||
{{sfHookResult, soeREQUIRED},
|
||||
@@ -157,6 +163,13 @@ InnerObjectFormats::InnerObjectFormats()
|
||||
{sfDigest, soeOPTIONAL},
|
||||
{sfFlags, soeOPTIONAL},
|
||||
});
|
||||
|
||||
add(sfBatchTxn.jsonName.c_str(),
|
||||
sfBatchTxn.getCode(),
|
||||
{{sfAccount, soeREQUIRED},
|
||||
{sfOuterSequence, soeREQUIRED},
|
||||
{sfSequence, soeOPTIONAL},
|
||||
{sfBatchIndex, soeREQUIRED}});
|
||||
}
|
||||
|
||||
InnerObjectFormats const&
|
||||
|
||||
@@ -89,6 +89,7 @@ CONSTRUCT_TYPED_SFIELD(sfTransactionResult, "TransactionResult", UINT8,
|
||||
CONSTRUCT_TYPED_SFIELD(sfTickSize, "TickSize", UINT8, 16);
|
||||
CONSTRUCT_TYPED_SFIELD(sfUNLModifyDisabling, "UNLModifyDisabling", UINT8, 17);
|
||||
CONSTRUCT_TYPED_SFIELD(sfHookResult, "HookResult", UINT8, 18);
|
||||
CONSTRUCT_TYPED_SFIELD(sfBatchIndex, "BatchIndex", UINT8, 19);
|
||||
|
||||
// 16-bit integers
|
||||
CONSTRUCT_TYPED_SFIELD(sfLedgerEntryType, "LedgerEntryType", UINT16, 1, SField::sMD_Never);
|
||||
@@ -157,6 +158,7 @@ CONSTRUCT_TYPED_SFIELD(sfLockCount, "LockCount", UINT32,
|
||||
|
||||
CONSTRUCT_TYPED_SFIELD(sfFirstNFTokenSequence, "FirstNFTokenSequence", UINT32, 50);
|
||||
|
||||
CONSTRUCT_TYPED_SFIELD(sfOuterSequence, "OuterSequence", UINT32, 95);
|
||||
CONSTRUCT_TYPED_SFIELD(sfXahauActivationLgrSeq, "XahauActivationLgrSeq",UINT32, 96);
|
||||
CONSTRUCT_TYPED_SFIELD(sfImportSequence, "ImportSequence", UINT32, 97);
|
||||
CONSTRUCT_TYPED_SFIELD(sfRewardTime, "RewardTime", UINT32, 98);
|
||||
@@ -344,12 +346,15 @@ 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);
|
||||
CONSTRUCT_UNTYPED_SFIELD(sfImportVLKey, "ImportVLKey", OBJECT, 94);
|
||||
CONSTRUCT_UNTYPED_SFIELD(sfHookEmission, "HookEmission", OBJECT, 93);
|
||||
CONSTRUCT_UNTYPED_SFIELD(sfMintURIToken, "MintURIToken", OBJECT, 92);
|
||||
CONSTRUCT_UNTYPED_SFIELD(sfAmountEntry, "AmountEntry", OBJECT, 91);
|
||||
CONSTRUCT_UNTYPED_SFIELD(sfBatchTxn, "BatchTxn", OBJECT, 90);
|
||||
|
||||
// array of objects
|
||||
// ARRAY/1 is reserved for end of array
|
||||
@@ -370,6 +375,9 @@ 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);
|
||||
CONSTRUCT_UNTYPED_SFIELD(sfActiveValidators, "ActiveValidators", ARRAY, 95);
|
||||
CONSTRUCT_UNTYPED_SFIELD(sfImportVLKeys, "ImportVLKeys", ARRAY, 94);
|
||||
|
||||
@@ -186,7 +186,17 @@ STTx::getSeqProxy() const
|
||||
{
|
||||
std::uint32_t const seq{getFieldU32(sfSequence)};
|
||||
if (seq != 0)
|
||||
{
|
||||
return SeqProxy::sequence(seq);
|
||||
}
|
||||
|
||||
if (isFieldPresent(sfBatchTxn))
|
||||
{
|
||||
STObject const batchTxn = const_cast<ripple::STTx&>(*this).getField(sfBatchTxn).downcast<STObject>();
|
||||
std::uint32_t const startSequence{batchTxn.getFieldU32(sfOuterSequence)};
|
||||
std::uint32_t const batchIndex{batchTxn.getFieldU8(sfBatchIndex)};
|
||||
return SeqProxy::sequence(startSequence + batchIndex + 1);
|
||||
}
|
||||
|
||||
std::optional<std::uint32_t> const ticketSeq{operator[](~sfTicketSequence)};
|
||||
if (!ticketSeq)
|
||||
|
||||
@@ -92,6 +92,7 @@ transResults()
|
||||
MAKE_ERROR(tecREQUIRES_FLAG, "The transaction or part-thereof requires a flag that wasn't set."),
|
||||
MAKE_ERROR(tecPRECISION_LOSS, "The amounts used by the transaction cannot interact."),
|
||||
MAKE_ERROR(tecINSUF_RESERVE_SELLER, "The seller of an object has insufficient reserves, and thus cannot complete the sale."),
|
||||
MAKE_ERROR(tecBATCH_FAILURE, "Tx Batch Failure."),
|
||||
MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."),
|
||||
MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."),
|
||||
MAKE_ERROR(tefBAD_AUTH, "Transaction's public key is not authorized."),
|
||||
|
||||
@@ -44,6 +44,8 @@ TxFormats::TxFormats()
|
||||
{sfNetworkID, soeOPTIONAL},
|
||||
{sfHookParameters, soeOPTIONAL},
|
||||
{sfOperationLimit, soeOPTIONAL},
|
||||
{sfCloseResolution, soeOPTIONAL},
|
||||
{sfBatchTxn, soeOPTIONAL},
|
||||
};
|
||||
|
||||
add(jss::AccountSet,
|
||||
@@ -456,6 +458,13 @@ TxFormats::TxFormats()
|
||||
{sfTicketSequence, soeOPTIONAL},
|
||||
},
|
||||
commonFields);
|
||||
|
||||
add(jss::Batch,
|
||||
ttBATCH,
|
||||
{
|
||||
{sfRawTransactions, soeOPTIONAL},
|
||||
},
|
||||
commonFields);
|
||||
}
|
||||
|
||||
TxFormats const&
|
||||
|
||||
@@ -44,6 +44,9 @@ TxMeta::TxMeta(
|
||||
if (obj.isFieldPresent(sfDeliveredAmount))
|
||||
setDeliveredAmount(obj.getFieldAmount(sfDeliveredAmount));
|
||||
|
||||
if (obj.isFieldPresent(sfBatchExecutions))
|
||||
setBatchExecutions(obj.getFieldArray(sfBatchExecutions));
|
||||
|
||||
if (obj.isFieldPresent(sfHookExecutions))
|
||||
setHookExecutions(obj.getFieldArray(sfHookExecutions));
|
||||
|
||||
@@ -68,6 +71,9 @@ TxMeta::TxMeta(uint256 const& txid, std::uint32_t ledger, STObject const& obj)
|
||||
if (obj.isFieldPresent(sfDeliveredAmount))
|
||||
setDeliveredAmount(obj.getFieldAmount(sfDeliveredAmount));
|
||||
|
||||
if (obj.isFieldPresent(sfBatchExecutions))
|
||||
setBatchExecutions(obj.getFieldArray(sfBatchExecutions));
|
||||
|
||||
if (obj.isFieldPresent(sfHookExecutions))
|
||||
setHookExecutions(obj.getFieldArray(sfHookExecutions));
|
||||
|
||||
@@ -228,6 +234,9 @@ TxMeta::getAsObject() const
|
||||
if (hasDeliveredAmount())
|
||||
metaData.setFieldAmount(sfDeliveredAmount, getDeliveredAmount());
|
||||
|
||||
if (hasBatchExecutions())
|
||||
metaData.setFieldArray(sfBatchExecutions, getBatchExecutions());
|
||||
|
||||
if (hasHookExecutions())
|
||||
metaData.setFieldArray(sfHookExecutions, getHookExecutions());
|
||||
|
||||
|
||||
@@ -50,7 +50,9 @@ JSS(AccountSet); // transaction type.
|
||||
JSS(Amendments); // ledger type.
|
||||
JSS(Amount); // in: TransactionSign; field.
|
||||
JSS(Authorize); // field
|
||||
JSS(Blob);
|
||||
JSS(Batch); // transaction type.
|
||||
JSS(RawTransaction); // in: Batch
|
||||
JSS(Blob); // field.
|
||||
JSS(Check); // ledger type.
|
||||
JSS(CheckCancel); // transaction type.
|
||||
JSS(CheckCash); // transaction type.
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
437
src/test/app/Batch_test.cpp
Normal file
437
src/test/app/Batch_test.cpp
Normal file
@@ -0,0 +1,437 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2019 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.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <ripple/protocol/Feature.h>
|
||||
#include <ripple/protocol/TxFlags.h>
|
||||
#include <ripple/protocol/jss.h>
|
||||
#include <test/jtx.h>
|
||||
|
||||
// tfOnlyOne
|
||||
// Tx1: Payment = tecUNFUNDED => Leave
|
||||
// Tx2: Payment = tesSUCCESS => Leave
|
||||
// TER(tesSUCCESS)
|
||||
|
||||
// tfUntilFailure
|
||||
// Tx1: Payment = tesSUCCESS => Leave
|
||||
// Tx2: Payment = tecUNFUNDED => Leave
|
||||
// TER(tesSUCCESS)
|
||||
|
||||
// tfBatchAtomic
|
||||
// Tx1: Payment = tesSUCCESS => Revert
|
||||
// Tx2: Payment = tecUNFUNDED => Leave
|
||||
// TER(tecBATCH_FAILURE)
|
||||
|
||||
// Broadcast - Manually broadcast the inner transactions
|
||||
// Stacking Views
|
||||
// TicketCreate as first of batch
|
||||
// Sequence optional except when needed specifically
|
||||
// Bypass the queue for inner transactions
|
||||
// Fee only on outer - if 2 payments and fee escellation make sure that the outer tx includes the fee escallation for all inner
|
||||
// Look at TicketCreate for creating virtual tickets (Transactor.cpp) (TransactionConsequences.cpp)
|
||||
// If we have the batch index, the virtual ticket number is seq of batch + batch index.
|
||||
// Think about multiple different angles of this. AccountA & AccountB.
|
||||
|
||||
// OptionB: Use OfferID
|
||||
|
||||
namespace ripple {
|
||||
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<TestBatchData, 2> 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;
|
||||
}
|
||||
}
|
||||
|
||||
Json::Value
|
||||
addBatchTx(
|
||||
Json::Value jv,
|
||||
Json::Value const& tx,
|
||||
jtx::Account const& account,
|
||||
XRPAmount feeDrops,
|
||||
std::uint8_t index,
|
||||
std::uint32_t outerSequence)
|
||||
{
|
||||
jv[sfRawTransactions.jsonName][index] = Json::Value{};
|
||||
jv[sfRawTransactions.jsonName][index][jss::RawTransaction] = tx;
|
||||
jv[sfRawTransactions.jsonName][index][jss::RawTransaction][jss::SigningPubKey] = strHex(account.pk());
|
||||
jv[sfRawTransactions.jsonName][index][jss::RawTransaction][sfFee.jsonName] = 0;
|
||||
jv[sfRawTransactions.jsonName][index][jss::RawTransaction][jss::Sequence] = 0;
|
||||
jv[sfRawTransactions.jsonName][index][jss::RawTransaction][sfBatchTxn.jsonName] = Json::Value{};
|
||||
jv[sfRawTransactions.jsonName][index][jss::RawTransaction][sfBatchTxn.jsonName][jss::Account] = account.human();
|
||||
jv[sfRawTransactions.jsonName][index][jss::RawTransaction][sfBatchTxn.jsonName][sfOuterSequence.jsonName] = outerSequence;
|
||||
jv[sfRawTransactions.jsonName][index][jss::RawTransaction][sfBatchTxn.jsonName][sfBatchIndex.jsonName] = index;
|
||||
return jv;
|
||||
}
|
||||
|
||||
// OnSucess -> (0)
|
||||
// OnFailure -> Next Tx Index
|
||||
// Ex.
|
||||
// 0: MintURIToken: If Fail -> 2
|
||||
// 1: Payment: If Fail -> 2
|
||||
// 2: Payment: 0
|
||||
|
||||
void
|
||||
testBadPubKey(FeatureBitset features)
|
||||
{
|
||||
testcase("bad pubkey");
|
||||
|
||||
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");
|
||||
env.fund(XRP(1000), alice, bob);
|
||||
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
|
||||
Json::Value const tx1 = pay(alice, bob, XRP(1));
|
||||
jv = addBatchTx(jv, tx1, alice, feeDrops, 0, 0);
|
||||
|
||||
// Tx 2
|
||||
Json::Value const tx2 = pay(alice, bob, XRP(1));
|
||||
jv = addBatchTx(jv, tx2, bob, feeDrops, 1, 0);
|
||||
|
||||
env(jv, fee(feeDrops * 2), ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
std::array<TestBatchData, 2> 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) == 2);
|
||||
BEAST_EXPECT(env.balance(alice) == XRP(1000) - XRP(1) - (feeDrops * 2));
|
||||
BEAST_EXPECT(env.balance(bob) == XRP(1000) + XRP(1));
|
||||
}
|
||||
|
||||
void
|
||||
testUnfunded(FeatureBitset features)
|
||||
{
|
||||
testcase("unfunded");
|
||||
|
||||
using namespace test::jtx;
|
||||
using namespace std::literals;
|
||||
|
||||
test::jtx::Env env{*this, envconfig()};
|
||||
|
||||
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
|
||||
Json::Value const tx1 = pay(alice, bob, XRP(1));
|
||||
jv = addBatchTx(jv, tx1, alice, feeDrops, 0, 0);
|
||||
|
||||
// Tx 2
|
||||
Json::Value const tx2 = pay(alice, bob, XRP(999));
|
||||
jv = addBatchTx(jv, tx2, alice, feeDrops, 1, 0);
|
||||
|
||||
env(jv, fee(feeDrops * 2), ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
std::array<TestBatchData, 2> 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) == 2);
|
||||
BEAST_EXPECT(env.balance(alice) == XRP(1000) - XRP(1) - (feeDrops * 1));
|
||||
BEAST_EXPECT(env.balance(bob) == XRP(1000) + XRP(1));
|
||||
}
|
||||
|
||||
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();
|
||||
jv[jss::Sequence] = seq;
|
||||
|
||||
// Batch Transactions
|
||||
jv[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue};
|
||||
|
||||
// Tx 1
|
||||
Json::Value const tx1 = pay(alice, bob, XRP(1));
|
||||
jv = addBatchTx(jv, tx1, alice, feeDrops, 0, seq);
|
||||
|
||||
// Tx 2
|
||||
Json::Value const tx2 = pay(alice, bob, XRP(1));
|
||||
jv = addBatchTx(jv, tx2, alice, feeDrops, 1, seq);
|
||||
|
||||
env(jv, fee(feeDrops * 2), ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
std::array<TestBatchData, 2> testCases = {{
|
||||
{"tesSUCCESS", "Payment"},
|
||||
{"tesSUCCESS", "Payment"},
|
||||
}};
|
||||
|
||||
Json::Value params;
|
||||
params[jss::ledger_index] = env.current()->seq() - 1;
|
||||
params[jss::transactions] = true;
|
||||
params[jss::expand] = true;
|
||||
auto const jrr = env.rpc("json", "ledger", to_string(params));
|
||||
std::cout << "jrr: " << jrr << "\n";
|
||||
|
||||
// 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);
|
||||
|
||||
std::cout << "seq: " << env.seq(alice) << "\n";
|
||||
std::cout << "alice: " << env.balance(alice) << "\n";
|
||||
std::cout << "bob: " << env.balance(bob) << "\n";
|
||||
|
||||
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
|
||||
testAtomicFailure(FeatureBitset features)
|
||||
{
|
||||
testcase("atomic failure");
|
||||
|
||||
using namespace test::jtx;
|
||||
using namespace std::literals;
|
||||
|
||||
test::jtx::Env env{*this, envconfig()};
|
||||
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");
|
||||
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
|
||||
Json::Value const tx1 = pay(alice, bob, XRP(1));
|
||||
jv = addBatchTx(jv, tx1, alice, feeDrops, 0, seq + 1);
|
||||
|
||||
// Tx 2
|
||||
Json::Value const tx2 = pay(alice, bob, XRP(999));
|
||||
jv = addBatchTx(jv, tx2, alice, feeDrops, 1, seq + 2);
|
||||
|
||||
env(jv, fee(feeDrops * 2), txflags(tfAllOrNothing), ter(tecBATCH_FAILURE));
|
||||
env.close();
|
||||
|
||||
Json::Value params;
|
||||
params[jss::ledger_index] = env.current()->seq() - 1;
|
||||
params[jss::transactions] = true;
|
||||
params[jss::expand] = true;
|
||||
auto const jrr = env.rpc("json", "ledger", to_string(params));
|
||||
std::cout << "jrr: " << jrr << "\n";
|
||||
|
||||
std::cout << "seq: " << env.seq(alice) << "\n";
|
||||
std::cout << "alice: " << env.balance(alice) << "\n";
|
||||
std::cout << "bob: " << env.balance(bob) << "\n";
|
||||
|
||||
BEAST_EXPECT(env.seq(alice) == 2);
|
||||
BEAST_EXPECT(env.balance(alice) == XRP(1000) - (feeDrops * 2));
|
||||
BEAST_EXPECT(env.balance(bob) == XRP(1000));
|
||||
}
|
||||
|
||||
void
|
||||
testFirstFailure(FeatureBitset features)
|
||||
{
|
||||
testcase("first failure");
|
||||
|
||||
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");
|
||||
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
|
||||
Json::Value const tx1 = pay(alice, bob, XRP(1));
|
||||
jv = addBatchTx(jv, tx1, alice, feeDrops, 0, 0);
|
||||
|
||||
// Tx 2
|
||||
Json::Value const tx2 = pay(alice, bob, XRP(999));
|
||||
jv = addBatchTx(jv, tx2, alice, feeDrops, 1, 1);
|
||||
|
||||
// Tx 3
|
||||
Json::Value const tx3 = pay(alice, bob, XRP(1));
|
||||
jv = addBatchTx(jv, tx3, alice, feeDrops, 2, 2);
|
||||
|
||||
env(jv, fee(feeDrops * 3), txflags(tfOnlyOne), ter(tecBATCH_FAILURE));
|
||||
env.close();
|
||||
|
||||
Json::Value params;
|
||||
params[jss::ledger_index] = env.current()->seq() - 1;
|
||||
params[jss::transactions] = true;
|
||||
params[jss::expand] = true;
|
||||
auto const jrr = env.rpc("json", "ledger", to_string(params));
|
||||
std::cout << "jrr: " << jrr << "\n";
|
||||
|
||||
std::cout << "seq: " << env.seq(alice) << "\n";
|
||||
std::cout << "alice: " << env.balance(alice) << "\n";
|
||||
std::cout << "bob: " << env.balance(bob) << "\n";
|
||||
|
||||
BEAST_EXPECT(env.seq(alice) == 4);
|
||||
BEAST_EXPECT(env.balance(alice) == XRP(1000) - XRP(1));
|
||||
BEAST_EXPECT(env.balance(bob) == XRP(1000) + XRP(1));
|
||||
}
|
||||
|
||||
void
|
||||
testWithFeats(FeatureBitset features)
|
||||
{
|
||||
// testBadPubKey(features);
|
||||
// testUnfunded(features);
|
||||
testSuccess(features);
|
||||
// testAtomicFailure(features);
|
||||
// testFirstFailure(features);
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
using namespace test::jtx;
|
||||
auto const sa = supported_amendments();
|
||||
testWithFeats(sa);
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(Batch, app, ripple);
|
||||
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
@@ -5096,4 +5096,4 @@ BEAST_DEFINE_TESTSUITE_PRIO(TxQ1, app, ripple, 1);
|
||||
BEAST_DEFINE_TESTSUITE_PRIO(TxQ2, app, ripple, 1);
|
||||
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
} // namespace ripple
|
||||
Reference in New Issue
Block a user