Compare commits

...

14 Commits

Author SHA1 Message Date
Denis Angell
86fbddaa2e update fields 2024-04-25 09:34:00 +02:00
Denis Angell
d67a93dcf7 update fields 2024-04-24 13:30:22 +02:00
Denis Angell
d596ceca4a update flags 2024-04-24 13:30:12 +02:00
Denis Angell
7a4e2500bb dump 2024-04-16 10:34:53 +02:00
Denis Angell
175f436974 dump 2024-04-13 09:32:24 +02:00
Denis Angell
8a8822cb57 dump more building 2024-04-04 10:32:04 +02:00
Denis Angell
43fce18099 dump bulding 2024-03-29 18:34:47 +01:00
Denis Angell
aa438663e8 Update TxQ_test.cpp 2024-03-28 22:24:08 +01:00
Denis Angell
cbe8c6869c Merge branch 'dev' into ttBATCH 2024-03-28 22:21:30 +01:00
Denis Angell
a1da3ab477 bad commit 2024-03-28 22:18:52 +01:00
Denis Angell
1a3376e453 build/test 2024-03-28 21:48:21 +01:00
Denis Angell
a8ff49739d misc 2023-12-05 10:51:19 +01:00
Denis Angell
e326e11a6f add all transactions 2023-12-05 03:26:19 +01:00
Denis Angell
e26d4164a9 add batch 2023-12-04 10:37:47 +01:00
49 changed files with 1521 additions and 123 deletions

View File

@@ -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,

View File

@@ -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

View File

@@ -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

View File

@@ -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)
{

View File

@@ -59,6 +59,7 @@ buildLedgerImpl(
OpenView accum(&*built);
assert(!accum.open());
applyTxs(accum, built);
std::cout << "BuildLedger::buildLedgerImpl: " << "\n";
accum.apply(*built);
}

View File

@@ -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);

View File

@@ -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))
{

View File

@@ -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);
{

View File

@@ -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.

View File

@@ -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);
}

View File

@@ -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_;
};

View 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

View 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

View File

@@ -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)

View File

@@ -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();

View File

@@ -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.

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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,

View File

@@ -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;

View File

@@ -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;
}
};

View File

@@ -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;
}
};

View File

@@ -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

View File

@@ -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_;
};

View 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

View File

@@ -54,6 +54,7 @@ public:
void
apply(RawView& to)
{
std::cout << "Sandbox::apply: " << "\n";
items_.apply(to);
}
};

View File

@@ -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);

View File

@@ -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)

View File

@@ -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;
}

View File

@@ -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);
}
}
//---

View File

@@ -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;

View File

@@ -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;

View File

@@ -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

View File

@@ -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;
//------------------------------------------------------------------------------

View File

@@ -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,
};

View File

@@ -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

View File

@@ -185,6 +185,8 @@ enum TxType : std::uint16_t
ttUNL_MODIFY = 102,
ttEMIT_FAILURE = 103,
ttUNL_REPORT = 104,
ttBATCH = 105,
};
// clang-format on

View File

@@ -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;

View File

@@ -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.

View File

@@ -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&

View File

@@ -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);

View File

@@ -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)

View File

@@ -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."),

View File

@@ -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&

View File

@@ -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());

View File

@@ -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.

View File

@@ -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
View 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

View File

@@ -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