From 1ca680d0b92f9916839c66f6e117073a33b6e623 Mon Sep 17 00:00:00 2001 From: Dmytro Kozhevin Date: Wed, 12 Jun 2024 18:32:10 -0400 Subject: [PATCH] Initial support for parallel Soroban phase XDR. I've tried to minimize the scope of the changes; specifically this doesn't contain any actual logic for the parallel execution (such as data dependency validation and building parallel stages). However, there is still some refactoring that needed to happen in order to support new, more complex tx sets. I've also removed some legacy surge pricing logic and the corresponding tests - we should no longer need the surge pricing logic that covers more than one source account per tx set. --- Builds/VisualStudio/stellar-core.vcxproj | 2 + .../VisualStudio/stellar-core.vcxproj.filters | 6 + deny.toml | 5 +- src/herder/HerderImpl.cpp | 7 +- src/herder/SurgePricingUtils.cpp | 4 +- src/herder/SurgePricingUtils.h | 42 + src/herder/TransactionQueue.cpp | 4 +- src/herder/TransactionQueue.h | 2 +- src/herder/TxQueueLimiter.h | 38 - src/herder/TxSetFrame.cpp | 1874 +++++++++++------ src/herder/TxSetFrame.h | 311 ++- src/herder/TxSetUtils.cpp | 97 +- src/herder/TxSetUtils.h | 27 +- src/herder/test/HerderTests.cpp | 486 ++--- src/herder/test/TestTxSetUtils.cpp | 83 +- src/herder/test/TestTxSetUtils.h | 4 +- src/herder/test/TransactionQueueTests.cpp | 2 +- src/herder/test/TxSetTests.cpp | 192 +- src/herder/test/UpgradesTests.cpp | 15 +- src/history/test/HistoryTests.cpp | 4 +- src/history/test/HistoryTestsUtils.cpp | 4 +- src/ledger/LedgerManagerImpl.cpp | 252 +-- src/ledger/LedgerManagerImpl.h | 14 +- src/main/ApplicationImpl.cpp | 4 +- src/main/ApplicationImpl.h | 2 +- src/main/main.cpp | 10 +- src/protocol-next/xdr | 2 +- src/test/TxTests.cpp | 6 +- ...ger-close-meta-v1-protocol-22-soroban.json | 1113 +++++----- .../ledger-close-meta-v1-protocol-22.json | 823 ++++---- src/transactions/test/TxEnvelopeTests.cpp | 8 +- src/util/ProtocolVersion.h | 5 +- 32 files changed, 3107 insertions(+), 2341 deletions(-) diff --git a/Builds/VisualStudio/stellar-core.vcxproj b/Builds/VisualStudio/stellar-core.vcxproj index 2c53d25e83..e9b43505cb 100644 --- a/Builds/VisualStudio/stellar-core.vcxproj +++ b/Builds/VisualStudio/stellar-core.vcxproj @@ -602,6 +602,7 @@ exit /b 0 + @@ -1019,6 +1020,7 @@ exit /b 0 + diff --git a/Builds/VisualStudio/stellar-core.vcxproj.filters b/Builds/VisualStudio/stellar-core.vcxproj.filters index aee05a0099..d2174e0530 100644 --- a/Builds/VisualStudio/stellar-core.vcxproj.filters +++ b/Builds/VisualStudio/stellar-core.vcxproj.filters @@ -1344,6 +1344,9 @@ overlay + + overlay + @@ -2339,6 +2342,9 @@ overlay + + overlay + diff --git a/deny.toml b/deny.toml index 3a6457a9c9..f9b3099c73 100644 --- a/deny.toml +++ b/deny.toml @@ -51,7 +51,10 @@ exclude = [ # Somehow the tracking machinery of two different dev-dep tracing # subsystems also winds up pulling in conflicts, but again, just # dev-deps or non-produciton configs. - "tracking-allocator" + "tracking-allocator", + # Temporary exclusion due to signing-related vulnerability (this doesn't + # affect Core as Core doesn't use this for signing) + "curve25519-dalek" ] # If true, metadata will be collected with `--all-features`. Note that this can't diff --git a/src/herder/HerderImpl.cpp b/src/herder/HerderImpl.cpp index c4a475cd8f..f9e4d3fc24 100644 --- a/src/herder/HerderImpl.cpp +++ b/src/herder/HerderImpl.cpp @@ -1305,7 +1305,7 @@ HerderImpl::triggerNextLedger(uint32_t ledgerSeqToTrigger, // our first choice for this round's set is all the tx we have collected // during last few ledger closes auto const& lcl = mLedgerManager.getLastClosedLedgerHeader(); - TxSetPhaseTransactions txPhases; + PerPhaseTransactionList txPhases; txPhases.emplace_back(mTransactionQueue.getTransactions(lcl.header)); if (protocolVersionStartsFrom(lcl.header.ledgerVersion, @@ -1347,7 +1347,7 @@ HerderImpl::triggerNextLedger(uint32_t ledgerSeqToTrigger, upperBoundCloseTimeOffset = nextCloseTime - lcl.header.scpValue.closeTime; lowerBoundCloseTimeOffset = upperBoundCloseTimeOffset; - TxSetPhaseTransactions invalidTxPhases; + PerPhaseTransactionList invalidTxPhases; invalidTxPhases.resize(txPhases.size()); auto [proposedSet, applicableProposedSet] = @@ -2230,8 +2230,7 @@ HerderImpl::updateTransactionQueue(TxSetXDRFrameConstPtr externalizedTxSet) auto invalidTxs = TxSetUtils::getInvalidTxList( txs, mApp, 0, - getUpperBoundCloseTimeOffset(mApp, lhhe.header.scpValue.closeTime), - false); + getUpperBoundCloseTimeOffset(mApp, lhhe.header.scpValue.closeTime)); queue.ban(invalidTxs); queue.rebroadcast(); diff --git a/src/herder/SurgePricingUtils.cpp b/src/herder/SurgePricingUtils.cpp index c337d1a8be..dbbc0c88d9 100644 --- a/src/herder/SurgePricingUtils.cpp +++ b/src/herder/SurgePricingUtils.cpp @@ -162,7 +162,7 @@ SurgePricingPriorityQueue::getMostTopTxsWithinLimits( SurgePricingPriorityQueue queue( /* isHighestPriority */ true, laneConfig, stellar::rand_uniform(0, std::numeric_limits::max())); - for (auto txStack : txStacks) + for (auto const& txStack : txStacks) { queue.add(txStack); } @@ -185,7 +185,7 @@ SurgePricingPriorityQueue::visitTopTxs( { ZoneScoped; - for (auto txStack : txStacks) + for (auto const& txStack : txStacks) { add(txStack); } diff --git a/src/herder/SurgePricingUtils.h b/src/herder/SurgePricingUtils.h index a08e96fb54..6b267b8c43 100644 --- a/src/herder/SurgePricingUtils.h +++ b/src/herder/SurgePricingUtils.h @@ -43,6 +43,48 @@ class TxStack virtual ~TxStack() = default; }; +// A simple stack that holds a single transaction. +class SingleTxStack : public TxStack +{ + public: + SingleTxStack(TransactionFrameBasePtr tx, + bool useByteLimitInClassic = false) + : mTx(tx), mUseByteLimitInClassic(useByteLimitInClassic) + { + } + + TransactionFrameBasePtr + getTopTx() const override + { + releaseAssert(mTx); + return mTx; + } + + void + popTopTx() override + { + releaseAssert(mTx); + mTx = nullptr; + } + + bool + empty() const override + { + return mTx == nullptr; + } + + Resource + getResources() const override + { + releaseAssert(mTx); + return Resource(mTx->getResources(mUseByteLimitInClassic)); + } + + private: + TransactionFrameBasePtr mTx; + bool mUseByteLimitInClassic = false; +}; + using TxStackPtr = std::shared_ptr; // Configuration for multi-lane transaction limiting and surge pricing. diff --git a/src/herder/TransactionQueue.cpp b/src/herder/TransactionQueue.cpp index a9d36806f4..47ebbfe11d 100644 --- a/src/herder/TransactionQueue.cpp +++ b/src/herder/TransactionQueue.cpp @@ -814,11 +814,11 @@ TransactionQueue::isBanned(Hash const& hash) const }); } -TxSetTransactions +TxFrameList TransactionQueue::getTransactions(LedgerHeader const& lcl) const { ZoneScoped; - TxSetTransactions txs; + TxFrameList txs; uint32_t const nextLedgerSeq = lcl.ledgerSeq + 1; int64_t const startingSeq = getStartingSequenceNumber(nextLedgerSeq); diff --git a/src/herder/TransactionQueue.h b/src/herder/TransactionQueue.h index 5375a389f6..a1207dac95 100644 --- a/src/herder/TransactionQueue.h +++ b/src/herder/TransactionQueue.h @@ -125,7 +125,7 @@ class TransactionQueue bool isBanned(Hash const& hash) const; TransactionFrameBaseConstPtr getTx(Hash const& hash) const; - TxSetTransactions getTransactions(LedgerHeader const& lcl) const; + TxFrameList getTransactions(LedgerHeader const& lcl) const; bool sourceAccountPending(AccountID const& accountID) const; virtual size_t getMaxQueueSizeOps() const = 0; diff --git a/src/herder/TxQueueLimiter.h b/src/herder/TxQueueLimiter.h index 78100425b6..b6e722b986 100644 --- a/src/herder/TxQueueLimiter.h +++ b/src/herder/TxQueueLimiter.h @@ -13,44 +13,6 @@ namespace stellar { -class SingleTxStack : public TxStack -{ - public: - SingleTxStack(TransactionFrameBasePtr tx) : mTx(tx) - { - } - - TransactionFrameBasePtr - getTopTx() const override - { - releaseAssert(mTx); - return mTx; - } - - void - popTopTx() override - { - releaseAssert(mTx); - mTx = nullptr; - } - - bool - empty() const override - { - return mTx == nullptr; - } - - Resource - getResources() const override - { - releaseAssert(mTx); - return Resource(mTx->getResources(/* useByteLimitInClassic */ false)); - } - - private: - TransactionFrameBasePtr mTx; -}; - class TxQueueLimiter { // number of ledgers we can pool in memory diff --git a/src/herder/TxSetFrame.cpp b/src/herder/TxSetFrame.cpp index 3a1b324d6a..452d0dec1a 100644 --- a/src/herder/TxSetFrame.cpp +++ b/src/herder/TxSetFrame.cpp @@ -36,17 +36,119 @@ namespace stellar namespace { +std::string +getTxSetPhaseName(TxSetPhase phase) +{ + switch (phase) + { + case TxSetPhase::CLASSIC: + return "classic"; + case TxSetPhase::SOROBAN: + return "soroban"; + default: + throw std::runtime_error("Unknown phase"); + } +} + +bool +validateNonParallelPhaseXDRStructure(TransactionPhase const& phase) +{ + bool componentsNormalized = + std::is_sorted(phase.v0Components().begin(), phase.v0Components().end(), + [](auto const& c1, auto const& c2) { + if (!c1.txsMaybeDiscountedFee().baseFee || + !c2.txsMaybeDiscountedFee().baseFee) + { + return !c1.txsMaybeDiscountedFee().baseFee && + c2.txsMaybeDiscountedFee().baseFee; + } + return *c1.txsMaybeDiscountedFee().baseFee < + *c2.txsMaybeDiscountedFee().baseFee; + }); + if (!componentsNormalized) + { + CLOG_DEBUG(Herder, "Got bad txSet: incorrect component order"); + return false; + } + + bool componentBaseFeesUnique = + std::adjacent_find(phase.v0Components().begin(), + phase.v0Components().end(), + [](auto const& c1, auto const& c2) { + if (!c1.txsMaybeDiscountedFee().baseFee || + !c2.txsMaybeDiscountedFee().baseFee) + { + return !c1.txsMaybeDiscountedFee().baseFee && + !c2.txsMaybeDiscountedFee().baseFee; + } + return *c1.txsMaybeDiscountedFee().baseFee == + *c2.txsMaybeDiscountedFee().baseFee; + }) == phase.v0Components().end(); + if (!componentBaseFeesUnique) + { + CLOG_DEBUG(Herder, "Got bad txSet: duplicate component base fees"); + return false; + } + for (auto const& component : phase.v0Components()) + { + if (component.txsMaybeDiscountedFee().txs.empty()) + { + CLOG_DEBUG(Herder, "Got bad txSet: empty component"); + return false; + } + } + return true; +} + +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION +bool +validateParallelComponent(ParallelTxsComponent const& component) +{ + for (auto const& stage : component.executionStages) + { + if (stage.empty()) + { + CLOG_DEBUG(Herder, "Got bad txSet: empty stage"); + return false; + } + for (auto const& thread : stage) + { + if (thread.empty()) + { + CLOG_DEBUG(Herder, "Got bad txSet: empty thread"); + return false; + } + for (auto const& cluster : thread) + { + if (cluster.empty()) + { + CLOG_DEBUG(Herder, "Got bad txSet: empty cluster"); + return false; + } + } + } + } + return true; +} +#endif + bool validateTxSetXDRStructure(GeneralizedTransactionSet const& txSet) { +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION + uint32_t const MAX_PHASE = 1; +#else + uint32_t const MAX_PHASE = 0; +#endif if (txSet.v() != 1) { CLOG_DEBUG(Herder, "Got bad txSet: unsupported version {}", txSet.v()); return false; } + auto phaseCount = static_cast(TxSetPhase::PHASE_COUNT); auto const& txSetV1 = txSet.v1TxSet(); - // There was no protocol with 1 phase, so checking for 2 phases only - if (txSetV1.phases.size() != static_cast(TxSetPhase::PHASE_COUNT)) + // There was no protocol with 1 phase, so checking for 2 perPhaseTxs only + if (txSetV1.phases.size() != phaseCount) { CLOG_DEBUG(Herder, "Got bad txSet: exactly 2 phases are expected, got {}", @@ -54,62 +156,42 @@ validateTxSetXDRStructure(GeneralizedTransactionSet const& txSet) return false; } - for (auto const& phase : txSetV1.phases) + for (size_t phaseId = 0; phaseId < phaseCount; ++phaseId) { - if (phase.v() != 0) + auto const& phase = txSetV1.phases[phaseId]; + if (phase.v() > MAX_PHASE) { CLOG_DEBUG(Herder, "Got bad txSet: unsupported phase version {}", phase.v()); return false; } - - bool componentsNormalized = std::is_sorted( - phase.v0Components().begin(), phase.v0Components().end(), - [](auto const& c1, auto const& c2) { - if (!c1.txsMaybeDiscountedFee().baseFee || - !c2.txsMaybeDiscountedFee().baseFee) - { - return !c1.txsMaybeDiscountedFee().baseFee && - c2.txsMaybeDiscountedFee().baseFee; - } - return *c1.txsMaybeDiscountedFee().baseFee < - *c2.txsMaybeDiscountedFee().baseFee; - }); - if (!componentsNormalized) +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION + if (phase.v() == 1) { - CLOG_DEBUG(Herder, "Got bad txSet: incorrect component order"); - return false; - } - - bool componentBaseFeesUnique = - std::adjacent_find( - phase.v0Components().begin(), phase.v0Components().end(), - [](auto const& c1, auto const& c2) { - if (!c1.txsMaybeDiscountedFee().baseFee || - !c2.txsMaybeDiscountedFee().baseFee) - { - return !c1.txsMaybeDiscountedFee().baseFee && - !c2.txsMaybeDiscountedFee().baseFee; - } - return *c1.txsMaybeDiscountedFee().baseFee == - *c2.txsMaybeDiscountedFee().baseFee; - }) == phase.v0Components().end(); - if (!componentBaseFeesUnique) - { - CLOG_DEBUG(Herder, "Got bad txSet: duplicate component base fees"); - return false; + if (phaseId != static_cast(TxSetPhase::SOROBAN)) + { + CLOG_DEBUG(Herder, + "Got bad txSet: non-Soroban parallel phase {}", + phase.v()); + return false; + } + if (!validateParallelComponent(phase.parallelTxsComponent())) + { + return false; + } } - for (auto const& component : phase.v0Components()) + else +#endif { - if (component.txsMaybeDiscountedFee().txs.empty()) + if (!validateNonParallelPhaseXDRStructure(phase)) { - CLOG_DEBUG(Herder, "Got bad txSet: empty component"); return false; } } } return true; } + // We want to XOR the tx hash with the set hash. // This way people can't predict the order that txs will be applied in struct ApplyTxSorter @@ -123,14 +205,14 @@ struct ApplyTxSorter operator()(TransactionFrameBasePtr const& tx1, TransactionFrameBasePtr const& tx2) const { - // need to use the hash of whole tx here since multiple txs could have - // the same Contents + // need to use the hash of whole tx here since multiple txs could + // have the same Contents return lessThanXored(tx1->getFullHash(), tx2->getFullHash(), mSetHash); } }; Hash -computeNonGenericTxSetContentsHash(TransactionSet const& xdrTxSet) +computeNonGeneralizedTxSetContentsHash(TransactionSet const& xdrTxSet) { ZoneScoped; SHA256 hasher; @@ -142,8 +224,8 @@ computeNonGenericTxSetContentsHash(TransactionSet const& xdrTxSet) return hasher.finish(); } -// Note: Soroban txs also use this functionality for simplicity, as it's a no-op -// (all Soroban txs have 1 op max) +// Note: Soroban txs also use this functionality for simplicity, as it's a +// no-op (all Soroban txs have 1 op max) int64_t computePerOpFee(TransactionFrameBase const& tx, uint32_t ledgerVersion) { @@ -157,7 +239,7 @@ computePerOpFee(TransactionFrameBase const& tx, uint32_t ledgerVersion) } void -transactionsToTransactionSetXDR(TxSetTransactions const& txs, +transactionsToTransactionSetXDR(TxFrameList const& txs, Hash const& previousLedgerHash, TransactionSet& txSet) { @@ -171,63 +253,431 @@ transactionsToTransactionSetXDR(TxSetTransactions const& txs, txSet.previousLedgerHash = previousLedgerHash; } +void +nonParallelPhaseToXdr(TxFrameList const& txs, + InclusionFeeMap const& inclusionFeeMap, + TransactionPhase& xdrPhase) +{ + xdrPhase.v(0); + + std::map, size_t> feeTxCount; + for (auto const& [_, fee] : inclusionFeeMap) + { + ++feeTxCount[fee]; + } + auto& components = xdrPhase.v0Components(); + // Reserve a component per unique base fee in order to have the correct + // pointers in componentPerBid map. + components.reserve(feeTxCount.size()); + + std::map, xdr::xvector*> + componentPerBid; + for (auto const& [fee, txCount] : feeTxCount) + { + components.emplace_back(TXSET_COMP_TXS_MAYBE_DISCOUNTED_FEE); + auto& discountedFeeComponent = + components.back().txsMaybeDiscountedFee(); + if (fee) + { + discountedFeeComponent.baseFee.activate() = *fee; + } + componentPerBid[fee] = &discountedFeeComponent.txs; + componentPerBid[fee]->reserve(txCount); + } + auto sortedTxs = TxSetUtils::sortTxsInHashOrder(txs); + for (auto const& tx : sortedTxs) + { + componentPerBid[inclusionFeeMap.find(tx)->second]->push_back( + tx->getEnvelope()); + } +} + +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION +void +parallelPhaseToXdr(TxStageFrameList const& txs, + InclusionFeeMap const& inclusionFeeMap, + TransactionPhase& xdrPhase) +{ + xdrPhase.v(1); + + std::optional baseFee; + if (!inclusionFeeMap.empty()) + { + baseFee = inclusionFeeMap.begin()->second; + } + // We currently don't support multi-component parallel perPhaseTxs, so make + // sure all txs have the same base fee. + for (auto const& [_, fee] : inclusionFeeMap) + { + releaseAssert(fee == baseFee); + } + auto& component = xdrPhase.parallelTxsComponent(); + if (baseFee) + { + component.baseFee.activate() = *baseFee; + } + component.executionStages.reserve(txs.size()); + auto sortedTxs = TxSetUtils::sortParallelTxsInHashOrder(txs); + for (auto const& stage : sortedTxs) + { + auto& xdrStage = component.executionStages.emplace_back(); + xdrStage.reserve(stage.size()); + for (auto const& thread : stage) + { + auto& xdrThread = xdrStage.emplace_back(); + xdrThread.reserve(thread.size()); + for (auto const& cluster : thread) + { + auto& xdrCluster = xdrThread.emplace_back(); + xdrCluster.reserve(cluster.size()); + for (auto const& tx : cluster) + { + xdrCluster.push_back(tx->getEnvelope()); + } + } + } + } +} + +#endif + void transactionsToGeneralizedTransactionSetXDR( - TxSetPhaseTransactions const& phaseTxs, - std::vector>> const& - phaseInclusionFeeMap, - Hash const& previousLedgerHash, GeneralizedTransactionSet& generalizedTxSet) + std::vector const& phases, Hash const& previousLedgerHash, + GeneralizedTransactionSet& generalizedTxSet) { ZoneScoped; - releaseAssert(phaseTxs.size() == phaseInclusionFeeMap.size()); generalizedTxSet.v(1); generalizedTxSet.v1TxSet().previousLedgerHash = previousLedgerHash; + generalizedTxSet.v1TxSet().phases.resize(phases.size()); + for (int i = 0; i < phases.size(); ++i) + { + auto const& txPhase = phases[i]; + txPhase.toXDR(generalizedTxSet.v1TxSet().phases[i]); + } +} - for (int i = 0; i < phaseTxs.size(); ++i) +TxFrameList +sortedForApplyNonParallel(TxFrameList const& txs, Hash const& txSetHash) +{ + TxFrameList retList; + retList.reserve(txs.size()); + + auto txQueues = TxSetUtils::buildAccountTxQueues(txs); + + // build txBatches + // txBatches i-th element contains each i-th transaction for + // accounts with a transaction in the transaction set + std::vector> txBatches; + + while (!txQueues.empty()) { - auto const& txPhase = phaseTxs[i]; - auto& phase = - generalizedTxSet.v1TxSet().phases.emplace_back().v0Components(); + txBatches.emplace_back(); + auto& curBatch = txBatches.back(); + // go over all users that still have transactions + for (auto it = txQueues.begin(); it != txQueues.end();) + { + auto& txQueue = *it; + curBatch.emplace_back(txQueue->getTopTx()); + txQueue->popTopTx(); + if (txQueue->empty()) + { + // done with that user + it = txQueues.erase(it); + } + else + { + ++it; + } + } + } - auto const& feeMap = phaseInclusionFeeMap[i]; - std::map, size_t> feeTxCount; - for (auto const& [tx, fee] : feeMap) + for (auto& batch : txBatches) + { + // randomize each batch using the hash of the transaction set + // as a way to randomize even more + ApplyTxSorter s(txSetHash); + std::sort(batch.begin(), batch.end(), s); + for (auto const& tx : batch) { - ++feeTxCount[fee]; + retList.push_back(tx); } - // Reserve a component per unique base fee in order to have the correct - // pointers in componentPerBid map. - phase.reserve(feeTxCount.size()); + } - std::map, xdr::xvector*> - componentPerBid; - for (auto const& [fee, txCount] : feeTxCount) + return retList; +} + +TxStageFrameList +sortedForApplyParallel(TxStageFrameList const& stages, Hash const& txSetHash) +{ + ZoneScoped; + TxStageFrameList sortedStages = stages; + ApplyTxSorter sorter(txSetHash); + for (auto& stage : sortedStages) + { + for (auto& thread : stage) { - phase.emplace_back(TXSET_COMP_TXS_MAYBE_DISCOUNTED_FEE); - auto& discountedFeeComponent = phase.back().txsMaybeDiscountedFee(); - if (fee) + for (auto& cluster : thread) { - discountedFeeComponent.baseFee.activate() = *fee; + std::sort(cluster.begin(), cluster.end(), sorter); } - componentPerBid[fee] = &discountedFeeComponent.txs; - componentPerBid[fee]->reserve(txCount); + // There is no need to shuffle threads, as they are independent, + // so the apply order doesn't matter even if the threads are + // being applied sequentially. + } + std::sort( + stage.begin(), stage.end(), + [&sorter]( + std::vector> const& a, + std::vector> const& b) { + releaseAssert(!a.empty() && !b.empty()); + releaseAssert(!a.front().empty() && !b.front().empty()); + return sorter(a.front().front(), b.front().front()); + }); + } + return stages; +} + +// This assumes that the phase validation has already been done, +// specifically that there are no transactions that belong to the same +// source account, and that the ledger sequence corresponds to the +bool +phaseTxsAreValid(TxSetPhaseFrame const& phase, Application& app, + uint64_t lowerBoundCloseTimeOffset, + uint64_t upperBoundCloseTimeOffset) +{ + ZoneScoped; +#ifdef BUILD_TESTS + + if (protocolVersionIsBefore(app.getLedgerManager() + .getLastClosedLedgerHeader() + .header.ledgerVersion, + SOROBAN_PROTOCOL_VERSION)) + { + auto invalidTxs = TxSetUtils::getInvalidTxList( + phase.getNonParallelTxs(), app, lowerBoundCloseTimeOffset, + upperBoundCloseTimeOffset); + return invalidTxs.empty(); + } +#endif + + LedgerTxn ltx(app.getLedgerTxnRoot(), + /* shouldUpdateLastModified */ false, + TransactionMode::READ_ONLY_WITHOUT_SQL_TXN); + UnorderedMap accountFeeMap; + for (auto const& tx : phase) + { + if (!tx->checkValid(app, ltx, 0, lowerBoundCloseTimeOffset, + upperBoundCloseTimeOffset)) + { + + CLOG_DEBUG( + Herder, "Got bad txSet: tx invalid tx: {} result: {}", + xdrToCerealString(tx->getEnvelope(), "TransactionEnvelope"), + tx->getResultCode()); + return false; + } + auto feeSource = stellar::loadAccount(ltx, tx->getFeeSourceID()); + if (getAvailableBalance(ltx.loadHeader(), feeSource) < tx->getFullFee()) + { + + CLOG_DEBUG( + Herder, "Got bad txSet: account can't pay fee tx: {}", + xdrToCerealString(tx->getEnvelope(), "TransactionEnvelope")); + return false; + } + } + return true; +} + +bool +addWireTxsToList(Hash const& networkID, + xdr::xvector const& xdrTxs, + TxFrameList& txList) +{ + auto prevSize = txList.size(); + txList.reserve(prevSize + xdrTxs.size()); + for (auto const& env : xdrTxs) + { + auto tx = TransactionFrameBase::makeTransactionFromWire(networkID, env); + if (!tx->XDRProvidesValidFee()) + { + return false; + } + txList.push_back(tx); + } + if (!std::is_sorted(txList.begin() + prevSize, txList.end(), + &TxSetUtils::hashTxSorter)) + { + return false; + } + return true; +} + +std::vector +computeLaneBaseFee(TxSetPhase phase, LedgerHeader const& ledgerHeader, + SurgePricingLaneConfig const& surgePricingConfig, + std::vector const& lowestLaneFee, + std::vector const& hadTxNotFittingLane) +{ + std::vector laneBaseFee(lowestLaneFee.size(), + ledgerHeader.baseFee); + auto minBaseFee = + *std::min_element(lowestLaneFee.begin(), lowestLaneFee.end()); + for (size_t lane = 0; lane < laneBaseFee.size(); ++lane) + { + // If generic lane is full, then any transaction had to compete with not + // included transactions and independently of the lane they need to have + // at least the minimum fee in the tx set applied. + if (hadTxNotFittingLane[SurgePricingPriorityQueue::GENERIC_LANE]) + { + laneBaseFee[lane] = minBaseFee; + } + // If limited lane is full, then the transactions in this lane also had + // to compete with each other and have a base fee associated with this + // lane only. + if (lane != SurgePricingPriorityQueue::GENERIC_LANE && + hadTxNotFittingLane[lane]) + { + laneBaseFee[lane] = lowestLaneFee[lane]; + } + if (laneBaseFee[lane] > ledgerHeader.baseFee) + { + CLOG_WARNING( + Herder, + "{} phase: surge pricing for '{}' lane is in effect with base " + "fee={}, baseFee={}", + getTxSetPhaseName(phase), + lane == SurgePricingPriorityQueue::GENERIC_LANE ? "generic" + : "DEX", + laneBaseFee[lane], ledgerHeader.baseFee); + } + } + return laneBaseFee; +} + +std::pair> +applySurgePricing(TxSetPhase phase, TxFrameList const& txs, Application& app) +{ + ZoneScoped; + + auto const& lclHeader = + app.getLedgerManager().getLastClosedLedgerHeader().header; + + std::vector txStacks; + txStacks.reserve(txs.size()); + std::vector lowestLaneFee; + for (auto const& tx : txs) + { + txStacks.push_back(std::make_shared( + tx, /* useByteLimitInClassic */ true)); + } + std::vector hadTxNotFittingLane; + std::shared_ptr surgePricingLaneConfig; + if (phase == TxSetPhase::CLASSIC) + { + auto maxOps = + Resource({static_cast( + app.getLedgerManager().getLastMaxTxSetSizeOps()), + MAX_CLASSIC_BYTE_ALLOWANCE}); + std::optional dexOpsLimit; + if (app.getConfig().MAX_DEX_TX_OPERATIONS_IN_TX_SET) + { + // DEX operations limit implies that DEX transactions should + // compete with each other in in a separate fee lane, which + // is only possible with generalized tx set. + dexOpsLimit = + Resource({*app.getConfig().MAX_DEX_TX_OPERATIONS_IN_TX_SET, + MAX_CLASSIC_BYTE_ALLOWANCE}); + } + + surgePricingLaneConfig = + std::make_shared(maxOps, dexOpsLimit); + } + else + { + releaseAssert(phase == TxSetPhase::SOROBAN); + + auto limits = app.getLedgerManager().maxLedgerResources( + /* isSoroban */ true); + + auto byteLimit = + std::min(static_cast(MAX_SOROBAN_BYTE_ALLOWANCE), + limits.getVal(Resource::Type::TX_BYTE_SIZE)); + limits.setVal(Resource::Type::TX_BYTE_SIZE, byteLimit); + + surgePricingLaneConfig = + std::make_shared(limits); + } + auto includedTxs = SurgePricingPriorityQueue::getMostTopTxsWithinLimits( + txStacks, surgePricingLaneConfig, hadTxNotFittingLane); + + size_t laneCount = surgePricingLaneConfig->getLaneLimits().size(); + lowestLaneFee.resize(laneCount, std::numeric_limits::max()); + for (auto const& tx : includedTxs) + { + size_t lane = surgePricingLaneConfig->getLane(*tx); + auto perOpFee = computePerOpFee(*tx, lclHeader.ledgerVersion); + lowestLaneFee[lane] = std::min(lowestLaneFee[lane], perOpFee); + } + auto laneBaseFee = + computeLaneBaseFee(phase, lclHeader, *surgePricingLaneConfig, + lowestLaneFee, hadTxNotFittingLane); + auto inclusionFeeMapPtr = std::make_shared(); + auto& inclusionFeeMap = *inclusionFeeMapPtr; + for (auto const& tx : includedTxs) + { + inclusionFeeMap[tx] = laneBaseFee[surgePricingLaneConfig->getLane(*tx)]; + } + + return std::make_pair(includedTxs, inclusionFeeMapPtr); +} + +size_t +countOps(TxFrameList const& txs) +{ + return std::accumulate(txs.begin(), txs.end(), size_t(0), + [&](size_t a, TransactionFrameBasePtr const& tx) { + return a + tx->getNumOperations(); + }); +} + +int64_t +computeBaseFeeForLegacyTxSet(LedgerHeader const& lclHeader, + TxFrameList const& txs) +{ + ZoneScoped; + auto ledgerVersion = lclHeader.ledgerVersion; + int64_t lowestBaseFee = std::numeric_limits::max(); + for (auto const& tx : txs) + { + int64_t txBaseFee = computePerOpFee(*tx, ledgerVersion); + lowestBaseFee = std::min(lowestBaseFee, txBaseFee); + } + int64_t baseFee = lclHeader.baseFee; + + if (protocolVersionStartsFrom(ledgerVersion, ProtocolVersion::V_11)) + { + size_t surgeOpsCutoff = 0; + if (lclHeader.maxTxSetSize >= MAX_OPS_PER_TX) + { + surgeOpsCutoff = lclHeader.maxTxSetSize - MAX_OPS_PER_TX; } - auto sortedTxs = TxSetUtils::sortTxsInHashOrder(txPhase); - for (auto const& tx : sortedTxs) + if (countOps(txs) > surgeOpsCutoff) { - componentPerBid[feeMap.find(tx)->second]->push_back( - tx->getEnvelope()); + baseFee = lowestBaseFee; } } + return baseFee; } + } // namespace TxSetXDRFrame::TxSetXDRFrame(TransactionSet const& xdrTxSet) : mXDRTxSet(xdrTxSet) , mEncodedSize(xdr::xdr_argpack_size(xdrTxSet)) - , mHash(computeNonGenericTxSetContentsHash(xdrTxSet)) + , mHash(computeNonGeneralizedTxSetContentsHash(xdrTxSet)) { } @@ -265,7 +715,7 @@ TxSetXDRFrame::makeFromStoredTxSet(StoredTransactionSet const& storedSet) } std::pair -makeTxSetFromTransactions(TxSetPhaseTransactions const& txPhases, +makeTxSetFromTransactions(PerPhaseTransactionList const& txPhases, Application& app, uint64_t lowerBoundCloseTimeOffset, uint64_t upperBoundCloseTimeOffset #ifdef BUILD_TESTS @@ -274,7 +724,7 @@ makeTxSetFromTransactions(TxSetPhaseTransactions const& txPhases, #endif ) { - TxSetPhaseTransactions invalidTxs; + PerPhaseTransactionList invalidTxs; invalidTxs.resize(txPhases.size()); return makeTxSetFromTransactions(txPhases, app, lowerBoundCloseTimeOffset, upperBoundCloseTimeOffset, invalidTxs @@ -286,10 +736,10 @@ makeTxSetFromTransactions(TxSetPhaseTransactions const& txPhases, } std::pair -makeTxSetFromTransactions(TxSetPhaseTransactions const& txPhases, +makeTxSetFromTransactions(PerPhaseTransactionList const& txPhases, Application& app, uint64_t lowerBoundCloseTimeOffset, uint64_t upperBoundCloseTimeOffset, - TxSetPhaseTransactions& invalidTxs + PerPhaseTransactionList& invalidTxs #ifdef BUILD_TESTS , bool skipValidation @@ -300,12 +750,12 @@ makeTxSetFromTransactions(TxSetPhaseTransactions const& txPhases, releaseAssert(txPhases.size() <= static_cast(TxSetPhase::PHASE_COUNT)); - TxSetPhaseTransactions validatedPhases; + std::vector validatedPhases; for (int i = 0; i < txPhases.size(); ++i) { - auto& txs = txPhases[i]; + auto const& phaseTxs = txPhases[i]; bool expectSoroban = static_cast(i) == TxSetPhase::SOROBAN; - if (!std::all_of(txs.begin(), txs.end(), [&](auto const& tx) { + if (!std::all_of(phaseTxs.begin(), phaseTxs.end(), [&](auto const& tx) { return tx->isSoroban() == expectSoroban; })) { @@ -314,20 +764,45 @@ makeTxSetFromTransactions(TxSetPhaseTransactions const& txPhases, } auto& invalid = invalidTxs[i]; + TxFrameList validatedTxs; #ifdef BUILD_TESTS if (skipValidation) { - validatedPhases.emplace_back(txs); + validatedTxs = phaseTxs; } else { #endif - validatedPhases.emplace_back( - TxSetUtils::trimInvalid(txs, app, lowerBoundCloseTimeOffset, - upperBoundCloseTimeOffset, invalid)); + validatedTxs = TxSetUtils::trimInvalid( + phaseTxs, app, lowerBoundCloseTimeOffset, + upperBoundCloseTimeOffset, invalid); #ifdef BUILD_TESTS } #endif + auto phaseType = static_cast(i); + auto [includedTxs, inclusionFeeMap] = + applySurgePricing(phaseType, validatedTxs, app); + if (phaseType != TxSetPhase::SOROBAN || + protocolVersionIsBefore(app.getLedgerManager() + .getLastClosedLedgerHeader() + .header.ledgerVersion, + PARALLEL_SOROBAN_PHASE_PROTOCOL_VERSION)) + { + validatedPhases.emplace_back( + TxSetPhaseFrame(std::move(includedTxs), inclusionFeeMap)); + } + // This is a temporary stub for building a valid parallel tx set + // without any parallelization. + else + { + TxStageFrameList stages; + if (!includedTxs.empty()) + { + stages.emplace_back().emplace_back().push_back(includedTxs); + } + validatedPhases.emplace_back( + TxSetPhaseFrame(std::move(stages), inclusionFeeMap)); + } } auto const& lclHeader = app.getLedgerManager().getLastClosedLedgerHeader(); @@ -336,7 +811,7 @@ makeTxSetFromTransactions(TxSetPhaseTransactions const& txPhases, std::unique_ptr preliminaryApplicableTxSet( new ApplicableTxSetFrame(app, lclHeader, validatedPhases, std::nullopt)); - preliminaryApplicableTxSet->applySurgePricing(app); + // Do the roundtrip through XDR to ensure we never build an incorrect tx set // for nomination. auto outputTxSet = preliminaryApplicableTxSet->toWireTxSetFrame(); @@ -393,14 +868,21 @@ TxSetXDRFrame::makeEmpty(LedgerHeaderHistoryEntry const& lclHeader) if (protocolVersionStartsFrom(lclHeader.header.ledgerVersion, SOROBAN_PROTOCOL_VERSION)) { - TxSetPhaseTransactions emptyPhases( - static_cast(TxSetPhase::PHASE_COUNT)); - std::vector>> - emptyFeeMap(static_cast(TxSetPhase::PHASE_COUNT)); + std::vector emptyPhases( + static_cast(TxSetPhase::PHASE_COUNT), + TxSetPhaseFrame::makeEmpty(false)); +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION + if (protocolVersionStartsFrom(lclHeader.header.ledgerVersion, + PARALLEL_SOROBAN_PHASE_PROTOCOL_VERSION)) + { + emptyPhases[static_cast(TxSetPhase::SOROBAN)] = + TxSetPhaseFrame::makeEmpty(true); + } +#endif + GeneralizedTransactionSet txSet; - transactionsToGeneralizedTransactionSetXDR(emptyPhases, emptyFeeMap, - lclHeader.hash, txSet); + transactionsToGeneralizedTransactionSetXDR(emptyPhases, lclHeader.hash, + txSet); return TxSetXDRFrame::makeFromWire(txSet); } TransactionSet txSet; @@ -410,7 +892,7 @@ TxSetXDRFrame::makeEmpty(LedgerHeaderHistoryEntry const& lclHeader) TxSetXDRFrameConstPtr TxSetXDRFrame::makeFromHistoryTransactions(Hash const& previousLedgerHash, - TxSetTransactions const& txs) + TxFrameList const& txs) { TransactionSet txSet; transactionsToTransactionSetXDR(txs, previousLedgerHash, txSet); @@ -419,49 +901,58 @@ TxSetXDRFrame::makeFromHistoryTransactions(Hash const& previousLedgerHash, #ifdef BUILD_TESTS std::pair -makeTxSetFromTransactions(TxSetTransactions txs, Application& app, +makeTxSetFromTransactions(TxFrameList txs, Application& app, uint64_t lowerBoundCloseTimeOffset, uint64_t upperBoundCloseTimeOffset, bool enforceTxsApplyOrder) { - TxSetTransactions invalid; + TxFrameList invalid; return makeTxSetFromTransactions(txs, app, lowerBoundCloseTimeOffset, upperBoundCloseTimeOffset, invalid, enforceTxsApplyOrder); } std::pair -makeTxSetFromTransactions(TxSetTransactions txs, Application& app, +makeTxSetFromTransactions(TxFrameList txs, Application& app, uint64_t lowerBoundCloseTimeOffset, uint64_t upperBoundCloseTimeOffset, - TxSetTransactions& invalidTxs, - bool enforceTxsApplyOrder) + TxFrameList& invalidTxs, bool enforceTxsApplyOrder) { auto lclHeader = app.getLedgerManager().getLastClosedLedgerHeader(); - TxSetPhaseTransactions phases; - phases.resize(protocolVersionStartsFrom(lclHeader.header.ledgerVersion, - SOROBAN_PROTOCOL_VERSION) - ? 2 - : 1); + PerPhaseTransactionList perPhaseTxs; + perPhaseTxs.resize(protocolVersionStartsFrom(lclHeader.header.ledgerVersion, + SOROBAN_PROTOCOL_VERSION) + ? 2 + : 1); for (auto& tx : txs) { if (tx->isSoroban()) { - phases[static_cast(TxSetPhase::SOROBAN)].push_back(tx); + perPhaseTxs[static_cast(TxSetPhase::SOROBAN)].push_back(tx); } else { - phases[static_cast(TxSetPhase::CLASSIC)].push_back(tx); + perPhaseTxs[static_cast(TxSetPhase::CLASSIC)].push_back(tx); } } - TxSetPhaseTransactions invalid; - invalid.resize(phases.size()); - auto res = makeTxSetFromTransactions(phases, app, lowerBoundCloseTimeOffset, - upperBoundCloseTimeOffset, invalid, - enforceTxsApplyOrder); + PerPhaseTransactionList invalid; + invalid.resize(perPhaseTxs.size()); + auto res = makeTxSetFromTransactions( + perPhaseTxs, app, lowerBoundCloseTimeOffset, upperBoundCloseTimeOffset, + invalid, enforceTxsApplyOrder); if (enforceTxsApplyOrder) { - res.second->mApplyOrderOverride = txs; + auto const& resPhases = res.second->getPhases(); + // This only supports non-parallel tx sets for now. + std::vector overridePhases; + for (auto i = 0; i < resPhases.size(); ++i) + { + overridePhases.emplace_back( + TxSetPhaseFrame(std::move(perPhaseTxs[i]), + std::make_shared( + resPhases[i].getInclusionFeeMap()))); + } + res.second->mApplyOrderPhases = overridePhases; res.first->mApplicableTxSetOverride = std::move(res.second); } invalidTxs = invalid[0]; @@ -498,7 +989,7 @@ TxSetXDRFrame::prepareForApply(Application& app) const } #endif ZoneScoped; - std::unique_ptr txSet{}; + std::vector phaseFrames; if (isGeneralizedTxSet()) { auto const& xdrTxSet = std::get(mXDRTxSet); @@ -508,46 +999,29 @@ TxSetXDRFrame::prepareForApply(Application& app) const "Got bad generalized txSet with invalid XDR structure"); return nullptr; } - auto const& phases = xdrTxSet.v1TxSet().phases; - TxSetPhaseTransactions defaultPhases; - defaultPhases.resize(phases.size()); - - txSet = std::unique_ptr(new ApplicableTxSetFrame( - app, true, previousLedgerHash(), defaultPhases, mHash)); - // Mark fees as already computed as we read them from the XDR. - for (int i = 0; i < txSet->mFeesComputed.size(); i++) + auto const& xdrPhases = xdrTxSet.v1TxSet().phases; + + for (auto const& xdrPhase : xdrPhases) { - txSet->mFeesComputed[i] = true; + auto maybePhase = + TxSetPhaseFrame::makeFromWire(app.getNetworkID(), xdrPhase); + if (!maybePhase) + { + return nullptr; + } + phaseFrames.emplace_back(std::move(*maybePhase)); } - - releaseAssert(phases.size() <= - static_cast(TxSetPhase::PHASE_COUNT)); - for (auto phaseId = 0; phaseId < phases.size(); phaseId++) + for (size_t phaseId = 0; phaseId < phaseFrames.size(); ++phaseId) { - auto const& phase = phases[phaseId]; - auto const& components = phase.v0Components(); - for (auto const& component : components) + auto phase = static_cast(phaseId); + for (auto const& tx : phaseFrames[phaseId]) { - switch (component.type()) + if ((tx->isSoroban() && phase != TxSetPhase::SOROBAN) || + (!tx->isSoroban() && phase != TxSetPhase::CLASSIC)) { - case TXSET_COMP_TXS_MAYBE_DISCOUNTED_FEE: - std::optional baseFee; - if (component.txsMaybeDiscountedFee().baseFee) - { - baseFee = *component.txsMaybeDiscountedFee().baseFee; - } - if (!txSet->addTxsFromXdr( - app.getNetworkID(), - component.txsMaybeDiscountedFee().txs, true, - baseFee, static_cast(phaseId))) - { - CLOG_DEBUG(Herder, - "Got bad generalized txSet: transactions " - "are not ordered correctly or contain " - "invalid phase transactions"); - return nullptr; - } - break; + CLOG_DEBUG(Herder, "Got bad generalized txSet with invalid " + "phase transactions"); + return nullptr; } } } @@ -555,18 +1029,17 @@ TxSetXDRFrame::prepareForApply(Application& app) const else { auto const& xdrTxSet = std::get(mXDRTxSet); - txSet = std::unique_ptr(new ApplicableTxSetFrame( - app, false, previousLedgerHash(), {TxSetTransactions{}}, mHash)); - if (!txSet->addTxsFromXdr(app.getNetworkID(), xdrTxSet.txs, false, - std::nullopt, TxSetPhase::CLASSIC)) + auto maybePhase = TxSetPhaseFrame::makeFromWireLegacy( + app.getLedgerManager().getLastClosedLedgerHeader().header, + app.getNetworkID(), xdrTxSet.txs); + if (!maybePhase) { - CLOG_DEBUG(Herder, - "Got bad txSet: transactions are not ordered correctly " - "or contain invalid phase transactions"); return nullptr; } + phaseFrames.emplace_back(std::move(*maybePhase)); } - return txSet; + return std::unique_ptr(new ApplicableTxSetFrame( + app, isGeneralizedTxSet(), previousLedgerHash(), phaseFrames, mHash)); } bool @@ -603,9 +1076,31 @@ TxSetXDRFrame::sizeTxTotal() const size_t totalSize = 0; for (auto const& phase : txSet.phases) { - for (auto const& component : phase.v0Components()) + switch (phase.v()) { - totalSize += component.txsMaybeDiscountedFee().txs.size(); + case 0: + for (auto const& component : phase.v0Components()) + { + totalSize += component.txsMaybeDiscountedFee().txs.size(); + } + break; +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION + case 1: + for (auto const& stage : + phase.parallelTxsComponent().executionStages) + { + for (auto const& thread : stage) + { + for (auto const& cluster : thread) + { + totalSize += cluster.size(); + } + } + } + break; +#endif + default: + break; } } return totalSize; @@ -644,12 +1139,36 @@ TxSetXDRFrame::sizeOpTotalForLogging() const size_t totalSize = 0; for (auto const& phase : txSet.phases) { - for (auto const& component : phase.v0Components()) + switch (phase.v()) { - totalSize += std::accumulate( - component.txsMaybeDiscountedFee().txs.begin(), - component.txsMaybeDiscountedFee().txs.end(), 0ull, - accumulateTxsFn); + case 0: + for (auto const& component : phase.v0Components()) + { + totalSize += std::accumulate( + component.txsMaybeDiscountedFee().txs.begin(), + component.txsMaybeDiscountedFee().txs.end(), 0ull, + accumulateTxsFn); + } + break; +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION + case 1: + for (auto const& stage : + phase.parallelTxsComponent().executionStages) + { + for (auto const& thread : stage) + { + for (auto const& cluster : thread) + { + totalSize += + std::accumulate(cluster.begin(), cluster.end(), + 0ull, accumulateTxsFn); + } + } + } + break; +#endif + default: + break; } } return totalSize; @@ -661,10 +1180,10 @@ TxSetXDRFrame::sizeOpTotalForLogging() const } } -TxSetPhaseTransactions +PerPhaseTransactionList TxSetXDRFrame::createTransactionFrames(Hash const& networkID) const { - TxSetPhaseTransactions phaseTxs; + PerPhaseTransactionList phaseTxs; if (isGeneralizedTxSet()) { auto const& txSet = @@ -672,14 +1191,41 @@ TxSetXDRFrame::createTransactionFrames(Hash const& networkID) const for (auto const& phase : txSet.phases) { auto& txs = phaseTxs.emplace_back(); - for (auto const& component : phase.v0Components()) + switch (phase.v()) { - for (auto const& tx : component.txsMaybeDiscountedFee().txs) + case 0: + for (auto const& component : phase.v0Components()) + { + for (auto const& tx : component.txsMaybeDiscountedFee().txs) + { + txs.emplace_back( + TransactionFrameBase::makeTransactionFromWire( + networkID, tx)); + } + } + break; +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION + case 1: + for (auto const& stage : + phase.parallelTxsComponent().executionStages) { - txs.emplace_back( - TransactionFrameBase::makeTransactionFromWire(networkID, - tx)); + for (auto const& thread : stage) + { + for (auto const& cluster : thread) + { + for (auto const& tx : cluster) + { + txs.emplace_back( + TransactionFrameBase:: + makeTransactionFromWire(networkID, tx)); + } + } + } } + break; +#endif + default: + break; } } } @@ -696,51 +1242,465 @@ TxSetXDRFrame::createTransactionFrames(Hash const& networkID) const return phaseTxs; } -size_t -TxSetXDRFrame::encodedSize() const +size_t +TxSetXDRFrame::encodedSize() const +{ + return mEncodedSize; +} + +void +TxSetXDRFrame::toXDR(TransactionSet& txSet) const +{ + releaseAssert(!isGeneralizedTxSet()); + txSet = std::get(mXDRTxSet); +} + +void +TxSetXDRFrame::toXDR(GeneralizedTransactionSet& txSet) const +{ + releaseAssert(isGeneralizedTxSet()); + txSet = std::get(mXDRTxSet); +} + +void +TxSetXDRFrame::storeXDR(StoredTransactionSet& txSet) const +{ + if (isGeneralizedTxSet()) + { + txSet.v(1); + txSet.generalizedTxSet() = + std::get(mXDRTxSet); + } + else + { + txSet.v(0); + txSet.txSet() = std::get(mXDRTxSet); + } +} + +TxSetPhaseFrame::Iterator::Iterator( + std::variant const& txs, size_t stageIndex, + size_t txIndex) + : mTxs(txs), mStageIndex(stageIndex), mTxIndex(txIndex) +{ +} + +TransactionFrameBasePtr +TxSetPhaseFrame::Iterator::operator*() const +{ + return std::visit( + [this](auto const& txs) { + using T = std::decay_t; + if constexpr (std::is_same_v) + { + if (mTxIndex >= txs.size()) + { + throw std::runtime_error("TxPhase iterator out of bounds"); + } + return txs[mTxIndex]; + } + else if constexpr (std::is_same_v) + { + if (mStageIndex >= txs.size() || + mThreadIndex >= txs[mStageIndex].size() || + mClusterIndex >= txs[mStageIndex][mThreadIndex].size() || + mTxIndex >= + txs[mStageIndex][mThreadIndex][mClusterIndex].size()) + { + throw std::runtime_error("TxPhase iterator out of bounds"); + } + return txs[mStageIndex][mThreadIndex][mClusterIndex][mTxIndex]; + } + else + { + throw std::runtime_error("unexpected TxPhase iterator variant"); + return nullptr; + } + }, + mTxs); +} + +TxSetPhaseFrame::Iterator& +TxSetPhaseFrame::Iterator::operator++() +{ + std::visit( + [this](auto const& txs) { + using T = std::decay_t; + if constexpr (std::is_same_v) + { + if (mTxIndex >= txs.size()) + { + throw std::runtime_error("TxPhase iterator out of bounds"); + } + ++mTxIndex; + } + else if constexpr (std::is_same_v) + { + if (mStageIndex >= txs.size()) + { + throw std::runtime_error("TxPhase iterator out of bounds"); + } + ++mTxIndex; + if (mTxIndex >= + txs[mStageIndex][mThreadIndex][mClusterIndex].size()) + { + mTxIndex = 0; + ++mClusterIndex; + if (mClusterIndex >= txs[mStageIndex][mThreadIndex].size()) + { + mClusterIndex = 0; + ++mThreadIndex; + if (mThreadIndex >= txs[mStageIndex].size()) + { + mThreadIndex = 0; + ++mStageIndex; + } + } + } + } + else + { + throw std::runtime_error("unexpected TxPhase iterator variant"); + } + }, + mTxs); + return *this; +} + +TxSetPhaseFrame::Iterator +TxSetPhaseFrame::Iterator::operator++(int) +{ + auto it = *this; + ++(*this); + return it; +} + +bool +TxSetPhaseFrame::Iterator::operator==(Iterator const& other) const +{ + return mStageIndex == other.mStageIndex && + mThreadIndex == other.mThreadIndex && mTxIndex == other.mTxIndex && + // Make sure to compare the pointers, not the contents, both for + // correctness and optimization. + &mTxs == &other.mTxs; +} + +bool +TxSetPhaseFrame::Iterator::operator!=(Iterator const& other) const +{ + return !(*this == other); +} + +std::optional +TxSetPhaseFrame::makeFromWire(Hash const& networkID, + TransactionPhase const& xdrPhase) +{ + auto inclusionFeeMapPtr = std::make_shared(); + auto& inclusionFeeMap = *inclusionFeeMapPtr; + switch (xdrPhase.v()) + { + case 0: + { + TxFrameList txList; + auto const& components = xdrPhase.v0Components(); + for (auto const& component : components) + { + switch (component.type()) + { + case TXSET_COMP_TXS_MAYBE_DISCOUNTED_FEE: + std::optional baseFee; + if (component.txsMaybeDiscountedFee().baseFee) + { + baseFee = *component.txsMaybeDiscountedFee().baseFee; + } + size_t prevSize = txList.size(); + if (!addWireTxsToList(networkID, + component.txsMaybeDiscountedFee().txs, + txList)) + { + CLOG_DEBUG(Herder, + "Got bad generalized txSet: transactions " + "are not ordered correctly or contain " + "invalid transactions"); + return std::nullopt; + } + for (auto it = txList.begin() + prevSize; it != txList.end(); + ++it) + { + inclusionFeeMap[*it] = baseFee; + } + break; + } + } + return TxSetPhaseFrame(std::move(txList), inclusionFeeMapPtr); + } +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION + case 1: + { + auto const& xdrStages = xdrPhase.parallelTxsComponent().executionStages; + std::optional baseFee; + if (xdrPhase.parallelTxsComponent().baseFee) + { + baseFee = *xdrPhase.parallelTxsComponent().baseFee; + } + TxStageFrameList stages; + stages.reserve(xdrStages.size()); + for (auto const& xdrStage : xdrStages) + { + auto& stage = stages.emplace_back(); + stage.reserve(xdrStage.size()); + for (auto const& xdrThread : xdrStage) + { + auto& thread = stage.emplace_back(); + thread.reserve(xdrThread.size()); + for (auto const& xdrCluster : xdrThread) + { + auto& cluster = thread.emplace_back(); + for (auto const& env : xdrCluster) + { + auto tx = TransactionFrameBase::makeTransactionFromWire( + networkID, env); + if (!tx->XDRProvidesValidFee()) + { + CLOG_DEBUG(Herder, "Got bad generalized txSet: " + "transaction has invalid XDR"); + return std::nullopt; + } + cluster.push_back(tx); + inclusionFeeMap[tx] = baseFee; + } + if (!std::is_sorted(cluster.begin(), cluster.end(), + &TxSetUtils::hashTxSorter)) + { + CLOG_DEBUG(Herder, "Got bad generalized txSet: " + "cluster is not sorted"); + return std::nullopt; + } + } + if (!std::is_sorted(thread.begin(), thread.end(), + [](auto const& a, auto const& b) { + releaseAssert(!a.empty() && !b.empty()); + return TxSetUtils::hashTxSorter( + a.front(), b.front()); + })) + { + CLOG_DEBUG(Herder, "Got bad generalized txSet: " + "thread is not sorted"); + return std::nullopt; + } + } + if (!std::is_sorted(stage.begin(), stage.end(), + [](auto const& a, auto const& b) { + releaseAssert(!a.empty() && !b.empty()); + return TxSetUtils::hashTxSorter( + a.front().front(), b.front().front()); + })) + { + CLOG_DEBUG(Herder, "Got bad generalized txSet: " + "stage is not sorted"); + return std::nullopt; + } + } + return TxSetPhaseFrame(std::move(stages), inclusionFeeMapPtr); + } +#endif + } + + return std::nullopt; +} + +std::optional +TxSetPhaseFrame::makeFromWireLegacy( + LedgerHeader const& lclHeader, Hash const& networkID, + xdr::xvector const& xdrTxs) +{ + TxFrameList txList; + if (!addWireTxsToList(networkID, xdrTxs, txList)) + { + CLOG_DEBUG( + Herder, + "Got bad legacy txSet: transactions are not ordered correctly " + "or contain invalid phase transactions"); + return std::nullopt; + } + auto inclusionFeeMapPtr = std::make_shared(); + auto& inclusionFeeMap = *inclusionFeeMapPtr; + int64_t baseFee = computeBaseFeeForLegacyTxSet(lclHeader, txList); + for (auto const& tx : txList) + { + inclusionFeeMap[tx] = baseFee; + } + return TxSetPhaseFrame(std::move(txList), inclusionFeeMapPtr); +} + +TxSetPhaseFrame +TxSetPhaseFrame::makeEmpty(bool isParallel) +{ + if (isParallel) + { + return TxSetPhaseFrame(TxStageFrameList{}, + std::make_shared()); + } + return TxSetPhaseFrame(TxFrameList{}, std::make_shared()); +} + +TxSetPhaseFrame::TxSetPhaseFrame( + TxFrameList&& txs, std::shared_ptr inclusionFeeMap) + : mTxs(txs), mInclusionFeeMap(inclusionFeeMap) +{ +} + +TxSetPhaseFrame::TxSetPhaseFrame( + TxStageFrameList&& txs, std::shared_ptr inclusionFeeMap) + : mTxs(txs), mInclusionFeeMap(inclusionFeeMap) +{ +} + +TxSetPhaseFrame::Iterator +TxSetPhaseFrame::begin() const +{ + return TxSetPhaseFrame::Iterator(mTxs, 0, 0); +} + +TxSetPhaseFrame::Iterator +TxSetPhaseFrame::end() const +{ + size_t stageIndex = 0; + size_t txIndex = 0; + if (std::holds_alternative(mTxs)) + { + stageIndex = std::get(mTxs).size(); + } + else if (std::holds_alternative(mTxs)) + { + txIndex = std::get(mTxs).size(); + } + return TxSetPhaseFrame::Iterator(mTxs, stageIndex, txIndex); +} + +size_t +TxSetPhaseFrame::size() const +{ + return std::visit( + [](auto const& txs) { + using T = std::decay_t; + if constexpr (std::is_same_v) + { + return txs.size(); + } + else if constexpr (std::is_same_v) + { + size_t size = 0; + for (auto const& stage : txs) + { + for (auto const& thread : stage) + { + size += thread.size(); + } + } + return size; + } + else + { + throw std::runtime_error("unexpected TxPhase variant"); + return static_cast(0); + } + }, + mTxs); +} + +bool +TxSetPhaseFrame::empty() const { - return mEncodedSize; + return size() == 0; } -void -TxSetXDRFrame::toXDR(TransactionSet& txSet) const +bool +TxSetPhaseFrame::isParallel() const { - releaseAssert(!isGeneralizedTxSet()); - txSet = std::get(mXDRTxSet); + return std::holds_alternative(mTxs); } -void -TxSetXDRFrame::toXDR(GeneralizedTransactionSet& txSet) const +TxStageFrameList const& +TxSetPhaseFrame::getParallelStages() const { - releaseAssert(isGeneralizedTxSet()); - txSet = std::get(mXDRTxSet); + releaseAssert(isParallel()); + return std::get(mTxs); +} + +TxFrameList const& +TxSetPhaseFrame::getNonParallelTxs() const +{ + releaseAssert(!isParallel()); + return std::get(mTxs); } void -TxSetXDRFrame::storeXDR(StoredTransactionSet& txSet) const +TxSetPhaseFrame::toXDR(TransactionPhase& xdrPhase) const { - if (isGeneralizedTxSet()) - { - txSet.v(1); - txSet.generalizedTxSet() = - std::get(mXDRTxSet); - } - else - { - txSet.v(0); - txSet.txSet() = std::get(mXDRTxSet); - } + return std::visit( + [this, &xdrPhase](auto const& txs) { + using T = std::decay_t; + if constexpr (std::is_same_v) + { + nonParallelPhaseToXdr(txs, *mInclusionFeeMap, xdrPhase); + } + else if constexpr (std::is_same_v) + { + +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION + parallelPhaseToXdr(txs, *mInclusionFeeMap, xdrPhase); +#else + releaseAssert(false); +#endif + } + else + { + throw std::runtime_error("unexpected TxPhase variant"); + } + }, + mTxs); +} + +InclusionFeeMap const& +TxSetPhaseFrame::getInclusionFeeMap() const +{ + return *mInclusionFeeMap; +} + +TxSetPhaseFrame +TxSetPhaseFrame::sortedForApply(Hash const& txSetHash) const +{ + return std::visit( + [this, &txSetHash](auto const& txs) { + using T = std::decay_t; + if constexpr (std::is_same_v) + { + return TxSetPhaseFrame( + sortedForApplyNonParallel(txs, txSetHash), + mInclusionFeeMap); + } + else if constexpr (std::is_same_v) + { + return TxSetPhaseFrame(sortedForApplyParallel(txs, txSetHash), + mInclusionFeeMap); + } + else + { + throw std::runtime_error("unexpected TxPhase variant"); + return TxSetPhaseFrame::makeEmpty(false); + } + }, + mTxs); } -ApplicableTxSetFrame::ApplicableTxSetFrame(Application& app, bool isGeneralized, - Hash const& previousLedgerHash, - TxSetPhaseTransactions const& txs, - std::optional contentsHash) +ApplicableTxSetFrame::ApplicableTxSetFrame( + Application& app, bool isGeneralized, Hash const& previousLedgerHash, + std::vector const& phases, + std::optional contentsHash) : mIsGeneralized(isGeneralized) , mPreviousLedgerHash(previousLedgerHash) - , mTxPhases(txs) - , mFeesComputed(mTxPhases.size(), false) - , mPhaseInclusionFeeMap(mTxPhases.size()) + , mPhases(phases) , mContentsHash(contentsHash) { releaseAssert(previousLedgerHash == @@ -749,12 +1709,13 @@ ApplicableTxSetFrame::ApplicableTxSetFrame(Application& app, bool isGeneralized, ApplicableTxSetFrame::ApplicableTxSetFrame( Application& app, LedgerHeaderHistoryEntry const& lclHeader, - TxSetPhaseTransactions const& txs, std::optional contentsHash) + std::vector const& phases, + std::optional contentsHash) : ApplicableTxSetFrame( app, protocolVersionStartsFrom(lclHeader.header.ledgerVersion, SOROBAN_PROTOCOL_VERSION), - lclHeader.hash, txs, contentsHash) + lclHeader.hash, phases, contentsHash) { } @@ -765,73 +1726,33 @@ ApplicableTxSetFrame::getContentsHash() const return *mContentsHash; } -TxSetTransactions const& -ApplicableTxSetFrame::getTxsForPhase(TxSetPhase phase) const +TxSetPhaseFrame const& +ApplicableTxSetFrame::getPhase(TxSetPhase phaseTxs) const { - releaseAssert(static_cast(phase) < mTxPhases.size()); - return mTxPhases.at(static_cast(phase)); + releaseAssert(static_cast(phaseTxs) < mPhases.size()); + return mPhases.at(static_cast(phaseTxs)); } -TxSetTransactions -ApplicableTxSetFrame::getTxsInApplyOrder() const +std::vector const& +ApplicableTxSetFrame::getPhases() const { -#ifdef BUILD_TESTS - if (mApplyOrderOverride) - { - return *mApplyOrderOverride; - } -#endif - ZoneScoped; - - // Use a single vector to order transactions from all phases - std::vector retList; - retList.reserve(sizeTxTotal()); + return mPhases; +} - for (auto const& phase : mTxPhases) +std::vector const& +ApplicableTxSetFrame::getPhasesInApplyOrder() const +{ + ZoneScoped; + if (mApplyOrderPhases.empty()) { - auto txQueues = TxSetUtils::buildAccountTxQueues(phase); - - // build txBatches - // txBatches i-th element contains each i-th transaction for accounts - // with a transaction in the transaction set - std::vector> txBatches; - - while (!txQueues.empty()) - { - txBatches.emplace_back(); - auto& curBatch = txBatches.back(); - // go over all users that still have transactions - for (auto it = txQueues.begin(); it != txQueues.end();) - { - auto& txQueue = *it; - curBatch.emplace_back(txQueue->getTopTx()); - txQueue->popTopTx(); - if (txQueue->empty()) - { - // done with that user - it = txQueues.erase(it); - } - else - { - ++it; - } - } - } - - for (auto& batch : txBatches) + mApplyOrderPhases.reserve(mPhases.size()); + for (auto const& phaseTxs : mPhases) { - // randomize each batch using the hash of the transaction set - // as a way to randomize even more - ApplyTxSorter s(getContentsHash()); - std::sort(batch.begin(), batch.end(), s); - for (auto const& tx : batch) - { - retList.push_back(tx); - } + mApplyOrderPhases.emplace_back( + phaseTxs.sortedForApply(getContentsHash())); } } - - return retList; + return mApplyOrderPhases; } // need to make sure every account that is submitting a tx has enough to pay @@ -866,8 +1787,6 @@ ApplicableTxSetFrame::checkValid(Application& app, if (isGeneralizedTxSet()) { - releaseAssert(std::all_of(mFeesComputed.begin(), mFeesComputed.end(), - [](bool comp) { return comp; })); auto checkFeeMap = [&](auto const& feeMap) { for (auto const& [tx, fee] : feeMap) { @@ -878,10 +1797,10 @@ ApplicableTxSetFrame::checkValid(Application& app, if (*fee < lcl.header.baseFee) { - CLOG_DEBUG( - Herder, - "Got bad txSet: {} has too low component base fee {}", - hexAbbrev(mPreviousLedgerHash), *fee); + CLOG_DEBUG(Herder, + "Got bad txSet: {} has too low component " + "base fee {}", + hexAbbrev(mPreviousLedgerHash), *fee); return false; } if (tx->getInclusionFee() < @@ -899,12 +1818,29 @@ ApplicableTxSetFrame::checkValid(Application& app, return true; }; - if (!checkFeeMap(getInclusionFeeMap(TxSetPhase::CLASSIC))) + for (auto const& phase : mPhases) + { + if (!checkFeeMap(phase.getInclusionFeeMap())) + { + return false; + } + } + if (mPhases[static_cast(TxSetPhase::CLASSIC)].isParallel()) { + CLOG_DEBUG(Herder, + "Got bad txSet: classic phase can't be parallel"); return false; } - if (!checkFeeMap(getInclusionFeeMap(TxSetPhase::SOROBAN))) + bool needParallelSorobanPhase = protocolVersionStartsFrom( + lcl.header.ledgerVersion, PARALLEL_SOROBAN_PHASE_PROTOCOL_VERSION); + if (mPhases[static_cast(TxSetPhase::SOROBAN)].isParallel() != + needParallelSorobanPhase) { + CLOG_DEBUG(Herder, + "Got bad txSet: Soroban phase parallel support " + "does not match the current protocol; '{}' was " + "expected", + needParallelSorobanPhase); return false; } } @@ -922,9 +1858,9 @@ ApplicableTxSetFrame::checkValid(Application& app, // First, ensure the tx set does not contain multiple txs per source // account std::unordered_set seenAccounts; - for (auto const& phase : mTxPhases) + for (auto const& phaseTxs : mPhases) { - for (auto const& tx : phase) + for (auto const& tx : phaseTxs) { if (!seenAccounts.insert(tx->getSourceID()).second) { @@ -961,11 +1897,10 @@ ApplicableTxSetFrame::checkValid(Application& app, } bool allValid = true; - for (auto const& txs : mTxPhases) + for (auto const& txs : mPhases) { - if (!TxSetUtils::getInvalidTxList(txs, app, lowerBoundCloseTimeOffset, - upperBoundCloseTimeOffset, true) - .empty()) + if (!phaseTxsAreValid(txs, app, lowerBoundCloseTimeOffset, + upperBoundCloseTimeOffset)) { allValid = false; break; @@ -976,21 +1911,21 @@ ApplicableTxSetFrame::checkValid(Application& app, size_t ApplicableTxSetFrame::size(LedgerHeader const& lh, - std::optional phase) const + std::optional phaseTxs) const { size_t sz = 0; - if (!phase) + if (!phaseTxs) { if (numPhases() > static_cast(TxSetPhase::SOROBAN)) { sz += sizeOp(TxSetPhase::SOROBAN); } } - else if (phase.value() == TxSetPhase::SOROBAN) + else if (phaseTxs.value() == TxSetPhase::SOROBAN) { sz += sizeOp(TxSetPhase::SOROBAN); } - if (!phase || phase.value() == TxSetPhase::CLASSIC) + if (!phaseTxs || phaseTxs.value() == TxSetPhase::CLASSIC) { sz += protocolVersionStartsFrom(lh.ledgerVersion, ProtocolVersion::V_11) ? sizeOp(TxSetPhase::CLASSIC) @@ -1000,10 +1935,10 @@ ApplicableTxSetFrame::size(LedgerHeader const& lh, } size_t -ApplicableTxSetFrame::sizeOp(TxSetPhase phase) const +ApplicableTxSetFrame::sizeOp(TxSetPhase phaseTxs) const { ZoneScoped; - auto const& txs = mTxPhases.at(static_cast(phase)); + auto const& txs = mPhases.at(static_cast(phaseTxs)); return std::accumulate(txs.begin(), txs.end(), size_t(0), [&](size_t a, TransactionFrameBasePtr const& tx) { return a + tx->getNumOperations(); @@ -1015,147 +1950,37 @@ ApplicableTxSetFrame::sizeOpTotal() const { ZoneScoped; size_t total = 0; - for (int i = 0; i < mTxPhases.size(); i++) + for (int i = 0; i < mPhases.size(); i++) { total += sizeOp(static_cast(i)); } return total; } +size_t +ApplicableTxSetFrame::sizeTx(TxSetPhase phase) const +{ + return mPhases.at(static_cast(phase)).size(); +} + size_t ApplicableTxSetFrame::sizeTxTotal() const { ZoneScoped; size_t total = 0; - for (int i = 0; i < mTxPhases.size(); i++) + for (int i = 0; i < mPhases.size(); i++) { total += sizeTx(static_cast(i)); } return total; } -void -ApplicableTxSetFrame::computeTxFeesForNonGeneralizedSet( - LedgerHeader const& lclHeader) const -{ - ZoneScoped; - auto ledgerVersion = lclHeader.ledgerVersion; - int64_t lowBaseFee = std::numeric_limits::max(); - releaseAssert(mTxPhases.size() == 1); - for (auto& txPtr : mTxPhases[0]) - { - int64_t txBaseFee = computePerOpFee(*txPtr, ledgerVersion); - lowBaseFee = std::min(lowBaseFee, txBaseFee); - } - computeTxFeesForNonGeneralizedSet(lclHeader, lowBaseFee, - /* enableLogging */ false); -} - -void -ApplicableTxSetFrame::computeTxFeesForNonGeneralizedSet( - LedgerHeader const& lclHeader, int64_t lowestBaseFee, - bool enableLogging) const -{ - ZoneScoped; - releaseAssert(std::none_of(mFeesComputed.begin(), mFeesComputed.end(), - [](bool comp) { return comp; })); - int64_t baseFee = lclHeader.baseFee; - - if (protocolVersionStartsFrom(lclHeader.ledgerVersion, - ProtocolVersion::V_11)) - { - size_t surgeOpsCutoff = 0; - if (lclHeader.maxTxSetSize >= MAX_OPS_PER_TX) - { - surgeOpsCutoff = lclHeader.maxTxSetSize - MAX_OPS_PER_TX; - } - if (sizeOp(TxSetPhase::CLASSIC) > surgeOpsCutoff) - { - baseFee = lowestBaseFee; - if (enableLogging) - { - CLOG_WARNING(Herder, "surge pricing in effect! {} > {}", - sizeOp(TxSetPhase::CLASSIC), surgeOpsCutoff); - } - } - } - - releaseAssert(mTxPhases.size() == 1); - releaseAssert(mPhaseInclusionFeeMap.size() == 1); - auto const& phase = mTxPhases[static_cast(TxSetPhase::CLASSIC)]; - auto& feeMap = getInclusionFeeMap(TxSetPhase::CLASSIC); - for (auto const& tx : phase) - { - feeMap[tx] = baseFee; - } - mFeesComputed[0] = true; -} - -void -ApplicableTxSetFrame::computeTxFees( - TxSetPhase phase, LedgerHeader const& ledgerHeader, - SurgePricingLaneConfig const& surgePricingConfig, - std::vector const& lowestLaneFee, - std::vector const& hadTxNotFittingLane) const -{ - releaseAssert(!mFeesComputed[static_cast(phase)]); - releaseAssert(isGeneralizedTxSet()); - releaseAssert(lowestLaneFee.size() == hadTxNotFittingLane.size()); - std::vector laneBaseFee(lowestLaneFee.size(), - ledgerHeader.baseFee); - auto minBaseFee = - *std::min_element(lowestLaneFee.begin(), lowestLaneFee.end()); - for (size_t lane = 0; lane < laneBaseFee.size(); ++lane) - { - // If generic lane is full, then any transaction had to compete with not - // included transactions and independently of the lane they need to have - // at least the minimum fee in the tx set applied. - if (hadTxNotFittingLane[SurgePricingPriorityQueue::GENERIC_LANE]) - { - laneBaseFee[lane] = minBaseFee; - } - // If limited lane is full, then the transactions in this lane also had - // to compete with each other and have a base fee associated with this - // lane only. - if (lane != SurgePricingPriorityQueue::GENERIC_LANE && - hadTxNotFittingLane[lane]) - { - laneBaseFee[lane] = lowestLaneFee[lane]; - } - if (laneBaseFee[lane] > ledgerHeader.baseFee) - { - CLOG_WARNING( - Herder, - "{} phase: surge pricing for '{}' lane is in effect with base " - "fee={}, baseFee={}", - getTxSetPhaseName(phase), - lane == SurgePricingPriorityQueue::GENERIC_LANE ? "generic" - : "DEX", - laneBaseFee[lane], ledgerHeader.baseFee); - } - } - - auto const& txs = mTxPhases.at(static_cast(phase)); - auto& feeMap = getInclusionFeeMap(phase); - for (auto const& tx : txs) - { - feeMap[tx] = laneBaseFee[surgePricingConfig.getLane(*tx)]; - } - mFeesComputed[static_cast(phase)] = true; -} - std::optional -ApplicableTxSetFrame::getTxBaseFee(TransactionFrameBaseConstPtr const& tx, - LedgerHeader const& lclHeader) const +ApplicableTxSetFrame::getTxBaseFee(TransactionFrameBaseConstPtr const& tx) const { - if (std::any_of(mFeesComputed.begin(), mFeesComputed.end(), - [](bool comp) { return !comp; })) - { - releaseAssert(!isGeneralizedTxSet()); - computeTxFeesForNonGeneralizedSet(lclHeader); - } - for (auto const& phaseMap : mPhaseInclusionFeeMap) + for (auto const& phaseTxs : mPhases) { + auto const& phaseMap = phaseTxs.getInclusionFeeMap(); if (auto it = phaseMap.find(tx); it != phaseMap.end()) { return it->second; @@ -1168,9 +1993,9 @@ ApplicableTxSetFrame::getTxBaseFee(TransactionFrameBaseConstPtr const& tx, std::optional ApplicableTxSetFrame::getTxSetSorobanResource() const { - releaseAssert(mTxPhases.size() > static_cast(TxSetPhase::SOROBAN)); + releaseAssert(mPhases.size() > static_cast(TxSetPhase::SOROBAN)); auto total = Resource::makeEmptySoroban(); - for (auto const& tx : mTxPhases[static_cast(TxSetPhase::SOROBAN)]) + for (auto const& tx : mPhases[static_cast(TxSetPhase::SOROBAN)]) { if (total.canAdd(tx->getResources(/* useByteLimitInClassic */ false))) { @@ -1189,16 +2014,13 @@ ApplicableTxSetFrame::getTotalFees(LedgerHeader const& lh) const { ZoneScoped; int64_t total{0}; - std::for_each(mTxPhases.begin(), mTxPhases.end(), - [&](TxSetTransactions const& phase) { - total += std::accumulate( - phase.begin(), phase.end(), int64_t(0), - [&](int64_t t, TransactionFrameBasePtr const& tx) { - return t + - tx->getFee(lh, getTxBaseFee(tx, lh), true); - }); - }); - + for (auto const& phaseTxs : mPhases) + { + for (auto const& tx : phaseTxs) + { + total += tx->getFee(lh, getTxBaseFee(tx), true); + } + } return total; } @@ -1207,15 +2029,13 @@ ApplicableTxSetFrame::getTotalInclusionFees() const { ZoneScoped; int64_t total{0}; - std::for_each(mTxPhases.begin(), mTxPhases.end(), - [&](TxSetTransactions const& phase) { - total += std::accumulate( - phase.begin(), phase.end(), int64_t(0), - [&](int64_t t, TransactionFrameBasePtr const& tx) { - return t + tx->getInclusionFee(); - }); - }); - + for (auto const& phaseTxs : mPhases) + { + for (auto const& tx : phaseTxs) + { + total += tx->getInclusionFee(); + } + } return total; } @@ -1232,7 +2052,8 @@ ApplicableTxSetFrame::summary() const FMT_STRING("txs:{}, ops:{}, base_fee:{}"), sizeTxTotal(), sizeOpTotal(), // NB: fee map can't be empty at this stage (checked above). - getInclusionFeeMap(TxSetPhase::CLASSIC) + mPhases[static_cast(TxSetPhase::CLASSIC)] + .getInclusionFeeMap() .begin() ->second.value_or(0)); } @@ -1271,18 +2092,17 @@ ApplicableTxSetFrame::summary() const }; std::string status; - releaseAssert(mTxPhases.size() <= + releaseAssert(mPhases.size() <= static_cast(TxSetPhase::PHASE_COUNT)); - for (auto i = 0; i != mTxPhases.size(); i++) + for (auto i = 0; i != mPhases.size(); i++) { if (!status.empty()) { status += ", "; } - status += fmt::format( - FMT_STRING("{} phase: {}"), - getTxSetPhaseName(static_cast(i)), - feeStats(getInclusionFeeMap(static_cast(i)))); + status += fmt::format(FMT_STRING("{} phase: {}"), + getTxSetPhaseName(static_cast(i)), + feeStats(mPhases[i].getInclusionFeeMap())); } return status; } @@ -1292,8 +2112,9 @@ ApplicableTxSetFrame::toXDR(TransactionSet& txSet) const { ZoneScoped; releaseAssert(!isGeneralizedTxSet()); - releaseAssert(mTxPhases.size() == 1); - transactionsToTransactionSetXDR(mTxPhases[0], mPreviousLedgerHash, txSet); + releaseAssert(mPhases.size() == 1); + transactionsToTransactionSetXDR(mPhases[0].getNonParallelTxs(), + mPreviousLedgerHash, txSet); } void @@ -1301,12 +2122,9 @@ ApplicableTxSetFrame::toXDR(GeneralizedTransactionSet& generalizedTxSet) const { ZoneScoped; releaseAssert(isGeneralizedTxSet()); - releaseAssert(std::all_of(mFeesComputed.begin(), mFeesComputed.end(), - [](bool comp) { return comp; })); - releaseAssert(mTxPhases.size() <= + releaseAssert(mPhases.size() <= static_cast(TxSetPhase::PHASE_COUNT)); - transactionsToGeneralizedTransactionSetXDR(mTxPhases, mPhaseInclusionFeeMap, - mPreviousLedgerHash, + transactionsToGeneralizedTransactionSetXDR(mPhases, mPreviousLedgerHash, generalizedTxSet); } @@ -1335,178 +2153,4 @@ ApplicableTxSetFrame::isGeneralizedTxSet() const return mIsGeneralized; } -bool -ApplicableTxSetFrame::addTxsFromXdr( - Hash const& networkID, xdr::xvector const& txs, - bool useBaseFee, std::optional baseFee, TxSetPhase phase) -{ - auto& phaseTxs = mTxPhases.at(static_cast(phase)); - size_t oldSize = phaseTxs.size(); - phaseTxs.reserve(oldSize + txs.size()); - - for (auto const& env : txs) - { - auto tx = TransactionFrameBase::makeTransactionFromWire(networkID, env); - if (!tx->XDRProvidesValidFee()) - { - return false; - } - // Phase should be consistent with the tx we're trying to add - if ((tx->isSoroban() && phase != TxSetPhase::SOROBAN) || - (!tx->isSoroban() && phase != TxSetPhase::CLASSIC)) - { - return false; - } - - phaseTxs.push_back(tx); - if (useBaseFee) - { - getInclusionFeeMap(phase)[tx] = baseFee; - } - } - return std::is_sorted(phaseTxs.begin() + oldSize, phaseTxs.end(), - &TxSetUtils::hashTxSorter); -} - -void -ApplicableTxSetFrame::applySurgePricing(Application& app) -{ - ZoneScoped; - - if (empty()) - { - for (int i = 0; i < mFeesComputed.size(); ++i) - { - mFeesComputed[i] = true; - } - return; - } - - auto const& lclHeader = - app.getLedgerManager().getLastClosedLedgerHeader().header; - - releaseAssert(mTxPhases.size() <= - static_cast(TxSetPhase::PHASE_COUNT)); - for (int i = 0; i < mTxPhases.size(); i++) - { - TxSetPhase phaseType = static_cast(i); - auto& phase = mTxPhases[i]; - auto actTxQueues = TxSetUtils::buildAccountTxQueues(phase); - - if (phaseType == TxSetPhase::CLASSIC) - { - auto maxOps = - Resource({static_cast( - app.getLedgerManager().getLastMaxTxSetSizeOps()), - MAX_CLASSIC_BYTE_ALLOWANCE}); - std::optional dexOpsLimit; - if (isGeneralizedTxSet() && - app.getConfig().MAX_DEX_TX_OPERATIONS_IN_TX_SET) - { - // DEX operations limit implies that DEX transactions should - // compete with each other in in a separate fee lane, which is - // only possible with generalized tx set. - dexOpsLimit = - Resource({*app.getConfig().MAX_DEX_TX_OPERATIONS_IN_TX_SET, - MAX_CLASSIC_BYTE_ALLOWANCE}); - } - - auto surgePricingLaneConfig = - std::make_shared(maxOps, dexOpsLimit); - - std::vector hadTxNotFittingLane; - auto includedTxs = - SurgePricingPriorityQueue::getMostTopTxsWithinLimits( - std::vector(actTxQueues.begin(), - actTxQueues.end()), - surgePricingLaneConfig, hadTxNotFittingLane); - - size_t laneCount = surgePricingLaneConfig->getLaneLimits().size(); - std::vector lowestLaneFee( - laneCount, std::numeric_limits::max()); - for (auto const& tx : includedTxs) - { - size_t lane = surgePricingLaneConfig->getLane(*tx); - auto perOpFee = computePerOpFee(*tx, lclHeader.ledgerVersion); - lowestLaneFee[lane] = std::min(lowestLaneFee[lane], perOpFee); - } - - phase = includedTxs; - if (isGeneralizedTxSet()) - { - computeTxFees(TxSetPhase::CLASSIC, lclHeader, - *surgePricingLaneConfig, lowestLaneFee, - hadTxNotFittingLane); - } - else - { - computeTxFeesForNonGeneralizedSet( - lclHeader, - lowestLaneFee[SurgePricingPriorityQueue::GENERIC_LANE], - /* enableLogging */ true); - } - } - else - { - releaseAssert(isGeneralizedTxSet()); - releaseAssert(phaseType == TxSetPhase::SOROBAN); - - auto limits = app.getLedgerManager().maxLedgerResources( - /* isSoroban */ true); - - auto byteLimit = - std::min(static_cast(MAX_SOROBAN_BYTE_ALLOWANCE), - limits.getVal(Resource::Type::TX_BYTE_SIZE)); - limits.setVal(Resource::Type::TX_BYTE_SIZE, byteLimit); - - auto surgePricingLaneConfig = - std::make_shared(limits); - - std::vector hadTxNotFittingLane; - auto includedTxs = - SurgePricingPriorityQueue::getMostTopTxsWithinLimits( - std::vector(actTxQueues.begin(), - actTxQueues.end()), - surgePricingLaneConfig, hadTxNotFittingLane); - - size_t laneCount = surgePricingLaneConfig->getLaneLimits().size(); - std::vector lowestLaneFee( - laneCount, std::numeric_limits::max()); - for (auto const& tx : includedTxs) - { - size_t lane = surgePricingLaneConfig->getLane(*tx); - auto perOpFee = computePerOpFee(*tx, lclHeader.ledgerVersion); - lowestLaneFee[lane] = std::min(lowestLaneFee[lane], perOpFee); - } - - phase = includedTxs; - computeTxFees(phaseType, lclHeader, *surgePricingLaneConfig, - lowestLaneFee, hadTxNotFittingLane); - } - - releaseAssert(mFeesComputed[i]); - } -} - -std::unordered_map>& -ApplicableTxSetFrame::getInclusionFeeMap(TxSetPhase phase) const -{ - size_t phaseId = static_cast(phase); - releaseAssert(phaseId < mPhaseInclusionFeeMap.size()); - return mPhaseInclusionFeeMap[phaseId]; -} - -std::string -getTxSetPhaseName(TxSetPhase phase) -{ - switch (phase) - { - case TxSetPhase::CLASSIC: - return "classic"; - case TxSetPhase::SOROBAN: - return "soroban"; - default: - throw std::runtime_error("Unknown phase"); - } -} } // namespace stellar diff --git a/src/herder/TxSetFrame.h b/src/herder/TxSetFrame.h index cbbdbe2fd6..cf0e980684 100644 --- a/src/herder/TxSetFrame.h +++ b/src/herder/TxSetFrame.h @@ -9,6 +9,7 @@ #include "overlay/StellarXDR.h" #include "transactions/TransactionFrame.h" #include "util/NonCopyable.h" +#include "util/ProtocolVersion.h" #include "xdr/Stellar-internal.h" #include @@ -33,10 +34,8 @@ enum class TxSetPhase PHASE_COUNT }; -using TxSetTransactions = std::vector; -using TxSetPhaseTransactions = std::vector; - -std::string getTxSetPhaseName(TxSetPhase phase); +using TxFrameList = std::vector; +using PerPhaseTransactionList = std::vector; // Creates a valid ApplicableTxSetFrame and corresponding TxSetXDRFrame // from the provided transactions. @@ -51,26 +50,26 @@ std::string getTxSetPhaseName(TxSetPhase phase); // transaction pointers. std::pair makeTxSetFromTransactions( - TxSetPhaseTransactions const& txPhases, Application& app, + PerPhaseTransactionList const& txPhases, Application& app, uint64_t lowerBoundCloseTimeOffset, uint64_t upperBoundCloseTimeOffset #ifdef BUILD_TESTS // Skips the tx set validation and preserves the pointers // to the passed-in transactions - use in conjunction with - // `orderOverride` argument in test-only overrides. + // `enforceTxsApplyOrder` argument in test-only overrides. , bool skipValidation = false #endif ); std::pair makeTxSetFromTransactions( - TxSetPhaseTransactions const& txPhases, Application& app, + PerPhaseTransactionList const& txPhases, Application& app, uint64_t lowerBoundCloseTimeOffset, uint64_t upperBoundCloseTimeOffset, - TxSetPhaseTransactions& invalidTxsPerPhase + PerPhaseTransactionList& invalidTxsPerPhase #ifdef BUILD_TESTS // Skips the tx set validation and preserves the pointers // to the passed-in transactions - use in conjunction with - // `orderOverride` argument in test-only overrides. + // `enforceTxsApplyOrder` argument in test-only overrides. , bool skipValidation = false #endif @@ -78,15 +77,15 @@ makeTxSetFromTransactions( #ifdef BUILD_TESTS std::pair -makeTxSetFromTransactions(TxSetTransactions txs, Application& app, +makeTxSetFromTransactions(TxFrameList txs, Application& app, uint64_t lowerBoundCloseTimeOffset, uint64_t upperBoundCloseTimeOffset, bool enforceTxsApplyOrder = false); std::pair -makeTxSetFromTransactions(TxSetTransactions txs, Application& app, +makeTxSetFromTransactions(TxFrameList txs, Application& app, uint64_t lowerBoundCloseTimeOffset, uint64_t upperBoundCloseTimeOffset, - TxSetTransactions& invalidTxs, + TxFrameList& invalidTxs, bool enforceTxsApplyOrder = false); #endif @@ -99,8 +98,7 @@ makeTxSetFromTransactions(TxSetTransactions txs, Application& app, // // Before even trying to validate and apply a TxSetXDRFrame it has // to be interpreted and prepared for apply using the ledger state -// this TxSetXDRFrame refers to. This is typically performed by -// `prepareForApply` method. +// this TxSetXDRFrame refers to. This is performed by `prepareForApply` method. class TxSetXDRFrame : public NonMovableOrCopyable { public: @@ -124,7 +122,7 @@ class TxSetXDRFrame : public NonMovableOrCopyable // historical transactions. static TxSetXDRFrameConstPtr makeFromHistoryTransactions(Hash const& previousLedgerHash, - TxSetTransactions const& txs); + TxFrameList const& txs); void toXDR(TransactionSet& set) const; void toXDR(GeneralizedTransactionSet& generalizedTxSet) const; @@ -152,8 +150,11 @@ class TxSetXDRFrame : public NonMovableOrCopyable // Returns the hash of this tx set. Hash const& getContentsHash() const; + // Returns the hash of the previous ledger that this tx set refers to. Hash const& previousLedgerHash() const; + // Returns the total number of transactions in this tx set (even if it's + // not structurally valid). size_t sizeTxTotal() const; // Gets the size of this transaction set in operations. @@ -170,7 +171,8 @@ class TxSetXDRFrame : public NonMovableOrCopyable // This is only necessary to serve a very specific use case of updating // the transaction queue with wired tx sets. Otherwise, use // getTransactionsForPhase() in `ApplicableTxSetFrame`. - TxSetPhaseTransactions createTransactionFrames(Hash const& networkID) const; + PerPhaseTransactionList + createTransactionFrames(Hash const& networkID) const; #ifdef BUILD_TESTS mutable ApplicableTxSetFrameConstPtr mApplicableTxSetOverride; @@ -187,6 +189,158 @@ class TxSetXDRFrame : public NonMovableOrCopyable Hash mHash; }; +// The following definitions are used to represent the 'parallel' phase of the +// transaction set. +// +// The structure of this phase is as follows: +// - The whole phase (`TxStageFrameList`) consists of several sequential +// 'stages' (`TxStageFrame`). A stage has to be executed after every +// transaction in the previous stage has been applied. +// - A 'stage' (`TxStageFrame`) consists of several parallel 'threads' +// (`TxThreadFrame`). Transactions in different 'threads' are independent of +// each other and can be applied in parallel. +// - A 'thread' (`TxThreadFrame`) consists of several 'clusters' (every one is +// just a list of transactions, `TxFrameList`). Transactions in the same +// 'cluster' are dependent on each other and must be applied in deterministic +// order. The clusters are independent of each other and may be applied in +// *different* threads in parallel. +// +// This structure mimics the XDR structure of the `ParallelTxsComponent`. +using TxThreadFrame = std::vector; +using TxStageFrame = std::vector; +using TxStageFrameList = std::vector; + +// Alias for the map from transaction to its inclusion fee as defined by the +// transaction set. +using InclusionFeeMap = + std::unordered_map>; + +// `TxSetPhaseFrame` represents a single phase of the `ApplicableTxSetFrame`. +// +// Phases can only be created as a part of the `ApplicableTxSetFrame` and thus +// don't have any public constructors. +// +// Phase may either wrap the corresponding `TransactionPhase` XDR for +// generalized transactions sets, or represent all the transactions in the +// 'legacy' transaction set (which is considered to have only a single phase). +// +// This does not assume any specific order of transactions by default - the +// phase in 'apply' order has to be explicitly requested from the parent +// `ApplicableTxSetFrame` via `getPhasesInApplyOrder` method. +class TxSetPhaseFrame +{ + public: + // Returns true when this phase can be applied in parallel. + // Currently only Soroban phase can be parallel, and only starting from + // PARALLEL_SOROBAN_PHASE_PROTOCOL_VERSION protocol + bool isParallel() const; + + // Returns the parallel stages of this phase. + // + // This may only be called when `isParallel()` is true. + TxStageFrameList const& getParallelStages() const; + // Returns all the transactions in this phase if it's not parallel. + // + // This may only be called when `isParallel()` is false. + TxFrameList const& getNonParallelTxs() const; + + // Serializes this phase to the provided XDR. + void toXDR(TransactionPhase& xdrPhase) const; + + // Iterator over the all transactions in this phase. + // The order of iteration is defined by the parent `ApplicableTxSetFrame`. + // If the phase is sorted for apply, then the iteration order can be used + // to determine a stable index of every transaction in the phase, even if + // the phase is parallel and can have certain transaction applied in + // arbitrary order. + class Iterator + { + public: + using value_type = TransactionFrameBasePtr; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + using iterator_category = std::forward_iterator_tag; + + TransactionFrameBasePtr operator*() const; + + Iterator& operator++(); + Iterator operator++(int); + + bool operator==(Iterator const& other) const; + bool operator!=(Iterator const& other) const; + + private: + friend class TxSetPhaseFrame; + + Iterator(std::variant const& txs, + size_t stageIndex, size_t txIndex); + std::variant const& mTxs; + size_t mStageIndex = 0; + size_t mThreadIndex = 0; + size_t mClusterIndex = 0; + size_t mTxIndex = 0; + }; + Iterator begin() const; + Iterator end() const; + size_t size() const; + bool empty() const; + + // Get _inclusion_ fee map for this phase. The map contains lowest base + // fee for each transaction (lowest base fee is identical for all + // transactions in the same lane) + InclusionFeeMap const& getInclusionFeeMap() const; + + private: + friend class TxSetXDRFrame; + friend class ApplicableTxSetFrame; + + friend std::pair + makeTxSetFromTransactions(PerPhaseTransactionList const& txPhases, + Application& app, + uint64_t lowerBoundCloseTimeOffset, + uint64_t upperBoundCloseTimeOffset, + PerPhaseTransactionList& invalidTxsPerPhase +#ifdef BUILD_TESTS + , + bool skipValidation +#endif + ); +#ifdef BUILD_TESTS + friend std::pair + makeTxSetFromTransactions(TxFrameList txs, Application& app, + uint64_t lowerBoundCloseTimeOffset, + uint64_t upperBoundCloseTimeOffset, + TxFrameList& invalidTxs, + bool enforceTxsApplyOrder); +#endif + + TxSetPhaseFrame(TxFrameList&& txs, + std::shared_ptr inclusionFeeMap); + TxSetPhaseFrame(TxStageFrameList&& txs, + std::shared_ptr inclusionFeeMap); + + // Creates a new phase from `TransactionPhase` XDR coming from a + // `GeneralizedTransactionSet`. + static std::optional + makeFromWire(Hash const& networkID, TransactionPhase const& xdrPhase); + + // Creates a new phase from all the transactions in the legacy + // `TransactionSet` XDR. + static std::optional + makeFromWireLegacy(LedgerHeader const& lclHeader, Hash const& networkID, + xdr::xvector const& xdrTxs); + + // Creates a valid empty phase with given `isParallel` flag. + static TxSetPhaseFrame makeEmpty(bool isParallel); + + // Returns a copy of this phase with transactions sorted for apply. + TxSetPhaseFrame sortedForApply(Hash const& txSetHash) const; + + std::shared_ptr mInclusionFeeMap; + std::variant mTxs; +}; + // Transaction set that is suitable for being applied to the ledger. // // This is not necessarily a fully *valid* transaction set: further validation @@ -201,51 +355,64 @@ class ApplicableTxSetFrame public: // Returns the base fee for the transaction or std::nullopt when the // transaction is not discounted. - std::optional getTxBaseFee(TransactionFrameBaseConstPtr const& tx, - LedgerHeader const& lclHeader) const; + std::optional + getTxBaseFee(TransactionFrameBaseConstPtr const& tx) const; + + // Gets the phase frame for the given phase in arbitrary order. + TxSetPhaseFrame const& getPhase(TxSetPhase phase) const; - // Gets all the transactions belonging to this frame in arbitrary order. - TxSetTransactions const& getTxsForPhase(TxSetPhase phase) const; + // Gets all the phases of this transaction set with transactions in + // arbitrary order. + std::vector const& getPhases() const; - // Build a list of transaction ready to be applied to the last closed - // ledger, based on the transaction set. + // Gets all the phases of this transaction set, each phase with + // transactions sorted for apply. // - // The order satisfies: - // * transactions for an account are sorted by sequence number (ascending) - // * the order between accounts is randomized - TxSetTransactions getTxsInApplyOrder() const; + // For the generalized transaction sets, the order is defined by shuffling + // all the transactions that are applied sequentially relatively to each + // other using the hash of the transaction set. + // + // For the legacy transaction sets, the apply order satisfies : + // - Transactions for an account are sorted by sequence number (ascending). + // - The order between accounts is randomized. + std::vector const& getPhasesInApplyOrder() const; - // Checks if this tx set frame is valid in the context of the current LCL. + // Checks if this transaction set frame is valid in the context of the + // current LCL. // This can be called when LCL does not match `previousLedgerHash`, but // then validation will never pass. bool checkValid(Application& app, uint64_t lowerBoundCloseTimeOffset, uint64_t upperBoundCloseTimeOffset) const; + // Returns the size of this whole transaction set, or the specified phase + // in operations or transactions (for older protocol versions). size_t size(LedgerHeader const& lh, std::optional phase = std::nullopt) const; - size_t - sizeTx(TxSetPhase phase) const - { - return mTxPhases.at(static_cast(phase)).size(); - } + // Returns the total number of transactions in the given phase. + size_t sizeTx(TxSetPhase phase) const; + // Returns the total number of transactions in this tx set. size_t sizeTxTotal() const; + // Returns the total number of operations in the given phase. + size_t sizeOp(TxSetPhase phase) const; + // Returns the total number of operations in this tx set. + size_t sizeOpTotal() const; + + // Returns whether this transaction set is empty. bool empty() const { return sizeTxTotal() == 0; } + // Returns the number of phases in this tx set. size_t numPhases() const { - return mTxPhases.size(); + return mPhases.size(); } - size_t sizeOp(TxSetPhase phase) const; - size_t sizeOpTotal() const; - // Returns the sum of all fees that this transaction set would take. int64_t getTotalFees(LedgerHeader const& lh) const; @@ -254,15 +421,17 @@ class ApplicableTxSetFrame int64_t getTotalInclusionFees() const; // Returns whether this transaction set is generalized, i.e. representable - // by GeneralizedTransactionSet XDR. + // by `GeneralizedTransactionSet` XDR. bool isGeneralizedTxSet() const; - // Returns a short description of this transaction set. + // Returns a short description of this transaction set for logging. std::string summary() const; + // Returns the hash of this transaction set. Hash const& getContentsHash() const; - // This shouldn't be needed for the regular flows, but is useful + // Converts this transaction set to XDR. + // This shouldn't be exposed for the regular flows, but is useful to expose // to cover XDR roundtrips in tests. #ifndef BUILD_TESTS private: @@ -271,12 +440,13 @@ class ApplicableTxSetFrame private: friend class TxSetXDRFrame; + friend std::pair - makeTxSetFromTransactions(TxSetPhaseTransactions const& txPhases, + makeTxSetFromTransactions(PerPhaseTransactionList const& txPhases, Application& app, uint64_t lowerBoundCloseTimeOffset, uint64_t upperBoundCloseTimeOffset, - TxSetPhaseTransactions& invalidTxsPerPhase + PerPhaseTransactionList& invalidTxsPerPhase #ifdef BUILD_TESTS , bool skipValidation @@ -284,74 +454,45 @@ class ApplicableTxSetFrame ); #ifdef BUILD_TESTS friend std::pair - makeTxSetFromTransactions(TxSetTransactions txs, Application& app, + makeTxSetFromTransactions(TxFrameList txs, Application& app, uint64_t lowerBoundCloseTimeOffset, uint64_t upperBoundCloseTimeOffset, - TxSetTransactions& invalidTxs, + TxFrameList& invalidTxs, bool enforceTxsApplyOrder); #endif ApplicableTxSetFrame(Application& app, LedgerHeaderHistoryEntry const& lclHeader, - TxSetPhaseTransactions const& txs, + std::vector const& phases, std::optional contentsHash); ApplicableTxSetFrame(Application& app, bool isGeneralized, Hash const& previousLedgerHash, - TxSetPhaseTransactions const& txs, + std::vector const& phases, std::optional contentsHash); ApplicableTxSetFrame(ApplicableTxSetFrame const&) = default; ApplicableTxSetFrame(ApplicableTxSetFrame&&) = default; - // Computes the fees for transactions in this set based on information from - // the non-generalized tx set. - // This has to be `const` in combination with `mutable` fee-related fields - // in order to accommodate one specific case: legacy (non-generalized) tx - // sets received from the peers don't include the fee information and we - // don't have immediate access to the ledger header needed to compute them. - // Hence we lazily compute the fees in `getTxBaseFee` for such TxSetFrames. - // This can be cleaned up after the protocol migration as non-generalized tx - // sets won't exist in the network anymore. - void computeTxFeesForNonGeneralizedSet(LedgerHeader const& lclHeader) const; - - bool addTxsFromXdr(Hash const& networkID, - xdr::xvector const& txs, - bool useBaseFee, std::optional baseFee, - TxSetPhase phase); - void applySurgePricing(Application& app); - - void computeTxFeesForNonGeneralizedSet(LedgerHeader const& lclHeader, - int64_t lowestBaseFee, - bool enableLogging) const; - - void computeTxFees(TxSetPhase phase, LedgerHeader const& ledgerHeader, - SurgePricingLaneConfig const& surgePricingConfig, - std::vector const& lowestLaneFee, - std::vector const& hadTxNotFittingLane) const; - std::optional getTxSetSorobanResource() const; - // Get _inclusion_ fee map for a given phase. The map contains lowest base - // fee for each transaction (lowest base fee is identical for all - // transactions in the same lane) - std::unordered_map>& - getInclusionFeeMap(TxSetPhase phase) const; + std::optional getTxSetSorobanResource() const; void toXDR(TransactionSet& set) const; void toXDR(GeneralizedTransactionSet& generalizedTxSet) const; bool const mIsGeneralized; Hash const mPreviousLedgerHash; + + // All the phases of this transaction set. + // // There can only be 1 phase (classic) prior to protocol 20. - // Starting protocol 20, there will be 2 phases (classic and soroban). - std::vector mTxPhases; + // Starting with protocol 20, there are 2 phases (classic and Soroban). + std::vector const mPhases; - mutable std::vector mFeesComputed; - mutable std::vector>> - mPhaseInclusionFeeMap; + // The phases with transactions sorted for apply. + // + // This is `mutable` because we want to do the sorting lazily only for the + // transaction sets that are actually applied. + mutable std::vector mApplyOrderPhases; std::optional mContentsHash; -#ifdef BUILD_TESTS - mutable std::optional mApplyOrderOverride; -#endif }; } // namespace stellar diff --git a/src/herder/TxSetUtils.cpp b/src/herder/TxSetUtils.cpp index d8a74421d2..e77d3fe436 100644 --- a/src/herder/TxSetUtils.cpp +++ b/src/herder/TxSetUtils.cpp @@ -34,8 +34,8 @@ namespace { // Target use case is to remove a subset of invalid transactions from a TxSet. // I.e. txSet.size() >= txsToRemove.size() -TxSetTransactions -removeTxs(TxSetTransactions const& txs, TxSetTransactions const& txsToRemove) +TxFrameList +removeTxs(TxFrameList const& txs, TxFrameList const& txsToRemove) { UnorderedSet txsToRemoveSet; txsToRemoveSet.reserve(txsToRemove.size()); @@ -44,7 +44,7 @@ removeTxs(TxSetTransactions const& txs, TxSetTransactions const& txsToRemove) std::inserter(txsToRemoveSet, txsToRemoveSet.end()), [](TransactionFrameBasePtr const& tx) { return tx->getFullHash(); }); - TxSetTransactions newTxs; + TxFrameList newTxs; newTxs.reserve(txs.size() - txsToRemove.size()); for (auto const& tx : txs) { @@ -123,22 +123,55 @@ TxSetUtils::hashTxSorter(TransactionFrameBasePtr const& tx1, return tx1->getFullHash() < tx2->getFullHash(); } -TxSetTransactions -TxSetUtils::sortTxsInHashOrder(TxSetTransactions const& transactions) +TxFrameList +TxSetUtils::sortTxsInHashOrder(TxFrameList const& transactions) { ZoneScoped; - TxSetTransactions sortedTxs(transactions); + TxFrameList sortedTxs(transactions); std::sort(sortedTxs.begin(), sortedTxs.end(), TxSetUtils::hashTxSorter); return sortedTxs; } +TxStageFrameList +TxSetUtils::sortParallelTxsInHashOrder(TxStageFrameList const& stages) +{ + ZoneScoped; + TxStageFrameList sortedStages = stages; + for (auto& stage : sortedStages) + { + for (auto& thread : stage) + { + for (auto& cluster : thread) + { + std::sort(cluster.begin(), cluster.end(), + TxSetUtils::hashTxSorter); + } + std::sort(thread.begin(), thread.end(), + [](std::vector const& a, + std::vector const& b) { + releaseAssert(!a.empty() && !b.empty()); + return hashTxSorter(a.front(), b.front()); + }); + } + std::sort( + stage.begin(), stage.end(), + [](std::vector> const& a, + std::vector> const& b) { + releaseAssert(!a.empty() && !b.empty()); + releaseAssert(!a.front().empty() && !b.front().empty()); + return hashTxSorter(a.front().front(), b.front().front()); + }); + } + return sortedStages; +} + std::vector> -TxSetUtils::buildAccountTxQueues(TxSetTransactions const& txs) +TxSetUtils::buildAccountTxQueues(TxFrameList const& txs) { ZoneScoped; UnorderedMap> actTxMap; - for (auto& tx : txs) + for (auto const& tx : txs) { auto id = tx->getSourceID(); auto it = @@ -154,11 +187,10 @@ TxSetUtils::buildAccountTxQueues(TxSetTransactions const& txs) return queues; } -TxSetTransactions -TxSetUtils::getInvalidTxList(TxSetTransactions const& txs, Application& app, +TxFrameList +TxSetUtils::getInvalidTxList(TxFrameList const& txs, Application& app, uint64_t lowerBoundCloseTimeOffset, - uint64_t upperBoundCloseTimeOffset, - bool returnEarlyOnFirstInvalidTx) + uint64_t upperBoundCloseTimeOffset) { ZoneScoped; LedgerTxn ltx(app.getLedgerTxnRoot(), /* shouldUpdateLastModified */ true, @@ -173,7 +205,7 @@ TxSetUtils::getInvalidTxList(TxSetTransactions const& txs, Application& app, } UnorderedMap accountFeeMap; - TxSetTransactions invalidTxs; + TxFrameList invalidTxs; auto accountTxQueues = buildAccountTxQueues(txs); for (auto& accountQueue : accountTxQueues) @@ -195,29 +227,6 @@ TxSetUtils::getInvalidTxList(TxSetTransactions const& txs, Application& app, { invalidTxs.emplace_back(tx); iter = accountQueue->mTxs.erase(iter); - if (returnEarlyOnFirstInvalidTx) - { - if (minSeqCheckIsInvalid) - { - CLOG_DEBUG(Herder, - "minSeqAge or minSeqLedgerGap set on tx " - "without lowest seqNum. tx: {}", - xdrToCerealString(tx->getEnvelope(), - "TransactionEnvelope")); - } - else - { - CLOG_DEBUG( - Herder, - "Got bad txSet: tx invalid lastSeq:{} tx: {} " - "result: {}", - lastSeq, - xdrToCerealString(tx->getEnvelope(), - "TransactionEnvelope"), - tx->getResultCode()); - } - return invalidTxs; - } } else // update the account fee map { @@ -252,14 +261,6 @@ TxSetUtils::getInvalidTxList(TxSetTransactions const& txs, Application& app, invalidTxs.emplace_back(*iter); ++iter; } - if (returnEarlyOnFirstInvalidTx) - { - CLOG_DEBUG(Herder, - "Got bad txSet: account can't pay fee tx: {}", - xdrToCerealString(tx->getEnvelope(), - "TransactionEnvelope")); - return invalidTxs; - } } else { @@ -271,14 +272,14 @@ TxSetUtils::getInvalidTxList(TxSetTransactions const& txs, Application& app, return invalidTxs; } -TxSetTransactions -TxSetUtils::trimInvalid(TxSetTransactions const& txs, Application& app, +TxFrameList +TxSetUtils::trimInvalid(TxFrameList const& txs, Application& app, uint64_t lowerBoundCloseTimeOffset, uint64_t upperBoundCloseTimeOffset, - TxSetTransactions& invalidTxs) + TxFrameList& invalidTxs) { invalidTxs = getInvalidTxList(txs, app, lowerBoundCloseTimeOffset, - upperBoundCloseTimeOffset, false); + upperBoundCloseTimeOffset); return removeTxs(txs, invalidTxs); } diff --git a/src/herder/TxSetUtils.h b/src/herder/TxSetUtils.h index 1420045be6..4e65a23b56 100644 --- a/src/herder/TxSetUtils.h +++ b/src/herder/TxSetUtils.h @@ -37,26 +37,25 @@ class TxSetUtils static bool hashTxSorter(TransactionFrameBasePtr const& tx1, TransactionFrameBasePtr const& tx2); - static TxSetTransactions - sortTxsInHashOrder(TxSetTransactions const& transactions); + static TxFrameList sortTxsInHashOrder(TxFrameList const& transactions); + static TxStageFrameList + sortParallelTxsInHashOrder(TxStageFrameList const& stages); static std::vector> - buildAccountTxQueues(TxSetTransactions const& txs); + buildAccountTxQueues(TxFrameList const& txs); // Returns transactions from a TxSet that are invalid. If // returnEarlyOnFirstInvalidTx is true, return immediately if an invalid // transaction is found (instead of finding all of them), this is useful for // checking if a TxSet is valid. - static TxSetTransactions - getInvalidTxList(TxSetTransactions const& txs, Application& app, - uint64_t lowerBoundCloseTimeOffset, - uint64_t upperBoundCloseTimeOffset, - bool returnEarlyOnFirstInvalidTx); - - static TxSetTransactions trimInvalid(TxSetTransactions const& txs, - Application& app, - uint64_t lowerBoundCloseTimeOffset, - uint64_t upperBoundCloseTimeOffset, - TxSetTransactions& invalidTxs); + static TxFrameList getInvalidTxList(TxFrameList const& txs, + Application& app, + uint64_t lowerBoundCloseTimeOffset, + uint64_t upperBoundCloseTimeOffset); + + static TxFrameList trimInvalid(TxFrameList const& txs, Application& app, + uint64_t lowerBoundCloseTimeOffset, + uint64_t upperBoundCloseTimeOffset, + TxFrameList& invalidTxs); }; // class TxSetUtils } // namespace stellar diff --git a/src/herder/test/HerderTests.cpp b/src/herder/test/HerderTests.cpp index 48dfe8eb8a..e719e33ba9 100644 --- a/src/herder/test/HerderTests.cpp +++ b/src/herder/test/HerderTests.cpp @@ -352,7 +352,7 @@ testTxSet(uint32 protocolVersion) { auto newUser = TestAccount{*app, getAccount("doesnotexist")}; txs.push_back(newUser.tx({payment(root, 1)})); - TxSetTransactions removed; + TxFrameList removed; auto txSet = makeTxSetFromTransactions(txs, *app, 0, 0, removed).second; REQUIRE(removed.size() == 1); @@ -366,7 +366,7 @@ testTxSet(uint32 protocolVersion) setSeqNum(tx, tx->getSeqNum() + 5); txs.push_back(tx); - TxSetTransactions removed; + TxFrameList removed; auto txSet = makeTxSetFromTransactions(txs, *app, 0, 0, removed).second; REQUIRE(removed.size() == 1); @@ -376,7 +376,7 @@ testTxSet(uint32 protocolVersion) { txs.erase(txs.begin()); - TxSetTransactions removed; + TxFrameList removed; auto txSet = makeTxSetFromTransactions(txs, *app, 0, 0, removed).second; @@ -394,7 +394,7 @@ testTxSet(uint32 protocolVersion) int remIdx = 2; // 3rd transaction from the first account txs.erase(txs.begin() + remIdx); - TxSetTransactions removed; + TxFrameList removed; auto txSet = makeTxSetFromTransactions(txs, *app, 0, 0, removed) .second; @@ -414,7 +414,7 @@ testTxSet(uint32 protocolVersion) // extra transaction would push the account below the reserve txs.push_back(accounts[0].tx({payment(accounts[0], 10)})); - TxSetTransactions removed; + TxFrameList removed; auto txSet = makeTxSetFromTransactions(txs, *app, 0, 0, removed).second; REQUIRE(removed.size() == (nbTransactions + 1)); @@ -425,7 +425,7 @@ testTxSet(uint32 protocolVersion) auto tx = std::static_pointer_cast(txs[0]); setMaxTime(tx, UINT64_MAX); tx->clearCached(); - TxSetTransactions removed; + TxFrameList removed; auto txSet = makeTxSetFromTransactions(txs, *app, 0, 0, removed).second; REQUIRE(removed.size() == nbTransactions); @@ -459,8 +459,8 @@ testTxSetWithFeeBumps(uint32 protocolVersion) auto account2 = root.create("a2", minBalance2); auto account3 = root.create("a3", minBalance2); - auto compareTxs = [](TxSetTransactions const& actual, - TxSetTransactions const& expected) { + auto compareTxs = [](TxFrameList const& actual, + TxFrameList const& expected) { auto actualNormalized = actual; auto expectedNormalized = expected; std::sort(actualNormalized.begin(), actualNormalized.end()); @@ -477,7 +477,7 @@ testTxSetWithFeeBumps(uint32 protocolVersion) auto tx2 = transaction(*app, account1, 2, 1, 100); auto fb2 = feeBump(*app, account2, tx2, minBalance2 - minBalance0 - 199); - TxSetTransactions invalidTxs; + TxFrameList invalidTxs; auto txSet = makeTxSetFromTransactions({fb1, fb2}, *app, 0, 0, invalidTxs); compareTxs(invalidTxs, {fb1, fb2}); @@ -493,7 +493,7 @@ testTxSetWithFeeBumps(uint32 protocolVersion) auto tx3 = transaction(*app, account1, 3, 1, 100); auto fb3 = feeBump(*app, account2, tx3, minBalance2 - minBalance0 - 199); - TxSetTransactions invalidTxs; + TxFrameList invalidTxs; auto txSet = makeTxSetFromTransactions({fb1, fb2, fb3}, *app, 0, 0, invalidTxs); compareTxs(invalidTxs, {fb2, fb3}); @@ -510,7 +510,7 @@ testTxSetWithFeeBumps(uint32 protocolVersion) auto fb3 = feeBump(*app, account2, tx3, minBalance2 - minBalance0 - 199); - TxSetTransactions invalidTxs; + TxFrameList invalidTxs; auto txSet = makeTxSetFromTransactions({fb1, fb2, fb3}, *app, 0, 0, invalidTxs); compareTxs(invalidTxs, {fb1, fb2, fb3}); @@ -526,7 +526,7 @@ testTxSetWithFeeBumps(uint32 protocolVersion) feeBump(*app, account2, tx2, minBalance2 - minBalance0 - 199); auto tx3 = transaction(*app, account1, 3, 1, 100); auto fb3 = feeBump(*app, account3, tx3, 200); - TxSetTransactions invalidTxs; + TxFrameList invalidTxs; auto txSet = makeTxSetFromTransactions({fb1, fb2, fb3}, *app, 0, 0, invalidTxs); compareTxs(invalidTxs, {fb1, fb2, fb3}); @@ -539,7 +539,7 @@ testTxSetWithFeeBumps(uint32 protocolVersion) auto tx2 = transaction(*app, account2, 1, 1, 100); auto fb2 = feeBump(*app, account2, tx2, minBalance2 - minBalance0 - 199); - TxSetTransactions invalidTxs; + TxFrameList invalidTxs; auto txSet = makeTxSetFromTransactions({fb1, fb2}, *app, 0, 0, invalidTxs); compareTxs(invalidTxs, {fb1, fb2}); @@ -552,7 +552,7 @@ testTxSetWithFeeBumps(uint32 protocolVersion) { auto tx1 = transaction(*app, account1, 1, 1, 100); auto fb1 = feeBump(*app, account2, tx1, minBalance2); - TxSetTransactions invalidTxs; + TxFrameList invalidTxs; auto txSet = makeTxSetFromTransactions({fb1}, *app, 0, 0, invalidTxs); compareTxs(invalidTxs, {fb1}); @@ -564,7 +564,7 @@ testTxSetWithFeeBumps(uint32 protocolVersion) auto fb1 = feeBump(*app, account2, tx1, minBalance2); auto tx2 = transaction(*app, account1, 2, 1, 100); auto fb2 = feeBump(*app, account2, tx2, 200); - TxSetTransactions invalidTxs; + TxFrameList invalidTxs; auto txSet = makeTxSetFromTransactions({fb1, fb2}, *app, 0, 0, invalidTxs); compareTxs(invalidTxs, {fb1, fb2}); @@ -578,7 +578,7 @@ testTxSetWithFeeBumps(uint32 protocolVersion) auto fb1 = feeBump(*app, account2, tx1, 200); auto tx2 = transaction(*app, account1, 2, 1, 100); auto fb2 = feeBump(*app, account2, tx2, minBalance2); - TxSetTransactions invalidTxs; + TxFrameList invalidTxs; auto txSet = makeTxSetFromTransactions({fb1, fb2}, *app, 0, 0, invalidTxs); compareTxs(invalidTxs, {fb2}); @@ -594,7 +594,7 @@ testTxSetWithFeeBumps(uint32 protocolVersion) auto tx2 = transaction(*app, account1, 2, -1, 100); auto fb2 = feeBump(*app, account2, tx2, minBalance2 - minBalance0 - 199); - TxSetTransactions invalidTxs; + TxFrameList invalidTxs; auto txSet = makeTxSetFromTransactions({fb1, fb2}, *app, 0, 0, invalidTxs); compareTxs(invalidTxs, {fb2}); @@ -607,7 +607,7 @@ testTxSetWithFeeBumps(uint32 protocolVersion) auto fb1 = feeBump(*app, account2, tx1, 200); auto tx2 = transaction(*app, account2, 1, 1, 100); auto fb2 = feeBump(*app, account2, tx2, minBalance2); - TxSetTransactions invalidTxs; + TxFrameList invalidTxs; auto txSet = makeTxSetFromTransactions({fb1, fb2}, *app, 0, 0, invalidTxs); compareTxs(invalidTxs, {fb2}); @@ -621,7 +621,7 @@ testTxSetWithFeeBumps(uint32 protocolVersion) auto tx2 = transaction(*app, account2, 1, -1, 100); auto fb2 = feeBump(*app, account2, tx2, minBalance2 - minBalance0 - 199); - TxSetTransactions invalidTxs; + TxFrameList invalidTxs; auto txSet = makeTxSetFromTransactions({fb1, fb2}, *app, 0, 0, invalidTxs); compareTxs(invalidTxs, {fb2}); @@ -637,7 +637,7 @@ testTxSetWithFeeBumps(uint32 protocolVersion) auto tx3 = transaction(*app, account1, 3, 1, 100); auto fb3 = feeBump(*app, account2, tx3, minBalance2 - minBalance0 - 199); - TxSetTransactions invalidTxs; + TxFrameList invalidTxs; auto txSet = makeTxSetFromTransactions({fb1, fb2, fb3}, *app, 0, 0, invalidTxs); compareTxs(invalidTxs, {fb2, fb3}); @@ -647,10 +647,6 @@ testTxSetWithFeeBumps(uint32 protocolVersion) TEST_CASE("txset", "[herder][txset]") { - SECTION("protocol 13") - { - testTxSet(13); - } SECTION("generalized tx set protocol") { testTxSet(static_cast(SOROBAN_PROTOCOL_VERSION)); @@ -675,7 +671,7 @@ TEST_CASE_VERSIONS("txset with PreconditionsV2", "[herder][txset]") for_versions_to(18, *app, [&] { auto checkTxSupport = [&](PreconditionsV2 const& c) { auto tx = transactionWithV2Precondition(*app, a1, 1, 100, c); - TxSetTransactions invalidTxs; + TxFrameList invalidTxs; auto txSet = makeTxSetFromTransactions({tx}, *app, 0, 0, invalidTxs); REQUIRE(invalidTxs.size() == 1); @@ -748,7 +744,7 @@ TEST_CASE_VERSIONS("txset with PreconditionsV2", "[herder][txset]") auto tx2InvalidGap = transactionWithV2Precondition( *app, a1, 5, 100, minSeqNumCond(a1.getLastSequenceNumber() + 2)); - TxSetTransactions removed; + TxFrameList removed; auto txSet = makeTxSetFromTransactions({tx1, tx2InvalidGap}, *app, 0, 0, removed); REQUIRE(removed.back() == tx2InvalidGap); @@ -799,7 +795,7 @@ TEST_CASE_VERSIONS("txset with PreconditionsV2", "[herder][txset]") auto txInvalid = transactionWithV2Precondition( *app, a1, 1, 100, minSeqLedgerGapCond(minGap + 2)); - TxSetTransactions removed; + TxFrameList removed; auto txSet = makeTxSetFromTransactions({txInvalid}, *app, 0, 0, removed) .second; @@ -882,7 +878,7 @@ TEST_CASE_VERSIONS("txset with PreconditionsV2", "[herder][txset]") auto txInvalid = transactionWithV2Precondition( *app, a1, 1, 100, minSeqAgeCond(minGap + 1)); - TxSetTransactions removed; + TxFrameList removed; auto txSet = makeTxSetFromTransactions({txInvalid}, *app, 0, 0, removed) .second; @@ -952,7 +948,7 @@ TEST_CASE_VERSIONS("txset with PreconditionsV2", "[herder][txset]") { auto txInvalid = transactionWithV2Precondition( *app, a1, 2, 100, ledgerBoundsCond(lclNum + 2, 0)); - TxSetTransactions removed; + TxFrameList removed; auto txSet = makeTxSetFromTransactions({tx1, txInvalid}, *app, 0, 0, removed); REQUIRE(removed.back() == txInvalid); @@ -970,7 +966,7 @@ TEST_CASE_VERSIONS("txset with PreconditionsV2", "[herder][txset]") { auto txInvalid = transactionWithV2Precondition( *app, a1, 2, 100, ledgerBoundsCond(0, lclNum)); - TxSetTransactions removed; + TxFrameList removed; auto txSet = makeTxSetFromTransactions({tx1, txInvalid}, *app, 0, 0, removed); REQUIRE(removed.back() == txInvalid); @@ -1000,14 +996,14 @@ TEST_CASE_VERSIONS("txset with PreconditionsV2", "[herder][txset]") SECTION("success") { tx->addSignature(root.getSecretKey()); - TxSetTransactions removed; + TxFrameList removed; auto txSet = makeTxSetFromTransactions({tx}, *app, 0, 0, removed); REQUIRE(removed.empty()); } SECTION("fail") { - TxSetTransactions removed; + TxFrameList removed; auto txSet = makeTxSetFromTransactions({tx}, *app, 0, 0, removed); REQUIRE(removed.back() == tx); @@ -1028,14 +1024,14 @@ TEST_CASE_VERSIONS("txset with PreconditionsV2", "[herder][txset]") SECTION("success") { tx->addSignature(a2.getSecretKey()); - TxSetTransactions removed; + TxFrameList removed; auto txSet = makeTxSetFromTransactions({tx}, *app, 0, 0, removed); REQUIRE(removed.empty()); } SECTION("fail") { - TxSetTransactions removed; + TxFrameList removed; auto txSet = makeTxSetFromTransactions({tx}, *app, 0, 0, removed); REQUIRE(removed.back() == tx); @@ -1047,7 +1043,7 @@ TEST_CASE_VERSIONS("txset with PreconditionsV2", "[herder][txset]") auto txDupeSigner = transactionWithV2Precondition(*app, a1, 1, 100, cond); txDupeSigner->addSignature(root.getSecretKey()); - TxSetTransactions removed; + TxFrameList removed; auto txSet = makeTxSetFromTransactions({txDupeSigner}, *app, 0, 0, removed); REQUIRE(removed.back() == txDupeSigner); @@ -1057,7 +1053,7 @@ TEST_CASE_VERSIONS("txset with PreconditionsV2", "[herder][txset]") { auto rootTx = transactionWithV2Precondition(*app, root, 1, 100, cond); - TxSetTransactions removed; + TxFrameList removed; auto txSet = makeTxSetFromTransactions({rootTx}, *app, 0, 0, removed); REQUIRE(removed.empty()); @@ -1072,14 +1068,14 @@ TEST_CASE_VERSIONS("txset with PreconditionsV2", "[herder][txset]") { tx->addSignature(root.getSecretKey()); - TxSetTransactions removed; + TxFrameList removed; auto txSet = makeTxSetFromTransactions({tx}, *app, 0, 0, removed); REQUIRE(removed.empty()); } SECTION("signature missing") { - TxSetTransactions removed; + TxFrameList removed; auto txSet = makeTxSetFromTransactions({tx}, *app, 0, 0, removed); REQUIRE(removed.back() == tx); @@ -1095,7 +1091,7 @@ TEST_CASE_VERSIONS("txset with PreconditionsV2", "[herder][txset]") {root.op(payment(a1, 1))}, {root}, cond); - TxSetTransactions removed; + TxFrameList removed; auto txSet = makeTxSetFromTransactions({tx}, *app, 0, 0, removed); REQUIRE(removed.empty()); @@ -1197,59 +1193,29 @@ TEST_CASE("txset base fee", "[herder][txset]") // 1 op, fee bid = baseFee*10 = 1000 // extra tx // 2 ops, fee bid = 20000+i - // to reach 112 - // protocol 10 adds 104 tx (208 ops) - // protocol 11 adds 52 tx (104 ops) + // should add 52 tx (104 ops) - // v11: surge threshold is 112-100=12 ops - // no surge pricing @ 10 (only 1 extra tx) + // surge threshold is 112-100=12 ops // surge pricing @ 12 (2 extra tx) uint32 const baseCount = 8; - uint32 const v10ExtraTx = 104; - uint32 const v11ExtraTx = 52; - uint32 const v10NewCount = 112; - uint32 const v11NewCount = 56; // 112/2 + uint32 const extraTx = 52; + uint32 const newCount = 56; // 112/2 SECTION("surged") { SECTION("mixed") { - SECTION("protocol 10") - { - // low = base tx - // high = last extra tx - testBaseFee(10, baseCount, v10ExtraTx, maxTxSetSize, 1000, - 20104); - } - SECTION("protocol before generalized tx set") - { - // low = 10*base tx = baseFee = 1000 - // high = 2*base (surge) - SECTION("maxed out surged") - { - testBaseFee( - static_cast(SOROBAN_PROTOCOL_VERSION) - 1, - baseCount, v11ExtraTx, maxTxSetSize, 1000, 2000); - } - SECTION("smallest surged") - { - testBaseFee( - static_cast(SOROBAN_PROTOCOL_VERSION) - 1, - baseCount + 1, v11ExtraTx - 50, maxTxSetSize - 100 + 1, - 1000, 2000); - } - } SECTION("generalized tx set protocol") { SECTION("fitting exactly into capacity does not cause surge") { testBaseFee(static_cast(SOROBAN_PROTOCOL_VERSION), - baseCount, v11ExtraTx, maxTxSetSize, 100, 200); + baseCount, extraTx, maxTxSetSize, 100, 200); } SECTION("evicting one tx causes surge") { testBaseFee(static_cast(SOROBAN_PROTOCOL_VERSION), - baseCount + 1, v11ExtraTx, maxTxSetSize, 1000, + baseCount + 1, extraTx, maxTxSetSize, 1000, 2000, 1); } } @@ -1264,13 +1230,13 @@ TEST_CASE("txset base fee", "[herder][txset]") { testBaseFee( static_cast(SOROBAN_PROTOCOL_VERSION), - baseCount, v11ExtraTx, maxTxSetSize, 100, 200); + baseCount, extraTx, maxTxSetSize, 100, 200); } SECTION("evicting one tx causes surge") { testBaseFee( static_cast(SOROBAN_PROTOCOL_VERSION), - baseCount + 1, v11ExtraTx, maxTxSetSize, 1000, 2000, + baseCount + 1, extraTx, maxTxSetSize, 1000, 2000, 1); } } @@ -1280,45 +1246,31 @@ TEST_CASE("txset base fee", "[herder][txset]") { testBaseFee( static_cast(SOROBAN_PROTOCOL_VERSION) - 1, - baseCount, v11ExtraTx, maxTxSetSize, 1000, 2000); + baseCount, extraTx, maxTxSetSize, 1000, 2000); } SECTION("smallest surged") { testBaseFee( static_cast(SOROBAN_PROTOCOL_VERSION) - 1, - baseCount + 1, v11ExtraTx - 50, - maxTxSetSize - 100 + 1, 1000, 2000); + baseCount + 1, extraTx - 50, maxTxSetSize - 100 + 1, + 1000, 2000); } } } } SECTION("newOnly") { - SECTION("protocol 10") - { - // low = 20000+1 - // high = 20000+112 - testBaseFee(10, 0, v10NewCount, maxTxSetSize, 20001, 20112); - } - SECTION("protocol before generalized tx set") - { - // low = 20000+1 -> baseFee = 20001/2+ = 10001 - // high = 10001*2 - testBaseFee(static_cast(SOROBAN_PROTOCOL_VERSION) - 1, - 0, v11NewCount, maxTxSetSize, 20001, 20002); - } SECTION("generalized tx set protocol") { SECTION("fitting exactly into capacity does not cause surge") { testBaseFee(static_cast(SOROBAN_PROTOCOL_VERSION), - 0, v11NewCount, maxTxSetSize, 200, 200); + 0, newCount, maxTxSetSize, 200, 200); } SECTION("evicting one tx causes surge") { testBaseFee(static_cast(SOROBAN_PROTOCOL_VERSION), - 0, v11NewCount + 1, maxTxSetSize, 20002, 20002, - 1); + 0, newCount + 1, maxTxSetSize, 20002, 20002, 1); } } SECTION("protocol current") @@ -1331,12 +1283,12 @@ TEST_CASE("txset base fee", "[herder][txset]") "fitting exactly into capacity does not cause surge") { testBaseFee(Config::CURRENT_LEDGER_PROTOCOL_VERSION, 0, - v11NewCount, maxTxSetSize, 200, 200); + newCount, maxTxSetSize, 200, 200); } SECTION("evicting one tx causes surge") { testBaseFee(Config::CURRENT_LEDGER_PROTOCOL_VERSION, 0, - v11NewCount + 1, maxTxSetSize, 20002, 20002, + newCount + 1, maxTxSetSize, 20002, 20002, 1); } } @@ -1344,7 +1296,7 @@ TEST_CASE("txset base fee", "[herder][txset]") { testBaseFee( static_cast(SOROBAN_PROTOCOL_VERSION) - 1, 0, - v11NewCount, maxTxSetSize, 20001, 20002); + newCount, maxTxSetSize, 20001, 20002); } } } @@ -1353,36 +1305,24 @@ TEST_CASE("txset base fee", "[herder][txset]") { SECTION("mixed") { - SECTION("protocol 10") - { - // low = 1000 - // high = 20000+4 - testBaseFee(10, baseCount, 4, baseCount + 4, 1000, 20004); - } SECTION("protocol current") { // baseFee = minFee = 100 // high = 2*minFee // highest number of ops not surged is max-100 testBaseFee(Config::CURRENT_LEDGER_PROTOCOL_VERSION, baseCount, - v11ExtraTx - 50, maxTxSetSize - 100, 100, 200); + extraTx - 50, maxTxSetSize - 100, 100, 200); } } SECTION("newOnly") { - SECTION("protocol 10") - { - // low = 20000+1 - // high = 20000+12 - testBaseFee(10, 0, 12, 12, 20001, 20012); - } SECTION("protocol current") { // low = minFee = 100 // high = 2*minFee // highest number of ops not surged is max-100 testBaseFee(Config::CURRENT_LEDGER_PROTOCOL_VERSION, 0, - v11NewCount - 50, maxTxSetSize - 100, 200, 200); + newCount - 50, maxTxSetSize - 100, 200, 200); } } } @@ -1392,152 +1332,6 @@ using std::placeholders::_1; using std::placeholders::_2; using std::placeholders::_3; -static void -surgeTest(uint32 protocolVersion, uint32_t nbTxs, uint32_t maxTxSetSize, - uint32_t expectedReduced) -{ - if (protocolVersion >= static_cast(SOROBAN_PROTOCOL_VERSION)) - { - throw std::runtime_error("Surge test does not apply post protocol 19"); - } - Config cfg(getTestConfig()); - cfg.TESTING_UPGRADE_MAX_TX_SET_SIZE = maxTxSetSize; - cfg.TESTING_UPGRADE_LEDGER_PROTOCOL_VERSION = protocolVersion; - VirtualClock clock; - Application::pointer app = createTestApplication(clock, cfg); - - LedgerHeader lhCopy; - { - LedgerTxn ltx(app->getLedgerTxnRoot()); - lhCopy = ltx.loadHeader().current(); - } - - // set up world - auto root = TestAccount::createRoot(*app); - - auto destAccount = root.create("destAccount", 500000000); - auto accountB = root.create("accountB", 5000000000); - auto accountC = root.create("accountC", 5000000000); - - auto multiPaymentTx = - std::bind(makeMultiPayment, destAccount, _1, _2, _3, 0, 100); - - auto refSeqNumRoot = root.getLastSequenceNumber(); - std::vector rootTxs; - for (uint32_t n = 0; n < 2 * nbTxs; n++) - { - rootTxs.push_back(multiPaymentTx(root, n + 1, 10000 + 1000 * n)); - } - - SECTION("basic single account") - { - auto txSet = makeTxSetFromTransactions(rootTxs, *app, 0, 0).second; - REQUIRE(txSet->size(lhCopy) == cfg.TESTING_UPGRADE_MAX_TX_SET_SIZE); - // check that the expected tx are there - auto txs = txSet->getTxsInApplyOrder(); - for (auto& tx : txs) - { - refSeqNumRoot++; - REQUIRE(tx->getSeqNum() == refSeqNumRoot); - } - } - - SECTION("one account paying more") - { - for (uint32_t n = 0; n < nbTxs; n++) - { - auto tx = multiPaymentTx(accountB, n + 1, 10000 + 1000 * n); - setFullFee(tx, static_cast(tx->getFullFee()) - 1); - getSignatures(tx).clear(); - tx->addSignature(accountB); - rootTxs.push_back(tx); - } - auto txSet = makeTxSetFromTransactions(rootTxs, *app, 0, 0).second; - REQUIRE(txSet->size(lhCopy) == cfg.TESTING_UPGRADE_MAX_TX_SET_SIZE); - // check that the expected tx are there - for (auto const& tx : txSet->getTxsForPhase(TxSetPhase::CLASSIC)) - { - REQUIRE(tx->getSourceID() == root.getPublicKey()); - } - } - - SECTION("one account with more operations but same total fee") - { - for (uint32_t n = 0; n < nbTxs; n++) - { - auto tx = multiPaymentTx(accountB, n + 2, 10000 + 1000 * n); - // find corresponding root tx (should have 1 less op) - auto rTx = rootTxs[n]; - REQUIRE(rTx->getNumOperations() == n + 1); - REQUIRE(tx->getNumOperations() == n + 2); - // use the same fee - setFullFee(tx, static_cast(rTx->getFullFee())); - getSignatures(tx).clear(); - tx->addSignature(accountB); - rootTxs.push_back(tx); - } - auto txSet = makeTxSetFromTransactions(rootTxs, *app, 0, 0).second; - REQUIRE(txSet->size(lhCopy) == cfg.TESTING_UPGRADE_MAX_TX_SET_SIZE); - // check that the expected tx are there - for (auto const& tx : txSet->getTxsForPhase(TxSetPhase::CLASSIC)) - { - REQUIRE(tx->getSourceID() == root.getPublicKey()); - } - } - - SECTION("one account paying more except for one tx in middle") - { - auto refSeqNumB = accountB.getLastSequenceNumber(); - for (uint32_t n = 0; n < nbTxs; n++) - { - auto tx = multiPaymentTx(accountB, n + 1, 10000 + 1000 * n); - if (n == 2) - { - setFullFee(tx, static_cast(tx->getFullFee()) - 1); - } - else - { - setFullFee(tx, static_cast(tx->getFullFee()) + 1); - } - getSignatures(tx).clear(); - tx->addSignature(accountB); - rootTxs.push_back(tx); - } - auto txSet = makeTxSetFromTransactions(rootTxs, *app, 0, 0).second; - REQUIRE(txSet->size(lhCopy) == expectedReduced); - // check that the expected tx are there - int nbAccountB = 0; - for (auto const& tx : txSet->getTxsInApplyOrder()) - { - if (tx->getSourceID() == accountB.getPublicKey()) - { - nbAccountB++; - refSeqNumB++; - REQUIRE(tx->getSeqNum() == refSeqNumB); - } - else - { - refSeqNumRoot++; - REQUIRE(tx->getSeqNum() == refSeqNumRoot); - } - } - REQUIRE(nbAccountB == 2); - } - - SECTION("a lot of txs") - { - for (uint32_t n = 0; n < nbTxs * 10; n++) - { - rootTxs.push_back(root.tx({payment(destAccount, n + 10)})); - rootTxs.push_back(accountB.tx({payment(destAccount, n + 10)})); - rootTxs.push_back(accountC.tx({payment(destAccount, n + 10)})); - } - auto txSet = makeTxSetFromTransactions(rootTxs, *app, 0, 0).second; - REQUIRE(txSet->size(lhCopy) == cfg.TESTING_UPGRADE_MAX_TX_SET_SIZE); - REQUIRE(txSet->checkValid(*app, 0, 0)); - } -} - TEST_CASE("tx set hits overlay byte limit during construction", "[transactionqueue][soroban]") { @@ -1584,7 +1378,7 @@ TEST_CASE("tx set hits overlay byte limit during construction", }; auto testPhaseWithOverlayLimit = [&](TxSetPhase const& phase) { - TxSetTransactions txs; + TxFrameList txs; size_t totalSize = 0; int txCount = 0; @@ -1595,17 +1389,17 @@ TEST_CASE("tx set hits overlay byte limit during construction", totalSize += xdr::xdr_size(txs.back()->getEnvelope()); } - TxSetPhaseTransactions invalidPhases; + PerPhaseTransactionList invalidPhases; invalidPhases.resize(static_cast(TxSetPhase::PHASE_COUNT)); - TxSetPhaseTransactions phases; + PerPhaseTransactionList phases; if (phase == TxSetPhase::SOROBAN) { - phases = TxSetPhaseTransactions{{}, txs}; + phases = PerPhaseTransactionList{{}, txs}; } else { - phases = TxSetPhaseTransactions{txs, {}}; + phases = PerPhaseTransactionList{txs, {}}; } auto [txSet, applicableTxSet] = @@ -1613,7 +1407,7 @@ TEST_CASE("tx set hits overlay byte limit during construction", REQUIRE(txSet->encodedSize() <= MAX_MESSAGE_SIZE); REQUIRE(invalidPhases[static_cast(phase)].empty()); - auto const& phaseTxs = applicableTxSet->getTxsForPhase(phase); + auto const& phaseTxs = applicableTxSet->getPhase(phase); auto trimmedSize = std::accumulate(phaseTxs.begin(), phaseTxs.end(), size_t(0), [&](size_t a, TransactionFrameBasePtr const& tx) { @@ -1639,11 +1433,6 @@ TEST_CASE("tx set hits overlay byte limit during construction", TEST_CASE("surge pricing", "[herder][txset][soroban]") { - SECTION("protocol 19") - { - // (1+..+4) + (1+2) = 10+3 = 13 - surgeTest(19, 5, 15, 13); - } SECTION("max 0 ops per ledger") { Config cfg(getTestConfig()); @@ -1659,7 +1448,7 @@ TEST_CASE("surge pricing", "[herder][txset][soroban]") { auto tx = makeMultiPayment(destAccount, root, 1, 100, 0, 1); - TxSetTransactions invalidTxs; + TxFrameList invalidTxs; auto txSet = makeTxSetFromTransactions({tx}, *app, 0, 0, invalidTxs).second; @@ -1677,10 +1466,10 @@ TEST_CASE("surge pricing", "[herder][txset][soroban]") auto sorobanTx = createUploadWasmTx( *app, root, baseFee, DEFAULT_TEST_RESOURCE_FEE, resources); - TxSetPhaseTransactions invalidTxs; + PerPhaseTransactionList invalidTxs; invalidTxs.resize(static_cast(TxSetPhase::PHASE_COUNT)); auto txSet = makeTxSetFromTransactions( - TxSetPhaseTransactions{{}, {sorobanTx}}, *app, 0, + PerPhaseTransactionList{{}, {sorobanTx}}, *app, 0, 0, invalidTxs) .second; @@ -1733,7 +1522,7 @@ TEST_CASE("surge pricing", "[herder][txset][soroban]") auto generateTxs = [&](std::vector& accounts, SorobanNetworkConfig conf) { - TxSetTransactions txs; + TxFrameList txs; for (auto& acc : accounts) { SorobanResources res; @@ -1798,10 +1587,10 @@ TEST_CASE("surge pricing", "[herder][txset][soroban]") invalidSoroban = createUploadWasmTx( *app, acc2, baseFee, DEFAULT_TEST_RESOURCE_FEE, resources); } - TxSetPhaseTransactions invalidPhases; + PerPhaseTransactionList invalidPhases; invalidPhases.resize(static_cast(TxSetPhase::PHASE_COUNT)); auto txSet = makeTxSetFromTransactions( - TxSetPhaseTransactions{{tx}, {invalidSoroban}}, + PerPhaseTransactionList{{tx}, {invalidSoroban}}, *app, 0, 0, invalidPhases) .second; @@ -1814,11 +1603,11 @@ TEST_CASE("surge pricing", "[herder][txset][soroban]") } SECTION("classic and soroban fit") { - TxSetPhaseTransactions invalidPhases; + PerPhaseTransactionList invalidPhases; invalidPhases.resize(static_cast(TxSetPhase::PHASE_COUNT)); auto txSet = makeTxSetFromTransactions( - TxSetPhaseTransactions{{tx}, {sorobanTx}}, *app, 0, - 0, invalidPhases) + PerPhaseTransactionList{{tx}, {sorobanTx}}, *app, + 0, 0, invalidPhases) .second; // Everything fits @@ -1828,11 +1617,11 @@ TEST_CASE("surge pricing", "[herder][txset][soroban]") } SECTION("classic and soroban in the same phase are rejected") { - TxSetPhaseTransactions invalidPhases; + PerPhaseTransactionList invalidPhases; invalidPhases.resize(1); REQUIRE_THROWS_AS(makeTxSetFromTransactions( - TxSetPhaseTransactions{{tx, sorobanTx}}, *app, - 0, 0, invalidPhases), + PerPhaseTransactionList{{tx, sorobanTx}}, + *app, 0, 0, invalidPhases), std::runtime_error); } SECTION("soroban surge pricing, classic unaffected") @@ -1840,21 +1629,23 @@ TEST_CASE("surge pricing", "[herder][txset][soroban]") // Another soroban tx with higher fee, which will be selected auto sorobanTxHighFee = createUploadWasmTx( *app, acc3, baseFee * 2, DEFAULT_TEST_RESOURCE_FEE, resources); - TxSetPhaseTransactions invalidPhases; + PerPhaseTransactionList invalidPhases; invalidPhases.resize(static_cast(TxSetPhase::PHASE_COUNT)); - auto txSet = - makeTxSetFromTransactions( - TxSetPhaseTransactions{{tx}, {sorobanTx, sorobanTxHighFee}}, - *app, 0, 0, invalidPhases) - .second; + auto txSet = makeTxSetFromTransactions( + PerPhaseTransactionList{ + {tx}, {sorobanTx, sorobanTxHighFee}}, + *app, 0, 0, invalidPhases) + .second; REQUIRE(std::all_of(invalidPhases.begin(), invalidPhases.end(), [](auto const& txs) { return txs.empty(); })); REQUIRE(txSet->sizeTxTotal() == 2); - auto const& classicTxs = txSet->getTxsForPhase(TxSetPhase::CLASSIC); + auto const& classicTxs = + txSet->getPhase(TxSetPhase::CLASSIC).getNonParallelTxs(); REQUIRE(classicTxs.size() == 1); REQUIRE(classicTxs[0]->getFullHash() == tx->getFullHash()); - auto const& sorobanTxs = txSet->getTxsForPhase(TxSetPhase::SOROBAN); + auto const& sorobanTxs = + txSet->getPhase(TxSetPhase::SOROBAN).getNonParallelTxs(); REQUIRE(sorobanTxs.size() == 1); REQUIRE(sorobanTxs[0]->getFullHash() == sorobanTxHighFee->getFullHash()); @@ -1876,11 +1667,11 @@ TEST_CASE("surge pricing", "[herder][txset][soroban]") auto smallSorobanLowFee = createUploadWasmTx( *app, acc4, baseFee / 10, DEFAULT_TEST_RESOURCE_FEE, resources); - TxSetPhaseTransactions invalidPhases; + PerPhaseTransactionList invalidPhases; invalidPhases.resize(static_cast(TxSetPhase::PHASE_COUNT)); auto txSet = makeTxSetFromTransactions( - TxSetPhaseTransactions{ + PerPhaseTransactionList{ {tx}, {sorobanTxHighFee, smallSorobanLowFee, sorobanTx}}, *app, 0, 0, invalidPhases) @@ -1889,10 +1680,11 @@ TEST_CASE("surge pricing", "[herder][txset][soroban]") REQUIRE(std::all_of(invalidPhases.begin(), invalidPhases.end(), [](auto const& txs) { return txs.empty(); })); REQUIRE(txSet->sizeTxTotal() == 3); - auto const& classicTxs = txSet->getTxsForPhase(TxSetPhase::CLASSIC); + auto const& classicTxs = + txSet->getPhase(TxSetPhase::CLASSIC).getNonParallelTxs(); REQUIRE(classicTxs.size() == 1); REQUIRE(classicTxs[0]->getFullHash() == tx->getFullHash()); - for (auto const& t : txSet->getTxsForPhase(TxSetPhase::SOROBAN)) + for (auto const& t : txSet->getPhase(TxSetPhase::SOROBAN)) { // smallSorobanLowFee was picked over sorobanTx to fill the gap bool pickedGap = @@ -1908,11 +1700,11 @@ TEST_CASE("surge pricing", "[herder][txset][soroban]") { SECTION("iteration " + std::to_string(i)) { - TxSetPhaseTransactions invalidPhases; + PerPhaseTransactionList invalidPhases; invalidPhases.resize( static_cast(TxSetPhase::PHASE_COUNT)); auto txSet = makeTxSetFromTransactions( - TxSetPhaseTransactions{ + PerPhaseTransactionList{ {tx}, generateTxs(accounts, conf)}, *app, 0, 0, invalidPhases) .second; @@ -1921,9 +1713,11 @@ TEST_CASE("surge pricing", "[herder][txset][soroban]") invalidPhases.begin(), invalidPhases.end(), [](auto const& txs) { return txs.empty(); })); auto const& classicTxs = - txSet->getTxsForPhase(TxSetPhase::CLASSIC); + txSet->getPhase(TxSetPhase::CLASSIC) + .getNonParallelTxs(); auto const& sorobanTxs = - txSet->getTxsForPhase(TxSetPhase::SOROBAN); + txSet->getPhase(TxSetPhase::SOROBAN) + .getNonParallelTxs(); REQUIRE(classicTxs.size() == 1); REQUIRE(classicTxs[0]->getFullHash() == tx->getFullHash()); // Depending on resources generated for each tx, can only @@ -1936,7 +1730,7 @@ TEST_CASE("surge pricing", "[herder][txset][soroban]") } SECTION("tx sets over limits are invalid") { - TxSetTransactions txs = generateTxs(accounts, conf); + TxFrameList txs = generateTxs(accounts, conf); auto txSet = testtxset::makeNonValidatedGeneralizedTxSet( {{}, {std::make_pair(500, txs)}}, *app, @@ -1964,12 +1758,6 @@ TEST_CASE("surge pricing with DEX separation", "[herder][txset]") VirtualClock clock; Application::pointer app = createTestApplication(clock, cfg); - LedgerHeader lhCopy; - { - LedgerTxn ltx(app->getLedgerTxnRoot()); - lhCopy = ltx.loadHeader().current(); - } - auto root = TestAccount::createRoot(*app); auto accountA = root.create("accountA", 5000000000); @@ -1989,8 +1777,9 @@ TEST_CASE("surge pricing with DEX separation", "[herder][txset]") int64_t expectedDexBaseFee) { auto txSet = makeTxSetFromTransactions(txs, *app, 0, 0).second; size_t cntA = 0, cntB = 0, cntC = 0, cntD = 0; - auto resTxs = txSet->getTxsInApplyOrder(); - for (auto const& tx : resTxs) + auto const& phases = txSet->getPhasesInApplyOrder(); + + for (auto const& tx : phases[static_cast(TxSetPhase::CLASSIC)]) { if (tx->getSourceID() == accountA.getPublicKey()) { @@ -2017,7 +1806,7 @@ TEST_CASE("surge pricing with DEX separation", "[herder][txset]") REQUIRE(seqNumD == tx->getSeqNum()); } - auto baseFee = txSet->getTxBaseFee(tx, lhCopy); + auto baseFee = txSet->getTxBaseFee(tx); REQUIRE(baseFee); if (tx->hasDexOperations()) { @@ -2028,6 +1817,7 @@ TEST_CASE("surge pricing with DEX separation", "[herder][txset]") REQUIRE(*baseFee == expectedNonDexBaseFee); } } + REQUIRE(cntA == expectedTxsA); REQUIRE(cntB == expectedTxsB); REQUIRE(cntC == expectedTxsC); @@ -2197,19 +1987,21 @@ TEST_CASE("surge pricing with DEX separation holds invariants", auto txs = genTxs(txCountDistr(Catch::rng())); auto txSet = makeTxSetFromTransactions(txs, *app, 0, 0).second; - auto resTxs = txSet->getTxsInApplyOrder(); + auto const& phases = txSet->getPhasesInApplyOrder(); std::array opsCounts{}; std::array baseFees{}; - for (auto const& resTx : resTxs) + + for (auto const& resTx : + phases[static_cast(TxSetPhase::CLASSIC)]) { auto isDex = static_cast(resTx->hasDexOperations()); opsCounts[isDex] += resTx->getNumOperations(); - auto baseFee = txSet->getTxBaseFee(resTx, lhCopy); + auto baseFee = txSet->getTxBaseFee(resTx); REQUIRE(baseFee); if (baseFees[isDex] != 0) { - // All base fees should be the same among the transaction - // categories. + // All base fees should be the same among the + // transaction categories. REQUIRE(baseFees[isDex] == *baseFee); } else @@ -2217,6 +2009,7 @@ TEST_CASE("surge pricing with DEX separation holds invariants", baseFees[isDex] = *baseFee; } } + REQUIRE(opsCounts[0] + opsCounts[1] <= cfg.TESTING_UPGRADE_MAX_TX_SET_SIZE); if (maxDexOps) @@ -2272,6 +2065,7 @@ TEST_CASE("generalized tx set applied to ledger", "[herder][txset][soroban]") { Config cfg(getTestConfig()); cfg.ENABLE_SOROBAN_DIAGNOSTIC_EVENTS = true; + VirtualClock clock; Application::pointer app = createTestApplication(clock, cfg); auto root = TestAccount::createRoot(*app); @@ -2722,7 +2516,7 @@ testSCPDriver(uint32 protocolVersion, uint32_t maxTxSetSize, size_t expectedOps) tx->addSignature(root.getSecretKey()); auto [txSet, applicableTxSet] = testtxset::makeNonValidatedTxSetBasedOnLedgerVersion( - protocolVersion, {tx}, *app, + {tx}, *app, app->getLedgerManager().getLastClosedLedgerHeader().hash); // Build a StellarValue containing the transaction set we just @@ -2743,10 +2537,11 @@ testSCPDriver(uint32 protocolVersion, uint32_t maxTxSetSize, size_t expectedOps) // makeTxSetFromTransactions() trims the transaction if // and only if we expect it to be invalid. auto closeTimeOffset = nextCloseTime - lclCloseTime; - TxSetTransactions removed; + TxFrameList removed; TxSetUtils::trimInvalid( - applicableTxSet->getTxsForPhase(TxSetPhase::CLASSIC), *app, - closeTimeOffset, closeTimeOffset, removed); + applicableTxSet->getPhase(TxSetPhase::CLASSIC) + .getNonParallelTxs(), + *app, closeTimeOffset, closeTimeOffset, removed); REQUIRE(removed.size() == (expectValid ? 0 : 1)); }; @@ -3659,18 +3454,40 @@ TEST_CASE("soroban txs each parameter surge priced", "[soroban][herder]") lclHeader.scpValue.txSetHash); GeneralizedTransactionSet xdrTxSet; txSet->toXDR(xdrTxSet); - auto const& components = - xdrTxSet.v1TxSet() - .phases.at(static_cast(TxSetPhase::SOROBAN)) - .v0Components(); - if (!components.empty()) + auto const& phase = xdrTxSet.v1TxSet().phases.at( + static_cast(TxSetPhase::SOROBAN)); + std::optional baseFee; + switch (phase.v()) { - auto baseFee = - components.at(0).txsMaybeDiscountedFee().baseFee; - hadSorobanSurgePricing = hadSorobanSurgePricing || - (baseFee && *baseFee > 100); + case 0: + if (!phase.v0Components().empty() && + phase.v0Components() + .at(0) + .txsMaybeDiscountedFee() + .baseFee) + { + + baseFee = *phase.v0Components() + .at(0) + .txsMaybeDiscountedFee() + .baseFee; + } + break; +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION + case 1: + if (phase.parallelTxsComponent().baseFee) + { + baseFee = *phase.parallelTxsComponent().baseFee; + } + break; +#endif + default: + releaseAssert(false); } + hadSorobanSurgePricing = + hadSorobanSurgePricing || (baseFee && *baseFee > 100); + return loadGenDone.count() > currLoadGenCount && secondLoadGenDone.count() > secondLoadGenCount; }, @@ -4907,7 +4724,7 @@ externalize(SecretKey const& sk, LedgerManager& lm, HerderImpl& herder, auto classicTxs = txs; - TxSetTransactions sorobanTxs; + TxFrameList sorobanTxs; for (auto it = classicTxs.begin(); it != classicTxs.end();) { if ((*it)->isSoroban()) @@ -4921,7 +4738,7 @@ externalize(SecretKey const& sk, LedgerManager& lm, HerderImpl& herder, } } - TxSetPhaseTransactions txsPhases{classicTxs}; + PerPhaseTransactionList txsPhases{classicTxs}; txsPhases.emplace_back(sorobanTxs); @@ -4975,12 +4792,11 @@ TEST_CASE("do not flood invalid transactions", "[herder]") auto const& lhhe = lm.getLastClosedLedgerHeader(); auto txs = tq.getTransactions(lhhe.header); - auto txSet = makeTxSetFromTransactions(txs, *app, 0, 0).second; - REQUIRE(txSet->sizeTxTotal() == 1); - REQUIRE( - txSet->getTxsForPhase(TxSetPhase::CLASSIC).front()->getContentsHash() == - tx1a->getContentsHash()); - REQUIRE(txSet->checkValid(*app, 0, 0)); + auto [_, applicableTxSet] = makeTxSetFromTransactions(txs, *app, 0, 0); + REQUIRE(applicableTxSet->sizeTxTotal() == 1); + REQUIRE((*applicableTxSet->getPhase(TxSetPhase::CLASSIC).begin()) + ->getContentsHash() == tx1a->getContentsHash()); + REQUIRE(applicableTxSet->checkValid(*app, 0, 0)); } TEST_CASE("do not flood too many soroban transactions", @@ -6064,4 +5880,4 @@ TEST_CASE("SCP message capture from previous ledger", "[herder]") // check ensures that C does not double count messages from ledger 2 when // closing ledger 3. REQUIRE(checkSCPHistoryEntries(C, 2, expectedTypes)); -} \ No newline at end of file +} diff --git a/src/herder/test/TestTxSetUtils.cpp b/src/herder/test/TestTxSetUtils.cpp index e27c43c3d5..2f3eb41698 100644 --- a/src/herder/test/TestTxSetUtils.cpp +++ b/src/herder/test/TestTxSetUtils.cpp @@ -3,6 +3,7 @@ // of this distribution or at http://www.apache.org/licenses/LICENSE-2.0 #include "herder/test/TestTxSetUtils.h" +#include "ledger/LedgerManager.h" #include "ledger/LedgerTxn.h" #include "main/Application.h" #include "util/ProtocolVersion.h" @@ -30,13 +31,26 @@ makeTxSetXDR(std::vector const& txs, } GeneralizedTransactionSet -makeGeneralizedTxSetXDR(std::vector const& txsPerBaseFeePhases, - Hash const& previousLedgerHash) +makeGeneralizedTxSetXDR(std::vector const& phases, + Hash const& previousLedgerHash, + bool useParallelSorobanPhase) { GeneralizedTransactionSet xdrTxSet(1); - for (auto& txsPerBaseFee : txsPerBaseFeePhases) + for (size_t i = 0; i < phases.size(); ++i) { - auto normalizedTxsPerBaseFee = txsPerBaseFee; + releaseAssert(i < static_cast(TxSetPhase::PHASE_COUNT)); + auto const& phase = phases[i]; + bool isParallelSorobanPhase = false; +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION + if (useParallelSorobanPhase && + i == static_cast(TxSetPhase::SOROBAN)) + { + releaseAssert(phase.size() <= 1); + isParallelSorobanPhase = true; + } +#endif + + auto normalizedTxsPerBaseFee = phase; std::sort(normalizedTxsPerBaseFee.begin(), normalizedTxsPerBaseFee.end()); for (auto& [_, txs] : normalizedTxsPerBaseFee) @@ -45,19 +59,49 @@ makeGeneralizedTxSetXDR(std::vector const& txsPerBaseFeePhases, } xdrTxSet.v1TxSet().previousLedgerHash = previousLedgerHash; - auto& phase = xdrTxSet.v1TxSet().phases.emplace_back(); + auto& xdrPhase = xdrTxSet.v1TxSet().phases.emplace_back(); + if (isParallelSorobanPhase) + { + xdrPhase.v(1); + } for (auto const& [baseFee, txs] : normalizedTxsPerBaseFee) { - auto& component = phase.v0Components().emplace_back( - TXSET_COMP_TXS_MAYBE_DISCOUNTED_FEE); - if (baseFee) + if (isParallelSorobanPhase) { - component.txsMaybeDiscountedFee().baseFee.activate() = *baseFee; +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION + auto& component = xdrPhase.parallelTxsComponent(); + if (baseFee) + { + component.baseFee.activate() = *baseFee; + } + if (!txs.empty()) + { + auto& cluster = component.executionStages.emplace_back() + .emplace_back() + .emplace_back(); + for (auto const& tx : txs) + { + cluster.emplace_back(tx->getEnvelope()); + } + } +#else + releaseAssert(false); +#endif } - auto& componentTxs = component.txsMaybeDiscountedFee().txs; - for (auto const& tx : txs) + else { - componentTxs.emplace_back(tx->getEnvelope()); + auto& component = xdrPhase.v0Components().emplace_back( + TXSET_COMP_TXS_MAYBE_DISCOUNTED_FEE); + if (baseFee) + { + component.txsMaybeDiscountedFee().baseFee.activate() = + *baseFee; + } + auto& componentTxs = component.txsMaybeDiscountedFee().txs; + for (auto const& tx : txs) + { + componentTxs.emplace_back(tx->getEnvelope()); + } } } } @@ -80,17 +124,24 @@ makeNonValidatedGeneralizedTxSet( std::vector const& txsPerBaseFee, Application& app, Hash const& previousLedgerHash) { - auto xdrTxSet = makeGeneralizedTxSetXDR(txsPerBaseFee, previousLedgerHash); + bool useParallelSorobanPhase = protocolVersionStartsFrom( + app.getLedgerManager().getLastClosedLedgerHeader().header.ledgerVersion, + PARALLEL_SOROBAN_PHASE_PROTOCOL_VERSION); + auto xdrTxSet = makeGeneralizedTxSetXDR(txsPerBaseFee, previousLedgerHash, + useParallelSorobanPhase); auto txSet = TxSetXDRFrame::makeFromWire(xdrTxSet); return std::make_pair(txSet, txSet->prepareForApply(app)); } std::pair makeNonValidatedTxSetBasedOnLedgerVersion( - uint32_t ledgerVersion, std::vector const& txs, - Application& app, Hash const& previousLedgerHash) + std::vector const& txs, Application& app, + Hash const& previousLedgerHash) { - if (protocolVersionStartsFrom(ledgerVersion, SOROBAN_PROTOCOL_VERSION)) + if (protocolVersionStartsFrom(app.getLedgerManager() + .getLastClosedLedgerHeader() + .header.ledgerVersion, + SOROBAN_PROTOCOL_VERSION)) { return makeNonValidatedGeneralizedTxSet( {{std::make_pair(100LL, txs)}, {}}, app, previousLedgerHash); diff --git a/src/herder/test/TestTxSetUtils.h b/src/herder/test/TestTxSetUtils.h index be9b7eac1e..4aca1428bd 100644 --- a/src/herder/test/TestTxSetUtils.h +++ b/src/herder/test/TestTxSetUtils.h @@ -21,7 +21,7 @@ makeNonValidatedGeneralizedTxSet( std::pair makeNonValidatedTxSetBasedOnLedgerVersion( - uint32_t ledgerVersion, std::vector const& txs, - Application& app, Hash const& previousLedgerHash); + std::vector const& txs, Application& app, + Hash const& previousLedgerHash); } // namespace testtxset } // namespace stellar diff --git a/src/herder/test/TransactionQueueTests.cpp b/src/herder/test/TransactionQueueTests.cpp index c6c20be607..154e153a99 100644 --- a/src/herder/test/TransactionQueueTests.cpp +++ b/src/herder/test/TransactionQueueTests.cpp @@ -198,7 +198,7 @@ class TransactionQueueTest REQUIRE(fees == expectedFees); - TxSetTransactions expectedTxs; + TxFrameList expectedTxs; size_t totOps = 0; for (auto const& accountState : state.mAccountStates) { diff --git a/src/herder/test/TxSetTests.cpp b/src/herder/test/TxSetTests.cpp index 6e11b087e1..4149dd2b85 100644 --- a/src/herder/test/TxSetTests.cpp +++ b/src/herder/test/TxSetTests.cpp @@ -472,14 +472,17 @@ TEST_CASE("generalized tx set XDR validation", "[txset]") } } -TEST_CASE("generalized tx set XDR conversion", "[txset]") +void +testGeneralizedTxSetXDRConversion(ProtocolVersion protocolVersion) { VirtualClock clock; auto cfg = getTestConfig(); - cfg.LEDGER_PROTOCOL_VERSION = - static_cast(SOROBAN_PROTOCOL_VERSION); + cfg.LEDGER_PROTOCOL_VERSION = static_cast(protocolVersion); cfg.TESTING_UPGRADE_LEDGER_PROTOCOL_VERSION = - static_cast(SOROBAN_PROTOCOL_VERSION); + static_cast(protocolVersion); + bool isParallelSoroban = protocolVersionStartsFrom( + cfg.LEDGER_PROTOCOL_VERSION, PARALLEL_SOROBAN_PHASE_PROTOCOL_VERSION); + Application::pointer app = createTestApplication(clock, cfg); overrideSorobanNetworkConfigForTest(*app); modifySorobanNetworkConfig(*app, [](SorobanNetworkConfig& sorobanCfg) { @@ -523,6 +526,7 @@ TEST_CASE("generalized tx set XDR conversion", "[txset]") TransactionMode::READ_ONLY_WITHOUT_SQL_TXN); applicableFrame = txSetFrame->prepareForApply(*app); } + REQUIRE(applicableFrame->checkValid(*app, 0, 0)); GeneralizedTransactionSet newXdr; applicableFrame->toWireTxSetFrame()->toXDR(newXdr); @@ -650,17 +654,45 @@ TEST_CASE("generalized tx set XDR conversion", "[txset]") GeneralizedTransactionSet txSetXdr; txSet->toXDR(txSetXdr); REQUIRE(txSetXdr.v1TxSet().phases.size() == 2); - for (auto const& phase : txSetXdr.v1TxSet().phases) + for (auto i = 0; i < txSetXdr.v1TxSet().phases.size(); ++i) { + auto const& phase = txSetXdr.v1TxSet().phases[i]; + // Base inclusion fee is 100 for all phases since no // surge pricing kicked in - REQUIRE(phase.v0Components().size() == 1); - REQUIRE(*phase.v0Components()[0] - .txsMaybeDiscountedFee() - .baseFee == lclHeader.header.baseFee); - REQUIRE(phase.v0Components()[0] - .txsMaybeDiscountedFee() - .txs.size() == 5); + if (i == static_cast(TxSetPhase::SOROBAN) && + isParallelSoroban) + { +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION + REQUIRE(phase.v() == 1); + REQUIRE(*phase.parallelTxsComponent().baseFee == + lclHeader.header.baseFee); + REQUIRE(phase.parallelTxsComponent() + .executionStages.size() == 1); + REQUIRE(phase.parallelTxsComponent() + .executionStages[0] + .size() == 1); + REQUIRE(phase.parallelTxsComponent() + .executionStages[0][0] + .size() == 1); + REQUIRE(phase.parallelTxsComponent() + .executionStages[0][0][0] + .size() == 5); +#else + releaseAssert(false); +#endif + } + else + { + REQUIRE(phase.v() == 0); + REQUIRE(phase.v0Components().size() == 1); + REQUIRE(*phase.v0Components()[0] + .txsMaybeDiscountedFee() + .baseFee == lclHeader.header.baseFee); + REQUIRE(phase.v0Components()[0] + .txsMaybeDiscountedFee() + .txs.size() == 5); + } } checkXdrRoundtrip(txSetXdr); } @@ -679,19 +711,45 @@ TEST_CASE("generalized tx set XDR conversion", "[txset]") GeneralizedTransactionSet txSetXdr; txSet->toXDR(txSetXdr); REQUIRE(txSetXdr.v1TxSet().phases.size() == 2); - for (int i = 0; i < txSetXdr.v1TxSet().phases.size(); i++) + for (auto i = 0; i < txSetXdr.v1TxSet().phases.size(); ++i) { auto const& phase = txSetXdr.v1TxSet().phases[i]; auto expectedBaseFee = i == 0 ? lclHeader.header.baseFee : higherFeeSorobanTxs[0]->getInclusionFee(); - REQUIRE(phase.v0Components().size() == 1); - REQUIRE(*phase.v0Components()[0] - .txsMaybeDiscountedFee() - .baseFee == expectedBaseFee); - REQUIRE(phase.v0Components()[0] - .txsMaybeDiscountedFee() - .txs.size() == 5); + if (i == static_cast(TxSetPhase::SOROBAN) && + isParallelSoroban) + { +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION + REQUIRE(phase.v() == 1); + REQUIRE(*phase.parallelTxsComponent().baseFee == + expectedBaseFee); + REQUIRE(phase.parallelTxsComponent() + .executionStages.size() == 1); + REQUIRE(phase.parallelTxsComponent() + .executionStages[0] + .size() == 1); + REQUIRE(phase.parallelTxsComponent() + .executionStages[0][0] + .size() == 1); + REQUIRE(phase.parallelTxsComponent() + .executionStages[0][0][0] + .size() == 5); +#else + releaseAssert(false); +#endif + } + else + { + REQUIRE(phase.v() == 0); + REQUIRE(phase.v0Components().size() == 1); + REQUIRE(*phase.v0Components()[0] + .txsMaybeDiscountedFee() + .baseFee == expectedBaseFee); + REQUIRE(phase.v0Components()[0] + .txsMaybeDiscountedFee() + .txs.size() == 5); + } } checkXdrRoundtrip(txSetXdr); } @@ -716,15 +774,31 @@ TEST_CASE("generalized tx set XDR conversion", "[txset]") } } +TEST_CASE("generalized tx set XDR conversion", + "[txset]"){SECTION("soroban protocol version"){ + testGeneralizedTxSetXDRConversion(SOROBAN_PROTOCOL_VERSION); +} +SECTION("current protocol version") +{ + testGeneralizedTxSetXDRConversion( + static_cast(Config::CURRENT_LEDGER_PROTOCOL_VERSION)); +} +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION +SECTION("parallel soroban protocol version") +{ + testGeneralizedTxSetXDRConversion(PARALLEL_SOROBAN_PHASE_PROTOCOL_VERSION); +} +#endif +} + TEST_CASE("generalized tx set with multiple txs per source account", "[txset][soroban]") { VirtualClock clock; auto cfg = getTestConfig(); - cfg.LEDGER_PROTOCOL_VERSION = - static_cast(SOROBAN_PROTOCOL_VERSION); + cfg.LEDGER_PROTOCOL_VERSION = Config::CURRENT_LEDGER_PROTOCOL_VERSION; cfg.TESTING_UPGRADE_LEDGER_PROTOCOL_VERSION = - static_cast(SOROBAN_PROTOCOL_VERSION); + Config::CURRENT_LEDGER_PROTOCOL_VERSION; Application::pointer app = createTestApplication(clock, cfg); auto root = TestAccount::createRoot(*app); int accountId = 1; @@ -814,10 +888,9 @@ TEST_CASE("generalized tx set fees", "[txset][soroban]") { VirtualClock clock; auto cfg = getTestConfig(); - cfg.LEDGER_PROTOCOL_VERSION = - static_cast(SOROBAN_PROTOCOL_VERSION); + cfg.LEDGER_PROTOCOL_VERSION = Config::CURRENT_LEDGER_PROTOCOL_VERSION; cfg.TESTING_UPGRADE_LEDGER_PROTOCOL_VERSION = - static_cast(SOROBAN_PROTOCOL_VERSION); + Config::CURRENT_LEDGER_PROTOCOL_VERSION; Application::pointer app = createTestApplication(clock, cfg); overrideSorobanNetworkConfigForTest(*app); @@ -866,6 +939,35 @@ TEST_CASE("generalized tx set fees", "[txset][soroban]") SECTION("valid txset") { + testtxset::ComponentPhases sorobanTxs; + bool isParallelSoroban = + protocolVersionStartsFrom(Config::CURRENT_LEDGER_PROTOCOL_VERSION, + PARALLEL_SOROBAN_PHASE_PROTOCOL_VERSION); + if (isParallelSoroban) + { + sorobanTxs = {std::make_pair( + 1000, std::vector{ + createTx(1, 1250, /* isSoroban */ true), + createTx(1, 1000, /* isSoroban */ true), + createTx(1, 1200, /* isSoroban */ true)})}; + } + else + { + sorobanTxs = { + std::make_pair(500, + std::vector{ + createTx(1, 1000, /* isSoroban */ true), + createTx(1, 500, /* isSoroban */ true)}), + std::make_pair(1000, + std::vector{ + createTx(1, 1250, /* isSoroban */ true), + createTx(1, 1000, /* isSoroban */ true), + createTx(1, 1200, /* isSoroban */ true)}), + std::make_pair(std::nullopt, + std::vector{ + createTx(1, 5000, /* isSoroban */ true), + createTx(1, 20000, /* isSoroban */ true)})}; + } auto txSet = testtxset::makeNonValidatedGeneralizedTxSet( {{std::make_pair(500, @@ -878,20 +980,7 @@ TEST_CASE("generalized tx set fees", "[txset][soroban]") std::make_pair(std::nullopt, std::vector{ createTx(2, 10000), createTx(5, 100000)})}, - {std::make_pair(500, - std::vector{ - createTx(1, 1000, /* isSoroban */ true), - createTx(1, 500, /* isSoroban */ true)}), - std::make_pair(1000, - std::vector{ - createTx(1, 1250, /* isSoroban */ true), - createTx(1, 1000, /* isSoroban */ true), - createTx(1, 1200, /* isSoroban */ true)}), - std::make_pair( - std::nullopt, - std::vector{ - createTx(1, 5000, /* isSoroban */ true), - createTx(1, 20000, /* isSoroban */ true)})}}, + sorobanTxs}, *app, app->getLedgerManager().getLastClosedLedgerHeader().hash) .second; @@ -899,18 +988,23 @@ TEST_CASE("generalized tx set fees", "[txset][soroban]") for (auto i = 0; i < static_cast(TxSetPhase::PHASE_COUNT); ++i) { std::vector> fees; - for (auto const& tx : - txSet->getTxsForPhase(static_cast(i))) + for (auto const& tx : txSet->getPhase(static_cast(i))) { - fees.push_back( - txSet->getTxBaseFee(tx, app->getLedgerManager() - .getLastClosedLedgerHeader() - .header)); + fees.push_back(txSet->getTxBaseFee(tx)); } std::sort(fees.begin(), fees.end()); - REQUIRE(fees == std::vector>{ - std::nullopt, std::nullopt, 500, 500, 1000, - 1000, 1000}); + if (isParallelSoroban && + i == static_cast(TxSetPhase::SOROBAN)) + { + REQUIRE(fees == + std::vector>{1000, 1000, 1000}); + } + else + { + REQUIRE(fees == std::vector>{ + std::nullopt, std::nullopt, 500, 500, 1000, + 1000, 1000}); + } } } SECTION("tx with too low discounted fee") diff --git a/src/herder/test/UpgradesTests.cpp b/src/herder/test/UpgradesTests.cpp index bc785006f4..e9aadc9d47 100644 --- a/src/herder/test/UpgradesTests.cpp +++ b/src/herder/test/UpgradesTests.cpp @@ -1947,11 +1947,10 @@ TEST_CASE("upgrade to version 11", "[upgrades]") uint64_t minBalance = lm.getLastMinBalance(5); uint64_t big = minBalance + ledgerSeq; uint64_t closeTime = 60 * 5 * ledgerSeq; - auto txSet = makeTxSetFromTransactions( - TxSetTransactions{ - root.tx({txtest::createAccount(stranger, big)})}, - *app, 0, 0) - .first; + auto txSet = + makeTxSetFromTransactions( + {root.tx({txtest::createAccount(stranger, big)})}, *app, 0, 0) + .first; // On 4th iteration of advance (a.k.a. ledgerSeq 5), perform a // ledger-protocol version upgrade to the new protocol, to activate @@ -2073,9 +2072,7 @@ TEST_CASE("upgrade to version 12", "[upgrades]") uint64_t closeTime = 60 * 5 * ledgerSeq; TxSetXDRFrameConstPtr txSet = makeTxSetFromTransactions( - TxSetTransactions{ - root.tx({txtest::createAccount(stranger, big)})}, - *app, 0, 0) + {root.tx({txtest::createAccount(stranger, big)})}, *app, 0, 0) .first; // On 4th iteration of advance (a.k.a. ledgerSeq 5), perform a @@ -3213,7 +3210,7 @@ TEST_CASE("upgrade to generalized tx set changes TxSetFrame format", static_cast(SOROBAN_PROTOCOL_VERSION) - 1)); auto root = TestAccount::createRoot(*app); - TxSetTransactions txs = {root.tx({payment(root, 1)})}; + TxFrameList txs = {root.tx({payment(root, 1)})}; auto [txSet, applicableTxSet] = makeTxSetFromTransactions(txs, *app, 0, 0); REQUIRE(!txSet->isGeneralizedTxSet()); REQUIRE(!applicableTxSet->isGeneralizedTxSet()); diff --git a/src/history/test/HistoryTests.cpp b/src/history/test/HistoryTests.cpp index 7e4821f039..8269e965c2 100644 --- a/src/history/test/HistoryTests.cpp +++ b/src/history/test/HistoryTests.cpp @@ -1081,9 +1081,7 @@ TEST_CASE("Catchup non-initentry buckets to initentry-supporting works", uint64_t closeTime = 60 * 5 * ledgerSeq; auto [txSet, applicableTxSet] = makeTxSetFromTransactions( - TxSetTransactions{ - root.tx({txtest::createAccount(stranger, big)})}, - *a, 0, 0); + {root.tx({txtest::createAccount(stranger, big)})}, *a, 0, 0); // On first iteration of advance, perform a ledger-protocol version // upgrade to the new protocol, to activate INITENTRY behaviour. diff --git a/src/history/test/HistoryTestsUtils.cpp b/src/history/test/HistoryTestsUtils.cpp index 87889f97f6..1da678791a 100644 --- a/src/history/test/HistoryTestsUtils.cpp +++ b/src/history/test/HistoryTestsUtils.cpp @@ -501,8 +501,8 @@ CatchupSimulation::generateRandomLedger(uint32_t version) auto phases = protocolVersionStartsFrom( lm.getLastClosedLedgerHeader().header.ledgerVersion, SOROBAN_PROTOCOL_VERSION) - ? TxSetPhaseTransactions{txs, sorobanTxs} - : TxSetPhaseTransactions{txs}; + ? PerPhaseTransactionList{txs, sorobanTxs} + : PerPhaseTransactionList{txs}; TxSetXDRFrameConstPtr txSet = makeTxSetFromTransactions(phases, mApp, 0, 0).first; diff --git a/src/ledger/LedgerManagerImpl.cpp b/src/ledger/LedgerManagerImpl.cpp index 7a30739598..05c068a30d 100644 --- a/src/ledger/LedgerManagerImpl.cpp +++ b/src/ledger/LedgerManagerImpl.cpp @@ -891,19 +891,12 @@ LedgerManagerImpl::closeLedger(LedgerCloseData const& ledgerData) ledgerCloseMeta->populateTxSet(*txSet); } - // the transaction set that was agreed upon by consensus - // was sorted by hash; we reorder it so that transactions are - // sorted such that sequence numbers are respected - std::vector const txs = - applicableTxSet->getTxsInApplyOrder(); - // first, prefetch source accounts for txset, then charge fees - prefetchTxSourceIds(txs); - processFeesSeqNums(txs, ltx, *applicableTxSet, ledgerCloseMeta); + prefetchTxSourceIds(*applicableTxSet); + processFeesSeqNums(*applicableTxSet, ltx, ledgerCloseMeta); - TransactionResultSet txResultSet; - txResultSet.results.reserve(txs.size()); - applyTransactions(*applicableTxSet, txs, ltx, txResultSet, ledgerCloseMeta); + auto txResultSet = + applyTransactions(*applicableTxSet, ltx, ledgerCloseMeta); if (mApp.getConfig().MODE_STORES_HISTORY_MISC) { storeTxSet(mApp.getDatabase(), ltx.loadHeader().current().ledgerSeq, @@ -1339,8 +1332,7 @@ mergeOpInTx(std::vector const& ops) void LedgerManagerImpl::processFeesSeqNums( - std::vector const& txs, - AbstractLedgerTxn& ltxOuter, ApplicableTxSetFrame const& txSet, + ApplicableTxSetFrame const& txSet, AbstractLedgerTxn& ltxOuter, std::unique_ptr const& ledgerCloseMeta) { ZoneScoped; @@ -1354,49 +1346,52 @@ LedgerManagerImpl::processFeesSeqNums( std::map accToMaxSeq; bool mergeSeen = false; - for (auto tx : txs) + for (auto const& phase : txSet.getPhasesInApplyOrder()) { - LedgerTxn ltxTx(ltx); - tx->processFeeSeqNum(ltxTx, txSet.getTxBaseFee(tx, header)); - - if (protocolVersionStartsFrom( - ltxTx.loadHeader().current().ledgerVersion, - ProtocolVersion::V_19)) + for (auto const& tx : phase) { - auto res = - accToMaxSeq.emplace(tx->getSourceID(), tx->getSeqNum()); - if (!res.second) + LedgerTxn ltxTx(ltx); + tx->processFeeSeqNum(ltxTx, txSet.getTxBaseFee(tx)); + + if (protocolVersionStartsFrom( + ltxTx.loadHeader().current().ledgerVersion, + ProtocolVersion::V_19)) { - res.first->second = - std::max(res.first->second, tx->getSeqNum()); + auto res = + accToMaxSeq.emplace(tx->getSourceID(), tx->getSeqNum()); + if (!res.second) + { + res.first->second = + std::max(res.first->second, tx->getSeqNum()); + } + + if (mergeOpInTx(tx->getRawOperations())) + { + mergeSeen = true; + } } - if (mergeOpInTx(tx->getRawOperations())) + LedgerEntryChanges changes = ltxTx.getChanges(); + if (ledgerCloseMeta) { - mergeSeen = true; + ledgerCloseMeta->pushTxProcessingEntry(); + ledgerCloseMeta->setLastTxProcessingFeeProcessingChanges( + changes); } + // Note to future: when we eliminate the txhistory and + // txfeehistory tables, the following step can be removed. + // + // Also note: for historical reasons the history tables number + // txs counting from 1, not 0. We preserve this for the time + // being in case anyone depends on it. + ++index; + if (mApp.getConfig().MODE_STORES_HISTORY_MISC) + { + storeTransactionFee(mApp.getDatabase(), ledgerSeq, tx, + changes, index); + } + ltxTx.commit(); } - - LedgerEntryChanges changes = ltxTx.getChanges(); - if (ledgerCloseMeta) - { - ledgerCloseMeta->pushTxProcessingEntry(); - ledgerCloseMeta->setLastTxProcessingFeeProcessingChanges( - changes); - } - // Note to future: when we eliminate the txhistory and txfeehistory - // tables, the following step can be removed. - // - // Also note: for historical reasons the history tables number - // txs counting from 1, not 0. We preserve this for the time being - // in case anyone depends on it. - ++index; - if (mApp.getConfig().MODE_STORES_HISTORY_MISC) - { - storeTransactionFee(mApp.getDatabase(), ledgerSeq, tx, changes, - index); - } - ltxTx.commit(); } if (protocolVersionStartsFrom(ltx.loadHeader().current().ledgerVersion, @@ -1439,50 +1434,52 @@ LedgerManagerImpl::processFeesSeqNums( } void -LedgerManagerImpl::prefetchTxSourceIds( - std::vector const& txs) +LedgerManagerImpl::prefetchTxSourceIds(ApplicableTxSetFrame const& txSet) { ZoneScoped; if (mApp.getConfig().PREFETCH_BATCH_SIZE > 0) { UnorderedSet keys; - for (auto const& tx : txs) + for (auto const& phase : txSet.getPhases()) { - tx->insertKeysForFeeProcessing(keys); + for (auto const& tx : phase) + { + tx->insertKeysForFeeProcessing(keys); + } } mApp.getLedgerTxnRoot().prefetch(keys); } } void -LedgerManagerImpl::prefetchTransactionData( - std::vector const& txs) +LedgerManagerImpl::prefetchTransactionData(ApplicableTxSetFrame const& txSet) { ZoneScoped; if (mApp.getConfig().PREFETCH_BATCH_SIZE > 0) { UnorderedSet keys; - for (auto const& tx : txs) + for (auto const& phase : txSet.getPhases()) { - tx->insertKeysForTxApply(keys); + for (auto const& tx : phase) + { + tx->insertKeysForTxApply(keys); + } } mApp.getLedgerTxnRoot().prefetch(keys); } } -void +TransactionResultSet LedgerManagerImpl::applyTransactions( - ApplicableTxSetFrame const& txSet, - std::vector const& txs, AbstractLedgerTxn& ltx, - TransactionResultSet& txResultSet, + ApplicableTxSetFrame const& txSet, AbstractLedgerTxn& ltx, std::unique_ptr const& ledgerCloseMeta) { ZoneNamedN(txsZone, "applyTransactions", true); int index = 0; // Record counts - auto numTxs = txs.size(); - auto numOps = txSet.sizeOpTotal(); + size_t numTxs = txSet.sizeTxTotal(); + size_t numOps = txSet.sizeOpTotal(); if (numTxs > 0) { mTransactionCount.Update(static_cast(numTxs)); @@ -1493,85 +1490,91 @@ LedgerManagerImpl::applyTransactions( CLOG_INFO(Tx, "applying ledger {} ({})", ltx.loadHeader().current().ledgerSeq, txSet.summary()); } + TransactionResultSet txResultSet; + txResultSet.results.reserve(numTxs); - prefetchTransactionData(txs); - + prefetchTransactionData(txSet); + auto phases = txSet.getPhasesInApplyOrder(); Hash sorobanBasePrngSeed = txSet.getContentsHash(); uint64_t txNum{0}; uint64_t txSucceeded{0}; uint64_t txFailed{0}; uint64_t sorobanTxSucceeded{0}; uint64_t sorobanTxFailed{0}; - for (auto tx : txs) + for (auto const& phase : phases) { - ZoneNamedN(txZone, "applyTransaction", true); - auto txTime = mTransactionApply.TimeScope(); - TransactionMetaFrame tm(ltx.loadHeader().current().ledgerVersion); - CLOG_DEBUG(Tx, " tx#{} = {} ops={} txseq={} (@ {})", index, - hexAbbrev(tx->getContentsHash()), tx->getNumOperations(), - tx->getSeqNum(), - mApp.getConfig().toShortString(tx->getSourceID())); - - Hash subSeed = sorobanBasePrngSeed; - // If tx can use the seed, we need to compute a sub-seed for it. - if (tx->isSoroban()) - { - SHA256 subSeedSha; - subSeedSha.add(sorobanBasePrngSeed); - subSeedSha.add(xdr::xdr_to_opaque(txNum)); - subSeed = subSeedSha.finish(); - } - ++txNum; - - tx->apply(mApp, ltx, tm, subSeed); - tx->processPostApply(mApp, ltx, tm); - TransactionResultPair results; - results.transactionHash = tx->getContentsHash(); - results.result = tx->getResult(); - if (results.result.result.code() == TransactionResultCode::txSUCCESS) + for (auto const& tx : phase) { + ZoneNamedN(txZone, "applyTransaction", true); + auto txTime = mTransactionApply.TimeScope(); + TransactionMetaFrame tm(ltx.loadHeader().current().ledgerVersion); + CLOG_DEBUG(Tx, " tx#{} = {} ops={} txseq={} (@ {})", index, + hexAbbrev(tx->getContentsHash()), tx->getNumOperations(), + tx->getSeqNum(), + mApp.getConfig().toShortString(tx->getSourceID())); + + Hash subSeed = sorobanBasePrngSeed; + // If tx can use the seed, we need to compute a sub-seed for it. if (tx->isSoroban()) { - ++sorobanTxSucceeded; + SHA256 subSeedSha; + subSeedSha.add(sorobanBasePrngSeed); + subSeedSha.add(xdr::xdr_to_opaque(txNum)); + subSeed = subSeedSha.finish(); } - ++txSucceeded; - } - else - { - if (tx->isSoroban()) + ++txNum; + + tx->apply(mApp, ltx, tm, subSeed); + tx->processPostApply(mApp, ltx, tm); + TransactionResultPair results; + results.transactionHash = tx->getContentsHash(); + results.result = tx->getResult(); + if (results.result.result.code() == + TransactionResultCode::txSUCCESS) + { + if (tx->isSoroban()) + { + ++sorobanTxSucceeded; + } + ++txSucceeded; + } + else { - ++sorobanTxFailed; + if (tx->isSoroban()) + { + ++sorobanTxFailed; + } + ++txFailed; } - ++txFailed; - } - // First gather the TransactionResultPair into the TxResultSet for - // hashing into the ledger header. - txResultSet.results.emplace_back(results); + // First gather the TransactionResultPair into the TxResultSet for + // hashing into the ledger header. + txResultSet.results.emplace_back(results); - // Then potentially add that TRP and its associated TransactionMeta - // into the associated slot of any LedgerCloseMeta we're collecting. - if (ledgerCloseMeta) - { - ledgerCloseMeta->setTxProcessingMetaAndResultPair( - tm.getXDR(), std::move(results), index); - } + // Then potentially add that TRP and its associated TransactionMeta + // into the associated slot of any LedgerCloseMeta we're collecting. + if (ledgerCloseMeta) + { + ledgerCloseMeta->setTxProcessingMetaAndResultPair( + tm.getXDR(), std::move(results), index); + } - // Then finally store the results and meta into the txhistory table. - // if we're running in a mode that has one. - // - // Note to future: when we eliminate the txhistory and txfeehistory - // tables, the following step can be removed. - // - // Also note: for historical reasons the history tables number - // txs counting from 1, not 0. We preserve this for the time being - // in case anyone depends on it. - ++index; - if (mApp.getConfig().MODE_STORES_HISTORY_MISC) - { - auto ledgerSeq = ltx.loadHeader().current().ledgerSeq; - storeTransaction(mApp.getDatabase(), ledgerSeq, tx, tm.getXDR(), - txResultSet, mApp.getConfig()); + // Then finally store the results and meta into the txhistory table. + // if we're running in a mode that has one. + // + // Note to future: when we eliminate the txhistory and txfeehistory + // tables, the following step can be removed. + // + // Also note: for historical reasons the history tables number + // txs counting from 1, not 0. We preserve this for the time being + // in case anyone depends on it. + ++index; + if (mApp.getConfig().MODE_STORES_HISTORY_MISC) + { + auto ledgerSeq = ltx.loadHeader().current().ledgerSeq; + storeTransaction(mApp.getDatabase(), ledgerSeq, tx, tm.getXDR(), + txResultSet, mApp.getConfig()); + } } } @@ -1580,6 +1583,7 @@ LedgerManagerImpl::applyTransactions( mSorobanTransactionApplySucceeded.inc(sorobanTxSucceeded); mSorobanTransactionApplyFailed.inc(sorobanTxFailed); logTxApplyMetrics(ltx, numTxs, numOps); + return txResultSet; } void diff --git a/src/ledger/LedgerManagerImpl.h b/src/ledger/LedgerManagerImpl.h index eac42ae59d..c007543365 100644 --- a/src/ledger/LedgerManagerImpl.h +++ b/src/ledger/LedgerManagerImpl.h @@ -75,14 +75,11 @@ class LedgerManagerImpl : public LedgerManager std::unique_ptr mNextMetaToEmit; void processFeesSeqNums( - std::vector const& txs, - AbstractLedgerTxn& ltxOuter, ApplicableTxSetFrame const& txSet, + ApplicableTxSetFrame const& txSet, AbstractLedgerTxn& ltxOuter, std::unique_ptr const& ledgerCloseMeta); - void applyTransactions( - ApplicableTxSetFrame const& txSet, - std::vector const& txs, AbstractLedgerTxn& ltx, - TransactionResultSet& txResultSet, + TransactionResultSet applyTransactions( + ApplicableTxSetFrame const& txSet, AbstractLedgerTxn& ltx, std::unique_ptr const& ledgerCloseMeta); // initialLedgerVers must be the ledger version at the start of the ledger. @@ -94,9 +91,8 @@ class LedgerManagerImpl : public LedgerManager uint32_t initialLedgerVers); void storeCurrentLedger(LedgerHeader const& header, bool storeHeader); - void - prefetchTransactionData(std::vector const& txs); - void prefetchTxSourceIds(std::vector const& txs); + void prefetchTransactionData(ApplicableTxSetFrame const& txSet); + void prefetchTxSourceIds(ApplicableTxSetFrame const& txSet); void closeLedgerIf(LedgerCloseData const& ledgerData); State mState; diff --git a/src/main/ApplicationImpl.cpp b/src/main/ApplicationImpl.cpp index 3151271578..4f8aa06c2a 100644 --- a/src/main/ApplicationImpl.cpp +++ b/src/main/ApplicationImpl.cpp @@ -95,8 +95,8 @@ ApplicationImpl::ApplicationImpl(VirtualClock& clock, Config const& cfg) ? std::make_unique(*mEvictionIOContext) : nullptr) , mOverlayIOContext(mConfig.EXPERIMENTAL_BACKGROUND_OVERLAY_PROCESSING - ? std::make_optional(1) - : std::nullopt) + ? std::make_unique(1) + : nullptr) , mOverlayWork(mOverlayIOContext ? std::make_unique( *mOverlayIOContext) : nullptr) diff --git a/src/main/ApplicationImpl.h b/src/main/ApplicationImpl.h index 0229bc1f42..d195e04774 100644 --- a/src/main/ApplicationImpl.h +++ b/src/main/ApplicationImpl.h @@ -153,7 +153,7 @@ class ApplicationImpl : public Application std::unique_ptr mWork; std::unique_ptr mEvictionWork; - std::optional mOverlayIOContext; + std::unique_ptr mOverlayIOContext; std::unique_ptr mOverlayWork; std::unique_ptr mBucketManager; diff --git a/src/main/main.cpp b/src/main/main.cpp index 7f841e3563..c1a4c38429 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -379,10 +379,14 @@ main(int argc, char* const* argv) // FIXME: This check is done against the XDR version enabled in the host // (curr vs next). At the moment, the host is using curr, but core can be - // built with vnext, causing a curr diff against next. This works now - // because the xdr is indentical, but the moment that changes this checkk - // will fail and will need to be fixed. + // built with vnext, causing a curr diff against next. + // Eventually, Core should support the third ('vnext') env instance and + // we'll be able to enable this check for vnext as well. + // At the moment vnext XDR changes only affect Core and not the Soroban + // env. +#ifndef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION checkXDRFileIdentity(); +#endif int res = handleCommandLine(argc, argv); #ifdef USE_TRACY diff --git a/src/protocol-next/xdr b/src/protocol-next/xdr index 38629ee46f..248090e2ec 160000 --- a/src/protocol-next/xdr +++ b/src/protocol-next/xdr @@ -1 +1 @@ -Subproject commit 38629ee46fbc7dfb034e00c38b2ddb954c6dd41d +Subproject commit 248090e2ec005d0693e71cc3237ccca4e2b79b22 diff --git a/src/test/TxTests.cpp b/src/test/TxTests.cpp index f806cf5720..900c5510db 100644 --- a/src/test/TxTests.cpp +++ b/src/test/TxTests.cpp @@ -519,15 +519,15 @@ closeLedgerOn(Application& app, uint32 ledgerSeq, TimePoint closeTime, } else { - TxSetTransactions classic; - TxSetTransactions soroban; + TxFrameList classic; + TxFrameList soroban; for (auto const& tx : txs) { tx->isSoroban() ? soroban.emplace_back(tx) : classic.emplace_back(tx); } - TxSetPhaseTransactions phases = {classic}; + PerPhaseTransactionList phases = {classic}; if (!soroban.empty()) { phases.emplace_back(soroban); diff --git a/src/testdata/ledger-close-meta-v1-protocol-22-soroban.json b/src/testdata/ledger-close-meta-v1-protocol-22-soroban.json index 391b91f9d6..ae4a16e6d5 100644 --- a/src/testdata/ledger-close-meta-v1-protocol-22-soroban.json +++ b/src/testdata/ledger-close-meta-v1-protocol-22-soroban.json @@ -6,23 +6,23 @@ "v": 0 }, "ledgerHeader": { - "hash": "f05b67cfe119406be0f63407fa1e3b613491f46d18c6ad63b4c46d59652da875", + "hash": "b581a2f3a68151378e522a284545390eb7e8f485c15c14b4886bf3d0b1dae9b8", "header": { "ledgerVersion": 22, - "previousLedgerHash": "0eaed167ffc69c757dbcbb06b00ab8d8c20aea12f02df47f5f2b71417aa680d3", + "previousLedgerHash": "b470d9b5b04f2f305b89337f58d04f584bc61fe4f49fa51ec2a7453d5130c2b8", "scpValue": { - "txSetHash": "7c884f9f96d5b6bc72b3dace157e0c3ccc40943abad63465fc5d24bcbbd3024e", + "txSetHash": "de56e38492f2fd83a692263c344f7697799c162e13c027b34ca0e0f50bfa2738", "closeTime": 1451692800, "upgrades": [], "ext": { "v": "STELLAR_VALUE_SIGNED", "lcValueSignature": { "nodeID": "GDDOUW25MRFLNXQMN3OODP6JQEXSGLMHAFZV4XPQ2D3GA4QFIDMEJG2O", - "signature": "a617ac3f8247ecbe03cb0a79e9891a36943e89d17e12e59ab303548471c7b33e1269a2d0a3d33ae33bddb7ad465988a0cbc146f9b95d7f69d36c44d434a0a20e" + "signature": "67824e47118e713cea977df078faf923aa204aebbb5246f7e83d192ec67fb97e158d14ceb1a0982ef9de3890568a7d4144fabb9345403d075f45fcb8dff0ba0d" } } }, - "txSetResultHash": "7f4fc057445ade63e75e1a66017f17223ef52bca0238a323e4f81bf977730f47", + "txSetResultHash": "5d0b857770ad49f1899deb6578ca91520b2a9a609cd76f5bc40ff1ae82580b86", "bucketListHash": "43f28903abacf8170b97638f846eabf8337f84260100c043a4b29d855d3a1581", "ledgerSeq": 23, "totalCoins": 1000000000000000000, @@ -49,452 +49,453 @@ "txSet": { "v": 1, "v1TxSet": { - "previousLedgerHash": "0eaed167ffc69c757dbcbb06b00ab8d8c20aea12f02df47f5f2b71417aa680d3", + "previousLedgerHash": "b470d9b5b04f2f305b89337f58d04f584bc61fe4f49fa51ec2a7453d5130c2b8", "phases": [ { "v": 0, "v0Components": [] }, { - "v": 0, - "v0Components": [ - { - "type": "TXSET_COMP_TXS_MAYBE_DISCOUNTED_FEE", - "txsMaybeDiscountedFee": { - "baseFee": 100, - "txs": [ - { - "type": "ENVELOPE_TYPE_TX", - "v1": { - "tx": { - "sourceAccount": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", - "fee": 102512, - "seqNum": 25769803777, - "cond": { - "type": "PRECOND_NONE" - }, - "memo": { - "type": "MEMO_NONE" - }, - "operations": [ - { - "sourceAccount": null, - "body": { - "type": "INVOKE_HOST_FUNCTION", - "invokeHostFunctionOp": { - "hostFunction": { - "type": "HOST_FUNCTION_TYPE_INVOKE_CONTRACT", - "invokeContract": { - "contractAddress": "CAA3QKIP2SNVXUJTB4HKOGF55JTSSMQGED3FZYNHMNSXYV3DRRMAWA3Y", - "functionName": "put_persistent", - "args": [ - { - "type": "SCV_SYMBOL", - "sym": "key" - }, - { - "type": "SCV_U64", - "u64": 42 - } - ] - } - }, - "auth": [] - } - } - } - ], - "ext": { - "v": 1, - "sorobanData": { - "ext": { - "v": 0 - }, - "resources": { - "footprint": { - "readOnly": [ - { - "type": "CONTRACT_CODE", - "contractCode": { - "hash": "fc644715caaead746e6145f4331ff75c427c965c20d2995a9942b01247515962" + "v": 1, + "parallelTxsComponent": { + "baseFee": 100, + "executionStages": [ + [ + [ + [ + { + "type": "ENVELOPE_TYPE_TX", + "v1": { + "tx": { + "sourceAccount": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", + "fee": 102512, + "seqNum": 25769803777, + "cond": { + "type": "PRECOND_NONE" + }, + "memo": { + "type": "MEMO_NONE" + }, + "operations": [ + { + "sourceAccount": null, + "body": { + "type": "INVOKE_HOST_FUNCTION", + "invokeHostFunctionOp": { + "hostFunction": { + "type": "HOST_FUNCTION_TYPE_INVOKE_CONTRACT", + "invokeContract": { + "contractAddress": "CAA3QKIP2SNVXUJTB4HKOGF55JTSSMQGED3FZYNHMNSXYV3DRRMAWA3Y", + "functionName": "put_persistent", + "args": [ + { + "type": "SCV_SYMBOL", + "sym": "key" + }, + { + "type": "SCV_U64", + "u64": 42 + } + ] } }, - { - "type": "CONTRACT_DATA", - "contractData": { - "contract": "CAA3QKIP2SNVXUJTB4HKOGF55JTSSMQGED3FZYNHMNSXYV3DRRMAWA3Y", - "key": { - "type": "SCV_LEDGER_KEY_CONTRACT_INSTANCE" - }, - "durability": "PERSISTENT" + "auth": [] + } + } + } + ], + "ext": { + "v": 1, + "sorobanData": { + "ext": { + "v": 0 + }, + "resources": { + "footprint": { + "readOnly": [ + { + "type": "CONTRACT_CODE", + "contractCode": { + "hash": "fc644715caaead746e6145f4331ff75c427c965c20d2995a9942b01247515962" + } + }, + { + "type": "CONTRACT_DATA", + "contractData": { + "contract": "CAA3QKIP2SNVXUJTB4HKOGF55JTSSMQGED3FZYNHMNSXYV3DRRMAWA3Y", + "key": { + "type": "SCV_LEDGER_KEY_CONTRACT_INSTANCE" + }, + "durability": "PERSISTENT" + } } - } - ], - "readWrite": [] + ], + "readWrite": [] + }, + "instructions": 4000000, + "readBytes": 10000, + "writeBytes": 1000 }, - "instructions": 4000000, - "readBytes": 10000, - "writeBytes": 1000 - }, - "resourceFee": 101512 + "resourceFee": 101512 + } } - } - }, - "signatures": [ - { - "hint": "477df904", - "signature": "23252dd4e8cc38c37904e893d7b045ec0593b1c1a9580cdf8a34677663135da84d18cb7f11826d165994f6c30c08bbb64fa8e3ee9a0f05d4d459591e76274207" - } - ] - } - }, - { - "type": "ENVELOPE_TYPE_TX", - "v1": { - "tx": { - "sourceAccount": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", - "fee": 1044855, - "seqNum": 30064771073, - "cond": { - "type": "PRECOND_NONE" - }, - "memo": { - "type": "MEMO_NONE" }, - "operations": [ + "signatures": [ { - "sourceAccount": null, - "body": { - "type": "INVOKE_HOST_FUNCTION", - "invokeHostFunctionOp": { - "hostFunction": { - "type": "HOST_FUNCTION_TYPE_CREATE_CONTRACT", - "createContract": { - "contractIDPreimage": { - "type": "CONTRACT_ID_PREIMAGE_FROM_ADDRESS", - "fromAddress": { - "address": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", - "salt": "63479ad69a090b258277ec8fba6f99419a2ffb248981510657c944ccd1148e97" + "hint": "477df904", + "signature": "23252dd4e8cc38c37904e893d7b045ec0593b1c1a9580cdf8a34677663135da84d18cb7f11826d165994f6c30c08bbb64fa8e3ee9a0f05d4d459591e76274207" + } + ] + } + }, + { + "type": "ENVELOPE_TYPE_TX", + "v1": { + "tx": { + "sourceAccount": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", + "fee": 1044855, + "seqNum": 30064771073, + "cond": { + "type": "PRECOND_NONE" + }, + "memo": { + "type": "MEMO_NONE" + }, + "operations": [ + { + "sourceAccount": null, + "body": { + "type": "INVOKE_HOST_FUNCTION", + "invokeHostFunctionOp": { + "hostFunction": { + "type": "HOST_FUNCTION_TYPE_CREATE_CONTRACT", + "createContract": { + "contractIDPreimage": { + "type": "CONTRACT_ID_PREIMAGE_FROM_ADDRESS", + "fromAddress": { + "address": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", + "salt": "63479ad69a090b258277ec8fba6f99419a2ffb248981510657c944ccd1148e97" + } + }, + "executable": { + "type": "CONTRACT_EXECUTABLE_WASM", + "wasm_hash": "fc644715caaead746e6145f4331ff75c427c965c20d2995a9942b01247515962" } - }, - "executable": { - "type": "CONTRACT_EXECUTABLE_WASM", - "wasm_hash": "fc644715caaead746e6145f4331ff75c427c965c20d2995a9942b01247515962" } - } - }, - "auth": [ - { - "credentials": { - "type": "SOROBAN_CREDENTIALS_SOURCE_ACCOUNT" - }, - "rootInvocation": { - "function": { - "type": "SOROBAN_AUTHORIZED_FUNCTION_TYPE_CREATE_CONTRACT_HOST_FN", - "createContractHostFn": { - "contractIDPreimage": { - "type": "CONTRACT_ID_PREIMAGE_FROM_ADDRESS", - "fromAddress": { - "address": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", - "salt": "63479ad69a090b258277ec8fba6f99419a2ffb248981510657c944ccd1148e97" + }, + "auth": [ + { + "credentials": { + "type": "SOROBAN_CREDENTIALS_SOURCE_ACCOUNT" + }, + "rootInvocation": { + "function": { + "type": "SOROBAN_AUTHORIZED_FUNCTION_TYPE_CREATE_CONTRACT_HOST_FN", + "createContractHostFn": { + "contractIDPreimage": { + "type": "CONTRACT_ID_PREIMAGE_FROM_ADDRESS", + "fromAddress": { + "address": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", + "salt": "63479ad69a090b258277ec8fba6f99419a2ffb248981510657c944ccd1148e97" + } + }, + "executable": { + "type": "CONTRACT_EXECUTABLE_WASM", + "wasm_hash": "fc644715caaead746e6145f4331ff75c427c965c20d2995a9942b01247515962" } - }, - "executable": { - "type": "CONTRACT_EXECUTABLE_WASM", - "wasm_hash": "fc644715caaead746e6145f4331ff75c427c965c20d2995a9942b01247515962" } - } - }, - "subInvocations": [] + }, + "subInvocations": [] + } } - } - ] + ] + } } } - } - ], - "ext": { - "v": 1, - "sorobanData": { - "ext": { - "v": 0 - }, - "resources": { - "footprint": { - "readOnly": [ - { - "type": "CONTRACT_CODE", - "contractCode": { - "hash": "fc644715caaead746e6145f4331ff75c427c965c20d2995a9942b01247515962" + ], + "ext": { + "v": 1, + "sorobanData": { + "ext": { + "v": 0 + }, + "resources": { + "footprint": { + "readOnly": [ + { + "type": "CONTRACT_CODE", + "contractCode": { + "hash": "fc644715caaead746e6145f4331ff75c427c965c20d2995a9942b01247515962" + } } - } - ], - "readWrite": [ - { - "type": "CONTRACT_DATA", - "contractData": { - "contract": "CCOKSYPZJ2B3244CEMLBGUGWPMQ3BLES6AKHRQCX2XF27K4HDBW2LKDF", - "key": { - "type": "SCV_LEDGER_KEY_CONTRACT_INSTANCE" - }, - "durability": "PERSISTENT" + ], + "readWrite": [ + { + "type": "CONTRACT_DATA", + "contractData": { + "contract": "CCOKSYPZJ2B3244CEMLBGUGWPMQ3BLES6AKHRQCX2XF27K4HDBW2LKDF", + "key": { + "type": "SCV_LEDGER_KEY_CONTRACT_INSTANCE" + }, + "durability": "PERSISTENT" + } } - } - ] + ] + }, + "instructions": 200000, + "readBytes": 5000, + "writeBytes": 5000 }, - "instructions": 200000, - "readBytes": 5000, - "writeBytes": 5000 - }, - "resourceFee": 1043855 + "resourceFee": 1043855 + } } - } - }, - "signatures": [ - { - "hint": "4b80097b", - "signature": "45b39cc150bff6e89ff81279c0e39c8a7660d359dcc7bf4220ca1ab9e6cbc2eb07fea3a741c1c4dc50428b15f7c6f3011dc25ca8b4975c2f24019ae42c939b04" - } - ] - } - }, - { - "type": "ENVELOPE_TYPE_TX", - "v1": { - "tx": { - "sourceAccount": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", - "fee": 1001000, - "seqNum": 17179869185, - "cond": { - "type": "PRECOND_NONE" }, - "memo": { - "type": "MEMO_NONE" - }, - "operations": [ + "signatures": [ { - "sourceAccount": null, - "body": { - "type": "EXTEND_FOOTPRINT_TTL", - "extendFootprintTTLOp": { - "ext": { - "v": 0 - }, - "extendTo": 10000 - } - } + "hint": "4b80097b", + "signature": "45b39cc150bff6e89ff81279c0e39c8a7660d359dcc7bf4220ca1ab9e6cbc2eb07fea3a741c1c4dc50428b15f7c6f3011dc25ca8b4975c2f24019ae42c939b04" } - ], - "ext": { - "v": 1, - "sorobanData": { - "ext": { - "v": 0 - }, - "resources": { - "footprint": { - "readOnly": [ - { - "type": "CONTRACT_CODE", - "contractCode": { - "hash": "fc644715caaead746e6145f4331ff75c427c965c20d2995a9942b01247515962" - } + ] + } + }, + { + "type": "ENVELOPE_TYPE_TX", + "v1": { + "tx": { + "sourceAccount": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", + "fee": 1001000, + "seqNum": 17179869185, + "cond": { + "type": "PRECOND_NONE" + }, + "memo": { + "type": "MEMO_NONE" + }, + "operations": [ + { + "sourceAccount": null, + "body": { + "type": "EXTEND_FOOTPRINT_TTL", + "extendFootprintTTLOp": { + "ext": { + "v": 0 }, - { - "type": "CONTRACT_DATA", - "contractData": { - "contract": "CAA3QKIP2SNVXUJTB4HKOGF55JTSSMQGED3FZYNHMNSXYV3DRRMAWA3Y", - "key": { - "type": "SCV_LEDGER_KEY_CONTRACT_INSTANCE" - }, - "durability": "PERSISTENT" + "extendTo": 10000 + } + } + } + ], + "ext": { + "v": 1, + "sorobanData": { + "ext": { + "v": 0 + }, + "resources": { + "footprint": { + "readOnly": [ + { + "type": "CONTRACT_CODE", + "contractCode": { + "hash": "fc644715caaead746e6145f4331ff75c427c965c20d2995a9942b01247515962" + } + }, + { + "type": "CONTRACT_DATA", + "contractData": { + "contract": "CAA3QKIP2SNVXUJTB4HKOGF55JTSSMQGED3FZYNHMNSXYV3DRRMAWA3Y", + "key": { + "type": "SCV_LEDGER_KEY_CONTRACT_INSTANCE" + }, + "durability": "PERSISTENT" + } } - } - ], - "readWrite": [] + ], + "readWrite": [] + }, + "instructions": 0, + "readBytes": 10000, + "writeBytes": 0 }, - "instructions": 0, - "readBytes": 10000, - "writeBytes": 0 - }, - "resourceFee": 1000000 + "resourceFee": 1000000 + } } - } - }, - "signatures": [ - { - "hint": "f7f60229", - "signature": "115131e2aa61cc0ed5783b1978acced5450d548ee75f474968199736872b449573f39ae7ac11b6ec78a6cc1ab84171c0430666f7e17ecd6084f1770256ad9e0b" - } - ] - } - }, - { - "type": "ENVELOPE_TYPE_TX", - "v1": { - "tx": { - "sourceAccount": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", - "fee": 1001000, - "seqNum": 12884901889, - "cond": { - "type": "PRECOND_NONE" - }, - "memo": { - "type": "MEMO_NONE" }, - "operations": [ + "signatures": [ { - "sourceAccount": null, - "body": { - "type": "RESTORE_FOOTPRINT", - "restoreFootprintOp": { - "ext": { - "v": 0 + "hint": "f7f60229", + "signature": "115131e2aa61cc0ed5783b1978acced5450d548ee75f474968199736872b449573f39ae7ac11b6ec78a6cc1ab84171c0430666f7e17ecd6084f1770256ad9e0b" + } + ] + } + }, + { + "type": "ENVELOPE_TYPE_TX", + "v1": { + "tx": { + "sourceAccount": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", + "fee": 1001000, + "seqNum": 12884901889, + "cond": { + "type": "PRECOND_NONE" + }, + "memo": { + "type": "MEMO_NONE" + }, + "operations": [ + { + "sourceAccount": null, + "body": { + "type": "RESTORE_FOOTPRINT", + "restoreFootprintOp": { + "ext": { + "v": 0 + } } } } - } - ], - "ext": { - "v": 1, - "sorobanData": { - "ext": { - "v": 0 - }, - "resources": { - "footprint": { - "readOnly": [], - "readWrite": [ - { - "type": "CONTRACT_DATA", - "contractData": { - "contract": "CAA3QKIP2SNVXUJTB4HKOGF55JTSSMQGED3FZYNHMNSXYV3DRRMAWA3Y", - "key": { - "type": "SCV_SYMBOL", - "sym": "archived" - }, - "durability": "PERSISTENT" + ], + "ext": { + "v": 1, + "sorobanData": { + "ext": { + "v": 0 + }, + "resources": { + "footprint": { + "readOnly": [], + "readWrite": [ + { + "type": "CONTRACT_DATA", + "contractData": { + "contract": "CAA3QKIP2SNVXUJTB4HKOGF55JTSSMQGED3FZYNHMNSXYV3DRRMAWA3Y", + "key": { + "type": "SCV_SYMBOL", + "sym": "archived" + }, + "durability": "PERSISTENT" + } } - } - ] + ] + }, + "instructions": 0, + "readBytes": 5000, + "writeBytes": 1000 }, - "instructions": 0, - "readBytes": 5000, - "writeBytes": 1000 - }, - "resourceFee": 1000000 + "resourceFee": 1000000 + } } - } - }, - "signatures": [ - { - "hint": "5099a12e", - "signature": "c9e283810f469c92769251263ae22cfceab70f822a274b506bf22651b23e55c998d93e679c52fe95d74182bc0021adc75baa72a2bcf00430840876e3dd028300" - } - ] - } - }, - { - "type": "ENVELOPE_TYPE_TX", - "v1": { - "tx": { - "sourceAccount": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", - "fee": 127626, - "seqNum": 21474836481, - "cond": { - "type": "PRECOND_NONE" }, - "memo": { - "type": "MEMO_NONE" - }, - "operations": [ + "signatures": [ { - "sourceAccount": null, - "body": { - "type": "INVOKE_HOST_FUNCTION", - "invokeHostFunctionOp": { - "hostFunction": { - "type": "HOST_FUNCTION_TYPE_INVOKE_CONTRACT", - "invokeContract": { - "contractAddress": "CAA3QKIP2SNVXUJTB4HKOGF55JTSSMQGED3FZYNHMNSXYV3DRRMAWA3Y", - "functionName": "put_persistent", - "args": [ - { - "type": "SCV_SYMBOL", - "sym": "key" - }, - { - "type": "SCV_U64", - "u64": 42 - } - ] - } - }, - "auth": [] - } - } + "hint": "5099a12e", + "signature": "c9e283810f469c92769251263ae22cfceab70f822a274b506bf22651b23e55c998d93e679c52fe95d74182bc0021adc75baa72a2bcf00430840876e3dd028300" } - ], - "ext": { - "v": 1, - "sorobanData": { - "ext": { - "v": 0 - }, - "resources": { - "footprint": { - "readOnly": [ - { - "type": "CONTRACT_CODE", - "contractCode": { - "hash": "fc644715caaead746e6145f4331ff75c427c965c20d2995a9942b01247515962" + ] + } + }, + { + "type": "ENVELOPE_TYPE_TX", + "v1": { + "tx": { + "sourceAccount": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", + "fee": 127626, + "seqNum": 21474836481, + "cond": { + "type": "PRECOND_NONE" + }, + "memo": { + "type": "MEMO_NONE" + }, + "operations": [ + { + "sourceAccount": null, + "body": { + "type": "INVOKE_HOST_FUNCTION", + "invokeHostFunctionOp": { + "hostFunction": { + "type": "HOST_FUNCTION_TYPE_INVOKE_CONTRACT", + "invokeContract": { + "contractAddress": "CAA3QKIP2SNVXUJTB4HKOGF55JTSSMQGED3FZYNHMNSXYV3DRRMAWA3Y", + "functionName": "put_persistent", + "args": [ + { + "type": "SCV_SYMBOL", + "sym": "key" + }, + { + "type": "SCV_U64", + "u64": 42 + } + ] } }, - { - "type": "CONTRACT_DATA", - "contractData": { - "contract": "CAA3QKIP2SNVXUJTB4HKOGF55JTSSMQGED3FZYNHMNSXYV3DRRMAWA3Y", - "key": { - "type": "SCV_LEDGER_KEY_CONTRACT_INSTANCE" - }, - "durability": "PERSISTENT" + "auth": [] + } + } + } + ], + "ext": { + "v": 1, + "sorobanData": { + "ext": { + "v": 0 + }, + "resources": { + "footprint": { + "readOnly": [ + { + "type": "CONTRACT_CODE", + "contractCode": { + "hash": "fc644715caaead746e6145f4331ff75c427c965c20d2995a9942b01247515962" + } + }, + { + "type": "CONTRACT_DATA", + "contractData": { + "contract": "CAA3QKIP2SNVXUJTB4HKOGF55JTSSMQGED3FZYNHMNSXYV3DRRMAWA3Y", + "key": { + "type": "SCV_LEDGER_KEY_CONTRACT_INSTANCE" + }, + "durability": "PERSISTENT" + } } - } - ], - "readWrite": [ - { - "type": "CONTRACT_DATA", - "contractData": { - "contract": "CAA3QKIP2SNVXUJTB4HKOGF55JTSSMQGED3FZYNHMNSXYV3DRRMAWA3Y", - "key": { - "type": "SCV_SYMBOL", - "sym": "key" - }, - "durability": "PERSISTENT" + ], + "readWrite": [ + { + "type": "CONTRACT_DATA", + "contractData": { + "contract": "CAA3QKIP2SNVXUJTB4HKOGF55JTSSMQGED3FZYNHMNSXYV3DRRMAWA3Y", + "key": { + "type": "SCV_SYMBOL", + "sym": "key" + }, + "durability": "PERSISTENT" + } } - } - ] + ] + }, + "instructions": 4000000, + "readBytes": 10000, + "writeBytes": 1000 }, - "instructions": 4000000, - "readBytes": 10000, - "writeBytes": 1000 - }, - "resourceFee": 126626 + "resourceFee": 126626 + } } - } - }, - "signatures": [ - { - "hint": "e189b409", - "signature": "f2a733956cdfcf5d20feb848d5104c69d732d607ccfc7091c939d4786d68188c12975cc623a283b8df5f2b6cd804f6b7cb87a075ac2015aba91554196bfefb09" - } - ] + }, + "signatures": [ + { + "hint": "e189b409", + "signature": "f2a733956cdfcf5d20feb848d5104c69d732d607ccfc7091c939d4786d68188c12975cc623a283b8df5f2b6cd804f6b7cb87a075ac2015aba91554196bfefb09" + } + ] + } } - } + ] ] - } - } - ] + ] + ] + } } ] } @@ -502,9 +503,9 @@ "txProcessing": [ { "result": { - "transactionHash": "dc7b999613ade2e947b8ed0e831036976f1529fa7489088e4b70ea0f5ba2e920", + "transactionHash": "c9cd0abaad9ddeee41c162e4fd0c88a8127611a5ad3184f455bee186e6fcc5dc", "result": { - "feeCharged": 42946, + "feeCharged": 61612, "result": { "code": "txFAILED", "results": [ @@ -513,7 +514,7 @@ "tr": { "type": "INVOKE_HOST_FUNCTION", "invokeHostFunctionResult": { - "code": "INVOKE_HOST_FUNCTION_RESOURCE_LIMIT_EXCEEDED" + "code": "INVOKE_HOST_FUNCTION_TRAPPED" } } } @@ -528,13 +529,13 @@ { "type": "LEDGER_ENTRY_STATE", "state": { - "lastModifiedLedgerSeq": 7, + "lastModifiedLedgerSeq": 6, "data": { "type": "ACCOUNT", "account": { - "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", + "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", "balance": 400000000, - "seqNum": 30064771072, + "seqNum": 25769803776, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -558,9 +559,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", - "balance": 398956045, - "seqNum": 30064771072, + "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", + "balance": 399898388, + "seqNum": 25769803776, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -592,9 +593,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", - "balance": 398956045, - "seqNum": 30064771072, + "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", + "balance": 399898388, + "seqNum": 25769803776, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -618,9 +619,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", - "balance": 398956045, - "seqNum": 30064771073, + "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", + "balance": 399898388, + "seqNum": 25769803777, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -671,9 +672,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", - "balance": 398956045, - "seqNum": 30064771073, + "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", + "balance": 399898388, + "seqNum": 25769803777, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -721,9 +722,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", - "balance": 399957054, - "seqNum": 30064771073, + "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", + "balance": 399938388, + "seqNum": 25769803777, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -781,9 +782,9 @@ }, { "result": { - "transactionHash": "c9cd0abaad9ddeee41c162e4fd0c88a8127611a5ad3184f455bee186e6fcc5dc", + "transactionHash": "dc7b999613ade2e947b8ed0e831036976f1529fa7489088e4b70ea0f5ba2e920", "result": { - "feeCharged": 61612, + "feeCharged": 42946, "result": { "code": "txFAILED", "results": [ @@ -792,7 +793,7 @@ "tr": { "type": "INVOKE_HOST_FUNCTION", "invokeHostFunctionResult": { - "code": "INVOKE_HOST_FUNCTION_TRAPPED" + "code": "INVOKE_HOST_FUNCTION_RESOURCE_LIMIT_EXCEEDED" } } } @@ -807,13 +808,13 @@ { "type": "LEDGER_ENTRY_STATE", "state": { - "lastModifiedLedgerSeq": 6, + "lastModifiedLedgerSeq": 7, "data": { "type": "ACCOUNT", "account": { - "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", + "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", "balance": 400000000, - "seqNum": 25769803776, + "seqNum": 30064771072, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -837,9 +838,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", - "balance": 399898388, - "seqNum": 25769803776, + "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", + "balance": 398956045, + "seqNum": 30064771072, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -871,9 +872,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", - "balance": 399898388, - "seqNum": 25769803776, + "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", + "balance": 398956045, + "seqNum": 30064771072, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -897,9 +898,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", - "balance": 399898388, - "seqNum": 25769803777, + "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", + "balance": 398956045, + "seqNum": 30064771073, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -950,9 +951,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", - "balance": 399898388, - "seqNum": 25769803777, + "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", + "balance": 398956045, + "seqNum": 30064771073, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1000,9 +1001,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", - "balance": 399938388, - "seqNum": 25769803777, + "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", + "balance": 399957054, + "seqNum": 30064771073, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1060,19 +1061,18 @@ }, { "result": { - "transactionHash": "f2ff59ab9afd48f7d7d8a8207bec84dbcee87a1338da7896ee8c46e09958e76f", + "transactionHash": "df60206f668bf546432dd37e734d01d6fb886ac8e17af2abd3a7ac742ce4559f", "result": { - "feeCharged": 106775, + "feeCharged": 60559, "result": { "code": "txSUCCESS", "results": [ { "code": "opINNER", "tr": { - "type": "INVOKE_HOST_FUNCTION", - "invokeHostFunctionResult": { - "code": "INVOKE_HOST_FUNCTION_SUCCESS", - "success": "cbbc48750debb8535093b3deaf88ac7f4cff87425576a58de2bac754acdb4616" + "type": "EXTEND_FOOTPRINT_TTL", + "extendFootprintTTLResult": { + "code": "EXTEND_FOOTPRINT_TTL_SUCCESS" } } } @@ -1087,13 +1087,13 @@ { "type": "LEDGER_ENTRY_STATE", "state": { - "lastModifiedLedgerSeq": 5, + "lastModifiedLedgerSeq": 4, "data": { "type": "ACCOUNT", "account": { - "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", + "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", "balance": 400000000, - "seqNum": 21474836480, + "seqNum": 17179869184, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1117,9 +1117,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", - "balance": 399873274, - "seqNum": 21474836480, + "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", + "balance": 398999900, + "seqNum": 17179869184, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1151,9 +1151,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", - "balance": 399873274, - "seqNum": 21474836480, + "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", + "balance": 398999900, + "seqNum": 17179869184, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1177,9 +1177,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", - "balance": 399873274, - "seqNum": 21474836481, + "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", + "balance": 398999900, + "seqNum": 17179869185, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1225,25 +1225,30 @@ { "changes": [ { - "type": "LEDGER_ENTRY_CREATED", - "created": { + "type": "LEDGER_ENTRY_STATE", + "state": { + "lastModifiedLedgerSeq": 2, + "data": { + "type": "TTL", + "ttl": { + "keyHash": "091ddece931776a53f93869b82c24e132cc12d00d961fac09bc3b9cb9021c62d", + "liveUntilLedgerSeq": 10002 + } + }, + "ext": { + "v": 0 + } + } + }, + { + "type": "LEDGER_ENTRY_UPDATED", + "updated": { "lastModifiedLedgerSeq": 23, "data": { - "type": "CONTRACT_DATA", - "contractData": { - "ext": { - "v": 0 - }, - "contract": "CAA3QKIP2SNVXUJTB4HKOGF55JTSSMQGED3FZYNHMNSXYV3DRRMAWA3Y", - "key": { - "type": "SCV_SYMBOL", - "sym": "key" - }, - "durability": "PERSISTENT", - "val": { - "type": "SCV_U64", - "u64": 42 - } + "type": "TTL", + "ttl": { + "keyHash": "091ddece931776a53f93869b82c24e132cc12d00d961fac09bc3b9cb9021c62d", + "liveUntilLedgerSeq": 10023 } }, "ext": { @@ -1252,14 +1257,30 @@ } }, { - "type": "LEDGER_ENTRY_CREATED", - "created": { + "type": "LEDGER_ENTRY_STATE", + "state": { + "lastModifiedLedgerSeq": 2, + "data": { + "type": "TTL", + "ttl": { + "keyHash": "60313f9b273db0b14c3e503cf6cc152dd14a0c57e5e81a23e86b4e27a23a2c06", + "liveUntilLedgerSeq": 10002 + } + }, + "ext": { + "v": 0 + } + } + }, + { + "type": "LEDGER_ENTRY_UPDATED", + "updated": { "lastModifiedLedgerSeq": 23, "data": { "type": "TTL", "ttl": { - "keyHash": "764f4e59e20ac1a357f9f26ab0eaf46d196ab74822db44f039353a6f114864aa", - "liveUntilLedgerSeq": 42 + "keyHash": "60313f9b273db0b14c3e503cf6cc152dd14a0c57e5e81a23e86b4e27a23a2c06", + "liveUntilLedgerSeq": 10023 } }, "ext": { @@ -1278,9 +1299,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", - "balance": 399873274, - "seqNum": 21474836481, + "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", + "balance": 398999900, + "seqNum": 17179869185, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1328,9 +1349,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", - "balance": 399893225, - "seqNum": 21474836481, + "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", + "balance": 399939441, + "seqNum": 17179869185, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1378,7 +1399,8 @@ }, "events": [], "returnValue": { - "type": "SCV_VOID" + "type": "SCV_BOOL", + "b": "FALSE" }, "diagnosticEvents": [] } @@ -1703,18 +1725,19 @@ }, { "result": { - "transactionHash": "df60206f668bf546432dd37e734d01d6fb886ac8e17af2abd3a7ac742ce4559f", + "transactionHash": "f2ff59ab9afd48f7d7d8a8207bec84dbcee87a1338da7896ee8c46e09958e76f", "result": { - "feeCharged": 60559, + "feeCharged": 106775, "result": { "code": "txSUCCESS", "results": [ { "code": "opINNER", "tr": { - "type": "EXTEND_FOOTPRINT_TTL", - "extendFootprintTTLResult": { - "code": "EXTEND_FOOTPRINT_TTL_SUCCESS" + "type": "INVOKE_HOST_FUNCTION", + "invokeHostFunctionResult": { + "code": "INVOKE_HOST_FUNCTION_SUCCESS", + "success": "cbbc48750debb8535093b3deaf88ac7f4cff87425576a58de2bac754acdb4616" } } } @@ -1729,13 +1752,13 @@ { "type": "LEDGER_ENTRY_STATE", "state": { - "lastModifiedLedgerSeq": 4, + "lastModifiedLedgerSeq": 5, "data": { "type": "ACCOUNT", "account": { - "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", + "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", "balance": 400000000, - "seqNum": 17179869184, + "seqNum": 21474836480, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1759,9 +1782,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", - "balance": 398999900, - "seqNum": 17179869184, + "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", + "balance": 399873274, + "seqNum": 21474836480, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1793,9 +1816,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", - "balance": 398999900, - "seqNum": 17179869184, + "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", + "balance": 399873274, + "seqNum": 21474836480, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1819,9 +1842,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", - "balance": 398999900, - "seqNum": 17179869185, + "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", + "balance": 399873274, + "seqNum": 21474836481, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1867,46 +1890,25 @@ { "changes": [ { - "type": "LEDGER_ENTRY_STATE", - "state": { - "lastModifiedLedgerSeq": 2, - "data": { - "type": "TTL", - "ttl": { - "keyHash": "091ddece931776a53f93869b82c24e132cc12d00d961fac09bc3b9cb9021c62d", - "liveUntilLedgerSeq": 10002 - } - }, - "ext": { - "v": 0 - } - } - }, - { - "type": "LEDGER_ENTRY_UPDATED", - "updated": { + "type": "LEDGER_ENTRY_CREATED", + "created": { "lastModifiedLedgerSeq": 23, "data": { - "type": "TTL", - "ttl": { - "keyHash": "091ddece931776a53f93869b82c24e132cc12d00d961fac09bc3b9cb9021c62d", - "liveUntilLedgerSeq": 10023 - } - }, - "ext": { - "v": 0 - } - } - }, - { - "type": "LEDGER_ENTRY_STATE", - "state": { - "lastModifiedLedgerSeq": 2, - "data": { - "type": "TTL", - "ttl": { - "keyHash": "60313f9b273db0b14c3e503cf6cc152dd14a0c57e5e81a23e86b4e27a23a2c06", - "liveUntilLedgerSeq": 10002 + "type": "CONTRACT_DATA", + "contractData": { + "ext": { + "v": 0 + }, + "contract": "CAA3QKIP2SNVXUJTB4HKOGF55JTSSMQGED3FZYNHMNSXYV3DRRMAWA3Y", + "key": { + "type": "SCV_SYMBOL", + "sym": "key" + }, + "durability": "PERSISTENT", + "val": { + "type": "SCV_U64", + "u64": 42 + } } }, "ext": { @@ -1915,14 +1917,14 @@ } }, { - "type": "LEDGER_ENTRY_UPDATED", - "updated": { + "type": "LEDGER_ENTRY_CREATED", + "created": { "lastModifiedLedgerSeq": 23, "data": { "type": "TTL", "ttl": { - "keyHash": "60313f9b273db0b14c3e503cf6cc152dd14a0c57e5e81a23e86b4e27a23a2c06", - "liveUntilLedgerSeq": 10023 + "keyHash": "764f4e59e20ac1a357f9f26ab0eaf46d196ab74822db44f039353a6f114864aa", + "liveUntilLedgerSeq": 42 } }, "ext": { @@ -1941,9 +1943,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", - "balance": 398999900, - "seqNum": 17179869185, + "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", + "balance": 399873274, + "seqNum": 21474836481, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1991,9 +1993,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", - "balance": 399939441, - "seqNum": 17179869185, + "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", + "balance": 399893225, + "seqNum": 21474836481, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -2041,8 +2043,7 @@ }, "events": [], "returnValue": { - "type": "SCV_BOOL", - "b": "FALSE" + "type": "SCV_VOID" }, "diagnosticEvents": [] } diff --git a/src/testdata/ledger-close-meta-v1-protocol-22.json b/src/testdata/ledger-close-meta-v1-protocol-22.json index ed10e93cce..51fbf04075 100644 --- a/src/testdata/ledger-close-meta-v1-protocol-22.json +++ b/src/testdata/ledger-close-meta-v1-protocol-22.json @@ -6,23 +6,23 @@ "v": 0 }, "ledgerHeader": { - "hash": "93e3f803b0da9f5517e42c9e266dc01e32619fd4b2af9f8f835e7af84e7accf3", + "hash": "ffbdaf9d06bf2d64cccf83a6f615dbe1278ec807b62a3f9b75efdc5f3df071ba", "header": { "ledgerVersion": 22, - "previousLedgerHash": "32daa64eed92f11534868ed9e62a99e54e9b371169db36a2cc9d80474a156bb9", + "previousLedgerHash": "827f9bd6f723cc87cdb8c79869653a6d8d7aa43efbbfd85c9d582c43c36b9898", "scpValue": { - "txSetHash": "c52790f7476a6ebf8064fa8254480a18f7dff3721769f8f37bf150fae9da1ce1", + "txSetHash": "0ebf9e4eb82a25f2062e011420fd4550498c5a2cbd92e5d9655b0f70e4cbe13f", "closeTime": 0, "upgrades": [], "ext": { "v": "STELLAR_VALUE_SIGNED", "lcValueSignature": { "nodeID": "GDDOUW25MRFLNXQMN3OODP6JQEXSGLMHAFZV4XPQ2D3GA4QFIDMEJG2O", - "signature": "a4a3a1f8eca04d84658264412dbfe5a84e2d0e18396453ea4617bb8b5d8620d3c69cdb216a0d1f212960d7cd4e063dd71cf614f3ac6c1cb9f2ccf6567386780e" + "signature": "88f4659ddd6e43a2d0f4c970238485f986a53f129721a6f342117ec6b8a5bf8f68178fb6a5ed06100b36499bb857f7440f4003675d5ec97100bf890de6c0870e" } } }, - "txSetResultHash": "cd1398cda325d4eada631301e6501421cda30fd0b43da3e5d0ffb26caf073b0a", + "txSetResultHash": "cf65fee29665ff0c2a6910b24c420a487a7416ccd79c683b48aac1d45ad21faa", "bucketListHash": "5ca0cf89a37c934dc25ba1ce52e90c1244a5e51010c6139d0f31afa7b8f32b1f", "ledgerSeq": 6, "totalCoins": 1000000000000000000, @@ -49,7 +49,7 @@ "txSet": { "v": 1, "v1TxSet": { - "previousLedgerHash": "32daa64eed92f11534868ed9e62a99e54e9b371169db36a2cc9d80474a156bb9", + "previousLedgerHash": "827f9bd6f723cc87cdb8c79869653a6d8d7aa43efbbfd85c9d582c43c36b9898", "phases": [ { "v": 0, @@ -176,8 +176,11 @@ ] }, { - "v": 0, - "v0Components": [] + "v": 1, + "parallelTxsComponent": { + "baseFee": null, + "executionStages": [] + } } ] } @@ -185,43 +188,22 @@ "txProcessing": [ { "result": { - "transactionHash": "66efe325ead9f52082c8908b7813bd96793fd5ff0f1e50fdc50b23f68938dd4d", + "transactionHash": "0db2322d85e9d8ea2421559922bb6107429650ebdad304c907480853d465c10d", "result": { - "feeCharged": 300, + "feeCharged": 100, "result": { - "code": "txFEE_BUMP_INNER_SUCCESS", - "innerResultPair": { - "transactionHash": "5ab197acffd4b32d320df39b2b1f76246e2279fa8070c6c690cca1343e5e7e0b", - "result": { - "feeCharged": 200, - "result": { - "code": "txSUCCESS", - "results": [ - { - "code": "opINNER", - "tr": { - "type": "PAYMENT", - "paymentResult": { - "code": "PAYMENT_SUCCESS" - } - } - }, - { - "code": "opINNER", - "tr": { - "type": "PAYMENT", - "paymentResult": { - "code": "PAYMENT_SUCCESS" - } - } - } - ] - }, - "ext": { - "v": 0 + "code": "txSUCCESS", + "results": [ + { + "code": "opINNER", + "tr": { + "type": "PAYMENT", + "paymentResult": { + "code": "PAYMENT_SUCCESS" + } } } - } + ] }, "ext": { "v": 0 @@ -232,13 +214,13 @@ { "type": "LEDGER_ENTRY_STATE", "state": { - "lastModifiedLedgerSeq": 3, + "lastModifiedLedgerSeq": 4, "data": { "type": "ACCOUNT", "account": { - "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", - "balance": 400000000, - "seqNum": 12884901888, + "accountID": "GC4EFXBN6BEENDAX7PBW5PGIIIVH3INMD3OEPQASXOLGOHVVP7ZEMG7X", + "balance": 999999998999989700, + "seqNum": 3, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -246,7 +228,31 @@ "thresholds": "01000000", "signers": [], "ext": { - "v": 0 + "v": 1, + "v1": { + "liabilities": { + "buying": 0, + "selling": 0 + }, + "ext": { + "v": 2, + "v2": { + "numSponsored": 0, + "numSponsoring": 0, + "signerSponsoringIDs": [], + "ext": { + "v": 3, + "v3": { + "ext": { + "v": 0 + }, + "seqLedger": 4, + "seqTime": 0 + } + } + } + } + } } } }, @@ -262,9 +268,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", - "balance": 399999700, - "seqNum": 12884901888, + "accountID": "GC4EFXBN6BEENDAX7PBW5PGIIIVH3INMD3OEPQASXOLGOHVVP7ZEMG7X", + "balance": 999999998999989600, + "seqNum": 3, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -272,7 +278,31 @@ "thresholds": "01000000", "signers": [], "ext": { - "v": 0 + "v": 1, + "v1": { + "liabilities": { + "buying": 0, + "selling": 0 + }, + "ext": { + "v": 2, + "v2": { + "numSponsored": 0, + "numSponsoring": 0, + "signerSponsoringIDs": [], + "ext": { + "v": 3, + "v3": { + "ext": { + "v": 0 + }, + "seqLedger": 4, + "seqTime": 0 + } + } + } + } + } } } }, @@ -296,61 +326,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", - "balance": 399999700, - "seqNum": 12884901888, - "numSubEntries": 0, - "inflationDest": null, - "flags": 0, - "homeDomain": "", - "thresholds": "01000000", - "signers": [], - "ext": { - "v": 0 - } - } - }, - "ext": { - "v": 0 - } - } - }, - { - "type": "LEDGER_ENTRY_UPDATED", - "updated": { - "lastModifiedLedgerSeq": 6, - "data": { - "type": "ACCOUNT", - "account": { - "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", - "balance": 399999700, - "seqNum": 12884901888, - "numSubEntries": 0, - "inflationDest": null, - "flags": 0, - "homeDomain": "", - "thresholds": "01000000", - "signers": [], - "ext": { - "v": 0 - } - } - }, - "ext": { - "v": 0 - } - } - }, - { - "type": "LEDGER_ENTRY_STATE", - "state": { - "lastModifiedLedgerSeq": 4, - "data": { - "type": "ACCOUNT", - "account": { - "accountID": "GCGE27HU2VYQANKL2VZWLCAOJYMEFST5DXPBWQ7BRRPOHUPK626DNG4Q", - "balance": 200010000, - "seqNum": 17179869184, + "accountID": "GC4EFXBN6BEENDAX7PBW5PGIIIVH3INMD3OEPQASXOLGOHVVP7ZEMG7X", + "balance": 999999998999989600, + "seqNum": 3, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -358,7 +336,31 @@ "thresholds": "01000000", "signers": [], "ext": { - "v": 0 + "v": 1, + "v1": { + "liabilities": { + "buying": 0, + "selling": 0 + }, + "ext": { + "v": 2, + "v2": { + "numSponsored": 0, + "numSponsoring": 0, + "signerSponsoringIDs": [], + "ext": { + "v": 3, + "v3": { + "ext": { + "v": 0 + }, + "seqLedger": 4, + "seqTime": 0 + } + } + } + } + } } } }, @@ -374,9 +376,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GCGE27HU2VYQANKL2VZWLCAOJYMEFST5DXPBWQ7BRRPOHUPK626DNG4Q", - "balance": 200010000, - "seqNum": 17179869185, + "accountID": "GC4EFXBN6BEENDAX7PBW5PGIIIVH3INMD3OEPQASXOLGOHVVP7ZEMG7X", + "balance": 999999998999989600, + "seqNum": 4, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -426,18 +428,43 @@ "state": { "lastModifiedLedgerSeq": 5, "data": { - "type": "TRUSTLINE", - "trustLine": { + "type": "ACCOUNT", + "account": { "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", - "asset": { - "assetCode": "CUR1", - "issuer": "GCGE27HU2VYQANKL2VZWLCAOJYMEFST5DXPBWQ7BRRPOHUPK626DNG4Q" - }, - "balance": 0, - "limit": 100, - "flags": 1, + "balance": 399999900, + "seqNum": 8589934593, + "numSubEntries": 1, + "inflationDest": null, + "flags": 0, + "homeDomain": "", + "thresholds": "01000000", + "signers": [], "ext": { - "v": 0 + "v": 1, + "v1": { + "liabilities": { + "buying": 0, + "selling": 0 + }, + "ext": { + "v": 2, + "v2": { + "numSponsored": 0, + "numSponsoring": 0, + "signerSponsoringIDs": [], + "ext": { + "v": 3, + "v3": { + "ext": { + "v": 0 + }, + "seqLedger": 5, + "seqTime": 0 + } + } + } + } + } } } }, @@ -451,18 +478,43 @@ "updated": { "lastModifiedLedgerSeq": 6, "data": { - "type": "TRUSTLINE", - "trustLine": { + "type": "ACCOUNT", + "account": { "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", - "asset": { - "assetCode": "CUR1", - "issuer": "GCGE27HU2VYQANKL2VZWLCAOJYMEFST5DXPBWQ7BRRPOHUPK626DNG4Q" - }, - "balance": 50, - "limit": 100, - "flags": 1, + "balance": 400000900, + "seqNum": 8589934593, + "numSubEntries": 1, + "inflationDest": null, + "flags": 0, + "homeDomain": "", + "thresholds": "01000000", + "signers": [], "ext": { - "v": 0 + "v": 1, + "v1": { + "liabilities": { + "buying": 0, + "selling": 0 + }, + "ext": { + "v": 2, + "v2": { + "numSponsored": 0, + "numSponsoring": 0, + "signerSponsoringIDs": [], + "ext": { + "v": 3, + "v3": { + "ext": { + "v": 0 + }, + "seqLedger": 5, + "seqTime": 0 + } + } + } + } + } } } }, @@ -470,28 +522,49 @@ "v": 0 } } - } - ] - }, - { - "changes": [ + }, { "type": "LEDGER_ENTRY_STATE", "state": { "lastModifiedLedgerSeq": 6, "data": { - "type": "TRUSTLINE", - "trustLine": { - "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", - "asset": { - "assetCode": "CUR1", - "issuer": "GCGE27HU2VYQANKL2VZWLCAOJYMEFST5DXPBWQ7BRRPOHUPK626DNG4Q" - }, - "balance": 50, - "limit": 100, - "flags": 1, + "type": "ACCOUNT", + "account": { + "accountID": "GC4EFXBN6BEENDAX7PBW5PGIIIVH3INMD3OEPQASXOLGOHVVP7ZEMG7X", + "balance": 999999998999989600, + "seqNum": 4, + "numSubEntries": 0, + "inflationDest": null, + "flags": 0, + "homeDomain": "", + "thresholds": "01000000", + "signers": [], "ext": { - "v": 0 + "v": 1, + "v1": { + "liabilities": { + "buying": 0, + "selling": 0 + }, + "ext": { + "v": 2, + "v2": { + "numSponsored": 0, + "numSponsoring": 0, + "signerSponsoringIDs": [], + "ext": { + "v": 3, + "v3": { + "ext": { + "v": 0 + }, + "seqLedger": 6, + "seqTime": 0 + } + } + } + } + } } } }, @@ -505,18 +578,43 @@ "updated": { "lastModifiedLedgerSeq": 6, "data": { - "type": "TRUSTLINE", - "trustLine": { - "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", - "asset": { - "assetCode": "CUR1", - "issuer": "GCGE27HU2VYQANKL2VZWLCAOJYMEFST5DXPBWQ7BRRPOHUPK626DNG4Q" - }, - "balance": 100, - "limit": 100, - "flags": 1, + "type": "ACCOUNT", + "account": { + "accountID": "GC4EFXBN6BEENDAX7PBW5PGIIIVH3INMD3OEPQASXOLGOHVVP7ZEMG7X", + "balance": 999999998999988600, + "seqNum": 4, + "numSubEntries": 0, + "inflationDest": null, + "flags": 0, + "homeDomain": "", + "thresholds": "01000000", + "signers": [], "ext": { - "v": 0 + "v": 1, + "v1": { + "liabilities": { + "buying": 0, + "selling": 0 + }, + "ext": { + "v": 2, + "v2": { + "numSponsored": 0, + "numSponsoring": 0, + "signerSponsoringIDs": [], + "ext": { + "v": 3, + "v3": { + "ext": { + "v": 0 + }, + "seqLedger": 6, + "seqTime": 0 + } + } + } + } + } } } }, @@ -535,22 +633,43 @@ }, { "result": { - "transactionHash": "0db2322d85e9d8ea2421559922bb6107429650ebdad304c907480853d465c10d", + "transactionHash": "66efe325ead9f52082c8908b7813bd96793fd5ff0f1e50fdc50b23f68938dd4d", "result": { - "feeCharged": 100, + "feeCharged": 300, "result": { - "code": "txSUCCESS", - "results": [ - { - "code": "opINNER", - "tr": { - "type": "PAYMENT", - "paymentResult": { - "code": "PAYMENT_SUCCESS" - } + "code": "txFEE_BUMP_INNER_SUCCESS", + "innerResultPair": { + "transactionHash": "5ab197acffd4b32d320df39b2b1f76246e2279fa8070c6c690cca1343e5e7e0b", + "result": { + "feeCharged": 200, + "result": { + "code": "txSUCCESS", + "results": [ + { + "code": "opINNER", + "tr": { + "type": "PAYMENT", + "paymentResult": { + "code": "PAYMENT_SUCCESS" + } + } + }, + { + "code": "opINNER", + "tr": { + "type": "PAYMENT", + "paymentResult": { + "code": "PAYMENT_SUCCESS" + } + } + } + ] + }, + "ext": { + "v": 0 } } - ] + } }, "ext": { "v": 0 @@ -561,13 +680,13 @@ { "type": "LEDGER_ENTRY_STATE", "state": { - "lastModifiedLedgerSeq": 4, + "lastModifiedLedgerSeq": 3, "data": { "type": "ACCOUNT", "account": { - "accountID": "GC4EFXBN6BEENDAX7PBW5PGIIIVH3INMD3OEPQASXOLGOHVVP7ZEMG7X", - "balance": 999999998999989700, - "seqNum": 3, + "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", + "balance": 400000000, + "seqNum": 12884901888, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -575,31 +694,7 @@ "thresholds": "01000000", "signers": [], "ext": { - "v": 1, - "v1": { - "liabilities": { - "buying": 0, - "selling": 0 - }, - "ext": { - "v": 2, - "v2": { - "numSponsored": 0, - "numSponsoring": 0, - "signerSponsoringIDs": [], - "ext": { - "v": 3, - "v3": { - "ext": { - "v": 0 - }, - "seqLedger": 4, - "seqTime": 0 - } - } - } - } - } + "v": 0 } } }, @@ -615,9 +710,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GC4EFXBN6BEENDAX7PBW5PGIIIVH3INMD3OEPQASXOLGOHVVP7ZEMG7X", - "balance": 999999998999989600, - "seqNum": 3, + "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", + "balance": 399999700, + "seqNum": 12884901888, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -625,57 +720,85 @@ "thresholds": "01000000", "signers": [], "ext": { - "v": 1, - "v1": { - "liabilities": { - "buying": 0, - "selling": 0 - }, + "v": 0 + } + } + }, + "ext": { + "v": 0 + } + } + } + ], + "txApplyProcessing": { + "v": 3, + "v3": { + "ext": { + "v": 0 + }, + "txChangesBefore": [ + { + "type": "LEDGER_ENTRY_STATE", + "state": { + "lastModifiedLedgerSeq": 6, + "data": { + "type": "ACCOUNT", + "account": { + "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", + "balance": 399999700, + "seqNum": 12884901888, + "numSubEntries": 0, + "inflationDest": null, + "flags": 0, + "homeDomain": "", + "thresholds": "01000000", + "signers": [], "ext": { - "v": 2, - "v2": { - "numSponsored": 0, - "numSponsoring": 0, - "signerSponsoringIDs": [], - "ext": { - "v": 3, - "v3": { - "ext": { - "v": 0 - }, - "seqLedger": 4, - "seqTime": 0 - } - } - } + "v": 0 + } + } + }, + "ext": { + "v": 0 + } + } + }, + { + "type": "LEDGER_ENTRY_UPDATED", + "updated": { + "lastModifiedLedgerSeq": 6, + "data": { + "type": "ACCOUNT", + "account": { + "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", + "balance": 399999700, + "seqNum": 12884901888, + "numSubEntries": 0, + "inflationDest": null, + "flags": 0, + "homeDomain": "", + "thresholds": "01000000", + "signers": [], + "ext": { + "v": 0 } } + }, + "ext": { + "v": 0 } } }, - "ext": { - "v": 0 - } - } - } - ], - "txApplyProcessing": { - "v": 3, - "v3": { - "ext": { - "v": 0 - }, - "txChangesBefore": [ { "type": "LEDGER_ENTRY_STATE", "state": { - "lastModifiedLedgerSeq": 6, + "lastModifiedLedgerSeq": 4, "data": { "type": "ACCOUNT", "account": { - "accountID": "GC4EFXBN6BEENDAX7PBW5PGIIIVH3INMD3OEPQASXOLGOHVVP7ZEMG7X", - "balance": 999999998999989600, - "seqNum": 3, + "accountID": "GCGE27HU2VYQANKL2VZWLCAOJYMEFST5DXPBWQ7BRRPOHUPK626DNG4Q", + "balance": 200010000, + "seqNum": 17179869184, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -683,31 +806,7 @@ "thresholds": "01000000", "signers": [], "ext": { - "v": 1, - "v1": { - "liabilities": { - "buying": 0, - "selling": 0 - }, - "ext": { - "v": 2, - "v2": { - "numSponsored": 0, - "numSponsoring": 0, - "signerSponsoringIDs": [], - "ext": { - "v": 3, - "v3": { - "ext": { - "v": 0 - }, - "seqLedger": 4, - "seqTime": 0 - } - } - } - } - } + "v": 0 } } }, @@ -723,9 +822,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GC4EFXBN6BEENDAX7PBW5PGIIIVH3INMD3OEPQASXOLGOHVVP7ZEMG7X", - "balance": 999999998999989600, - "seqNum": 4, + "accountID": "GCGE27HU2VYQANKL2VZWLCAOJYMEFST5DXPBWQ7BRRPOHUPK626DNG4Q", + "balance": 200010000, + "seqNum": 17179869185, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -775,43 +874,18 @@ "state": { "lastModifiedLedgerSeq": 5, "data": { - "type": "ACCOUNT", - "account": { + "type": "TRUSTLINE", + "trustLine": { "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", - "balance": 399999900, - "seqNum": 8589934593, - "numSubEntries": 1, - "inflationDest": null, - "flags": 0, - "homeDomain": "", - "thresholds": "01000000", - "signers": [], + "asset": { + "assetCode": "CUR1", + "issuer": "GCGE27HU2VYQANKL2VZWLCAOJYMEFST5DXPBWQ7BRRPOHUPK626DNG4Q" + }, + "balance": 0, + "limit": 100, + "flags": 1, "ext": { - "v": 1, - "v1": { - "liabilities": { - "buying": 0, - "selling": 0 - }, - "ext": { - "v": 2, - "v2": { - "numSponsored": 0, - "numSponsoring": 0, - "signerSponsoringIDs": [], - "ext": { - "v": 3, - "v3": { - "ext": { - "v": 0 - }, - "seqLedger": 5, - "seqTime": 0 - } - } - } - } - } + "v": 0 } } }, @@ -825,43 +899,18 @@ "updated": { "lastModifiedLedgerSeq": 6, "data": { - "type": "ACCOUNT", - "account": { + "type": "TRUSTLINE", + "trustLine": { "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", - "balance": 400000900, - "seqNum": 8589934593, - "numSubEntries": 1, - "inflationDest": null, - "flags": 0, - "homeDomain": "", - "thresholds": "01000000", - "signers": [], + "asset": { + "assetCode": "CUR1", + "issuer": "GCGE27HU2VYQANKL2VZWLCAOJYMEFST5DXPBWQ7BRRPOHUPK626DNG4Q" + }, + "balance": 50, + "limit": 100, + "flags": 1, "ext": { - "v": 1, - "v1": { - "liabilities": { - "buying": 0, - "selling": 0 - }, - "ext": { - "v": 2, - "v2": { - "numSponsored": 0, - "numSponsoring": 0, - "signerSponsoringIDs": [], - "ext": { - "v": 3, - "v3": { - "ext": { - "v": 0 - }, - "seqLedger": 5, - "seqTime": 0 - } - } - } - } - } + "v": 0 } } }, @@ -869,49 +918,28 @@ "v": 0 } } - }, + } + ] + }, + { + "changes": [ { "type": "LEDGER_ENTRY_STATE", "state": { "lastModifiedLedgerSeq": 6, "data": { - "type": "ACCOUNT", - "account": { - "accountID": "GC4EFXBN6BEENDAX7PBW5PGIIIVH3INMD3OEPQASXOLGOHVVP7ZEMG7X", - "balance": 999999998999989600, - "seqNum": 4, - "numSubEntries": 0, - "inflationDest": null, - "flags": 0, - "homeDomain": "", - "thresholds": "01000000", - "signers": [], + "type": "TRUSTLINE", + "trustLine": { + "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", + "asset": { + "assetCode": "CUR1", + "issuer": "GCGE27HU2VYQANKL2VZWLCAOJYMEFST5DXPBWQ7BRRPOHUPK626DNG4Q" + }, + "balance": 50, + "limit": 100, + "flags": 1, "ext": { - "v": 1, - "v1": { - "liabilities": { - "buying": 0, - "selling": 0 - }, - "ext": { - "v": 2, - "v2": { - "numSponsored": 0, - "numSponsoring": 0, - "signerSponsoringIDs": [], - "ext": { - "v": 3, - "v3": { - "ext": { - "v": 0 - }, - "seqLedger": 6, - "seqTime": 0 - } - } - } - } - } + "v": 0 } } }, @@ -925,43 +953,18 @@ "updated": { "lastModifiedLedgerSeq": 6, "data": { - "type": "ACCOUNT", - "account": { - "accountID": "GC4EFXBN6BEENDAX7PBW5PGIIIVH3INMD3OEPQASXOLGOHVVP7ZEMG7X", - "balance": 999999998999988600, - "seqNum": 4, - "numSubEntries": 0, - "inflationDest": null, - "flags": 0, - "homeDomain": "", - "thresholds": "01000000", - "signers": [], + "type": "TRUSTLINE", + "trustLine": { + "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", + "asset": { + "assetCode": "CUR1", + "issuer": "GCGE27HU2VYQANKL2VZWLCAOJYMEFST5DXPBWQ7BRRPOHUPK626DNG4Q" + }, + "balance": 100, + "limit": 100, + "flags": 1, "ext": { - "v": 1, - "v1": { - "liabilities": { - "buying": 0, - "selling": 0 - }, - "ext": { - "v": 2, - "v2": { - "numSponsored": 0, - "numSponsoring": 0, - "signerSponsoringIDs": [], - "ext": { - "v": 3, - "v3": { - "ext": { - "v": 0 - }, - "seqLedger": 6, - "seqTime": 0 - } - } - } - } - } + "v": 0 } } }, diff --git a/src/transactions/test/TxEnvelopeTests.cpp b/src/transactions/test/TxEnvelopeTests.cpp index 4e2a8ebadc..63ff3bc709 100644 --- a/src/transactions/test/TxEnvelopeTests.cpp +++ b/src/transactions/test/TxEnvelopeTests.cpp @@ -64,11 +64,11 @@ TEST_CASE("txset - correct apply order", "[tx][envelope]") auto tx1 = b1.tx({accountMerge(a1)}); auto tx2 = a1.tx({a1.op(payment(root, 112)), a1.op(payment(root, 101))}); - auto txSet = - makeTxSetFromTransactions(TxSetTransactions{tx1, tx2}, *app, 0, 0) - .second; + auto txSet = makeTxSetFromTransactions({tx1, tx2}, *app, 0, 0).second; - auto txs = txSet->getTxsInApplyOrder(); + auto txs = + txSet->getPhasesInApplyOrder()[static_cast(TxSetPhase::CLASSIC)] + .getNonParallelTxs(); REQUIRE(txs.size() == 2); // Sort for apply re-orders transaction set based on the contents hash if (lessThanXored(tx1->getFullHash(), tx2->getFullHash(), diff --git a/src/util/ProtocolVersion.h b/src/util/ProtocolVersion.h index bf94aa0046..d6cf6eb0a2 100644 --- a/src/util/ProtocolVersion.h +++ b/src/util/ProtocolVersion.h @@ -33,7 +33,8 @@ enum class ProtocolVersion : uint32_t V_18, V_19, V_20, - V_21 + V_21, + V_22, }; // Checks whether provided protocolVersion is before (i.e. strictly lower than) @@ -49,4 +50,6 @@ bool protocolVersionEquals(uint32_t protocolVersion, ProtocolVersion equalsVersion); constexpr ProtocolVersion SOROBAN_PROTOCOL_VERSION = ProtocolVersion::V_20; +constexpr ProtocolVersion PARALLEL_SOROBAN_PHASE_PROTOCOL_VERSION = + ProtocolVersion::V_22; }