From b930f32abc57878d552b7d96653499fabf56c943 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. --- src/herder/HerderImpl.cpp | 4 +- src/herder/TransactionQueue.cpp | 4 +- src/herder/TransactionQueue.h | 2 +- src/herder/TxSetFrame.cpp | 1675 +++++++++++------ src/herder/TxSetFrame.h | 304 ++- src/herder/TxSetUtils.cpp | 51 +- src/herder/TxSetUtils.h | 26 +- src/herder/test/HerderTests.cpp | 214 ++- src/herder/test/TestTxSetUtils.cpp | 82 +- src/herder/test/TestTxSetUtils.h | 4 +- src/herder/test/TransactionQueueTests.cpp | 2 +- src/herder/test/TxSetTests.cpp | 224 ++- src/herder/test/UpgradesTests.cpp | 13 +- src/history/test/HistoryTests.cpp | 4 +- src/history/test/HistoryTestsUtils.cpp | 4 +- src/ledger/LedgerManagerImpl.cpp | 280 +-- src/ledger/LedgerManagerImpl.h | 13 +- src/main/main.cpp | 2 +- src/test/TxTests.cpp | 6 +- ...ger-close-meta-v1-protocol-23-soroban.json | 29 +- .../ledger-close-meta-v1-protocol-23.json | 823 ++++---- src/testdata/txset/v_next.csv | 98 +- src/transactions/test/TxEnvelopeTests.cpp | 8 +- src/util/ProtocolVersion.h | 5 +- 24 files changed, 2366 insertions(+), 1511 deletions(-) diff --git a/src/herder/HerderImpl.cpp b/src/herder/HerderImpl.cpp index d2d3565c1a..8548acb025 100644 --- a/src/herder/HerderImpl.cpp +++ b/src/herder/HerderImpl.cpp @@ -1350,7 +1350,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, @@ -1415,7 +1415,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] = diff --git a/src/herder/TransactionQueue.cpp b/src/herder/TransactionQueue.cpp index 8950a01e56..695b1af788 100644 --- a/src/herder/TransactionQueue.cpp +++ b/src/herder/TransactionQueue.cpp @@ -938,11 +938,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 86cd2c97fd..81b2409853 100644 --- a/src/herder/TransactionQueue.h +++ b/src/herder/TransactionQueue.h @@ -143,7 +143,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/TxSetFrame.cpp b/src/herder/TxSetFrame.cpp index e4f951f458..cf4afc218d 100644 --- a/src/herder/TxSetFrame.cpp +++ b/src/herder/TxSetFrame.cpp @@ -37,17 +37,111 @@ 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 +validateSequentialPhaseXDRStructure(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; + } + } + } + return true; +} +#endif + bool validateTxSetXDRStructure(GeneralizedTransactionSet const& txSet) { +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION + int const MAX_PHASE = 1; +#else + int 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)) + if (txSetV1.phases.size() != phaseCount) { CLOG_DEBUG(Herder, "Got bad txSet: exactly 2 phases are expected, got {}", @@ -55,62 +149,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 (!validateSequentialPhaseXDRStructure(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 @@ -124,14 +198,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; @@ -143,8 +217,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) { @@ -158,7 +232,7 @@ computePerOpFee(TransactionFrameBase const& tx, uint32_t ledgerVersion) } void -transactionsToTransactionSetXDR(TxSetTransactions const& txs, +transactionsToTransactionSetXDR(TxFrameList const& txs, Hash const& previousLedgerHash, TransactionSet& txSet) { @@ -172,63 +246,186 @@ transactionsToTransactionSetXDR(TxSetTransactions const& txs, txSet.previousLedgerHash = previousLedgerHash; } +void +sequentialPhaseToXdr(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& tx : thread) + { + xdrThread.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; - - for (int i = 0; i < phaseTxs.size(); ++i) + generalizedTxSet.v1TxSet().phases.resize(phases.size()); + for (int i = 0; i < phases.size(); ++i) { - auto const& txPhase = phaseTxs[i]; - auto& phase = - generalizedTxSet.v1TxSet().phases.emplace_back().v0Components(); + auto const& txPhase = phases[i]; + txPhase.toXDR(generalizedTxSet.v1TxSet().phases[i]); + } +} - auto const& feeMap = phaseInclusionFeeMap[i]; - std::map, size_t> feeTxCount; - for (auto const& [tx, fee] : feeMap) - { - ++feeTxCount[fee]; - } - // Reserve a component per unique base fee in order to have the correct - // pointers in componentPerBid map. - phase.reserve(feeTxCount.size()); +TxFrameList +sortedForApplySequential(TxFrameList const& txs, Hash const& txSetHash) +{ + TxFrameList retList; + retList.reserve(txs.size()); + + auto txQueues = TxSetUtils::buildAccountTxQueues(txs); - std::map, xdr::xvector*> - componentPerBid; - for (auto const& [fee, txCount] : feeTxCount) + // 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();) { - phase.emplace_back(TXSET_COMP_TXS_MAYBE_DISCOUNTED_FEE); - auto& discountedFeeComponent = phase.back().txsMaybeDiscountedFee(); - if (fee) + auto& txQueue = *it; + curBatch.emplace_back(txQueue->getTopTx()); + txQueue->popTopTx(); + if (txQueue->empty()) { - discountedFeeComponent.baseFee.activate() = *fee; + // done with that user + it = txQueues.erase(it); + } + else + { + ++it; } - componentPerBid[fee] = &discountedFeeComponent.txs; - componentPerBid[fee]->reserve(txCount); } - auto sortedTxs = TxSetUtils::sortTxsInHashOrder(txPhase); - for (auto const& tx : sortedTxs) + } + + 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) { - componentPerBid[feeMap.find(tx)->second]->push_back( - tx->getEnvelope()); + retList.push_back(tx); } } + + 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) + { + std::sort(thread.begin(), thread.end(), sorter); + } + // There is no need to shuffle threads in the stage, as they are + // independent, so the apply order doesn't matter even if the threads + // are being applied sequentially. + } + std::sort(sortedStages.begin(), sortedStages.end(), + [&sorter](auto const& a, auto 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(TxSetTransactions const& phase, Application& app, +phaseTxsAreValid(TxSetPhaseFrame const& phase, Application& app, uint64_t lowerBoundCloseTimeOffset, uint64_t upperBoundCloseTimeOffset) { @@ -258,12 +455,186 @@ phaseTxsAreValid(TxSetTransactions const& phase, Application& app, } 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 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( + txs, 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); + } + 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; + } + if (countOps(txs) > surgeOpsCutoff) + { + baseFee = lowestBaseFee; + } + } + return baseFee; +} + } // namespace TxSetXDRFrame::TxSetXDRFrame(TransactionSet const& xdrTxSet) : mXDRTxSet(xdrTxSet) , mEncodedSize(xdr::xdr_argpack_size(xdrTxSet)) - , mHash(computeNonGenericTxSetContentsHash(xdrTxSet)) + , mHash(computeNonGeneralizedTxSetContentsHash(xdrTxSet)) { } @@ -301,7 +672,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 @@ -310,7 +681,7 @@ makeTxSetFromTransactions(TxSetPhaseTransactions const& txPhases, #endif ) { - TxSetPhaseTransactions invalidTxs; + PerPhaseTransactionList invalidTxs; invalidTxs.resize(txPhases.size()); return makeTxSetFromTransactions(txPhases, app, lowerBoundCloseTimeOffset, upperBoundCloseTimeOffset, invalidTxs @@ -322,10 +693,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 @@ -336,12 +707,12 @@ makeTxSetFromTransactions(TxSetPhaseTransactions const& txPhases, releaseAssert(txPhases.size() <= static_cast(TxSetPhase::PHASE_COUNT)); - TxSetPhaseTransactions validatedPhases; - for (int i = 0; i < txPhases.size(); ++i) + std::vector validatedPhases; + for (size_t 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; })) { @@ -350,20 +721,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().push_back(includedTxs); + } + validatedPhases.emplace_back( + TxSetPhaseFrame(std::move(stages), inclusionFeeMap)); + } } auto const& lclHeader = app.getLedgerManager().getLastClosedLedgerHeader(); @@ -372,7 +768,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(); @@ -403,7 +799,7 @@ makeTxSetFromTransactions(TxSetPhaseTransactions const& txPhases, outputApplicableTxSet->numPhases(); if (valid) { - for (int i = 0; i < preliminaryApplicableTxSet->numPhases(); ++i) + for (size_t i = 0; i < preliminaryApplicableTxSet->numPhases(); ++i) { valid = valid && preliminaryApplicableTxSet->sizeTx( static_cast(i)) == @@ -429,14 +825,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; @@ -446,7 +849,7 @@ TxSetXDRFrame::makeEmpty(LedgerHeaderHistoryEntry const& lclHeader) TxSetXDRFrameConstPtr TxSetXDRFrame::makeFromHistoryTransactions(Hash const& previousLedgerHash, - TxSetTransactions const& txs) + TxFrameList const& txs) { TransactionSet txSet; transactionsToTransactionSetXDR(txs, previousLedgerHash, txSet); @@ -455,49 +858,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 sequential tx sets for now. + std::vector overridePhases; + for (size_t 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]; @@ -534,7 +946,7 @@ TxSetXDRFrame::prepareForApply(Application& app) const } #endif ZoneScoped; - std::unique_ptr txSet{}; + std::vector phaseFrames; if (isGeneralizedTxSet()) { auto const& xdrTxSet = std::get(mXDRTxSet); @@ -544,41 +956,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()); + auto const& xdrPhases = xdrTxSet.v1TxSet().phases; - txSet = std::unique_ptr(new ApplicableTxSetFrame( - app, true, previousLedgerHash(), defaultPhases, mHash)); - - releaseAssert(phases.size() <= - static_cast(TxSetPhase::PHASE_COUNT)); - for (auto phaseId = 0; phaseId < phases.size(); phaseId++) + for (auto const& xdrPhase : xdrPhases) { - auto const& phase = phases[phaseId]; - auto const& components = phase.v0Components(); - for (auto const& component : components) + auto maybePhase = + TxSetPhaseFrame::makeFromWire(app.getNetworkID(), xdrPhase); + if (!maybePhase) { - switch (component.type()) + return nullptr; + } + phaseFrames.emplace_back(std::move(*maybePhase)); + } + for (size_t phaseId = 0; phaseId < phaseFrames.size(); ++phaseId) + { + auto phase = static_cast(phaseId); + for (auto const& tx : phaseFrames[phaseId]) + { + 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; } } } @@ -586,20 +986,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; } - txSet->computeTxFeesForNonGeneralizedSet( - app.getLedgerManager().getLastClosedLedgerHeader().header); + phaseFrames.emplace_back(std::move(*maybePhase)); } - return txSet; + return std::unique_ptr(new ApplicableTxSetFrame( + app, isGeneralizedTxSet(), previousLedgerHash(), phaseFrames, mHash)); } bool @@ -636,9 +1033,28 @@ 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) + { + totalSize += thread.size(); + } + } + break; +#endif + default: + break; } } return totalSize; @@ -677,12 +1093,33 @@ 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) + { + totalSize += + std::accumulate(thread.begin(), thread.end(), 0ull, + accumulateTxsFn); + } + } + break; +#endif + default: + break; } } return totalSize; @@ -694,10 +1131,10 @@ TxSetXDRFrame::sizeOpTotalForLogging() const } } -TxSetPhaseTransactions +PerPhaseTransactionList TxSetXDRFrame::createTransactionFrames(Hash const& networkID) const { - TxSetPhaseTransactions phaseTxs; + PerPhaseTransactionList phaseTxs; if (isGeneralizedTxSet()) { auto const& txSet = @@ -705,74 +1142,425 @@ 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()) { - txs.emplace_back( - TransactionFrameBase::makeTransactionFromWire(networkID, - tx)); + 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) + { + for (auto const& thread : stage) + { + for (auto const& tx : thread) + { + txs.emplace_back( + TransactionFrameBase::makeTransactionFromWire( + networkID, tx)); + } + } + } + break; +#endif + default: + break; } } } else { - auto& txs = phaseTxs.emplace_back(); - auto const& txSet = std::get(mXDRTxSet).txs; - for (auto const& tx : txSet) + auto& txs = phaseTxs.emplace_back(); + auto const& txSet = std::get(mXDRTxSet).txs; + for (auto const& tx : txSet) + { + txs.emplace_back( + TransactionFrameBase::makeTransactionFromWire(networkID, tx)); + } + } + return phaseTxs; +} + +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(TxStageFrameList const& txs, + size_t stageIndex) + : mStages(txs), mStageIndex(stageIndex) +{ +} + +TransactionFrameBasePtr +TxSetPhaseFrame::Iterator::operator*() const +{ + + if (mStageIndex >= mStages.size() || + mThreadIndex >= mStages[mStageIndex].size() || + mTxIndex >= mStages[mStageIndex][mThreadIndex].size()) + { + throw std::runtime_error("TxPhase iterator out of bounds"); + } + return mStages[mStageIndex][mThreadIndex][mTxIndex]; +} + +TxSetPhaseFrame::Iterator& +TxSetPhaseFrame::Iterator::operator++() +{ + if (mStageIndex >= mStages.size()) + { + throw std::runtime_error("TxPhase iterator out of bounds"); + } + ++mTxIndex; + if (mTxIndex >= mStages[mStageIndex][mThreadIndex].size()) + { + mTxIndex = 0; + ++mThreadIndex; + if (mThreadIndex >= mStages[mStageIndex].size()) + { + mThreadIndex = 0; + ++mStageIndex; + } + } + 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. + &mStages == &other.mStages; +} + +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& env : xdrThread) + { + auto tx = TransactionFrameBase::makeTransactionFromWire( + networkID, env); + if (!tx->XDRProvidesValidFee()) + { + CLOG_DEBUG(Herder, "Got bad generalized txSet: " + "transaction has invalid XDR"); + return std::nullopt; + } + thread.push_back(tx); + inclusionFeeMap[tx] = baseFee; + } + if (!std::is_sorted(thread.begin(), thread.end(), + &TxSetUtils::hashTxSorter)) + { + 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(), + b.front()); + })) + { + CLOG_DEBUG(Herder, "Got bad generalized txSet: " + "stage is not sorted"); + return std::nullopt; + } + } + if (!std::is_sorted(stages.begin(), stages.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: " + "stages are 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 const& txs, std::shared_ptr inclusionFeeMap) + : mInclusionFeeMap(inclusionFeeMap), mIsParallel(false) +{ + if (!txs.empty()) + { + mStages.emplace_back().push_back(txs); + } +} + +TxSetPhaseFrame::TxSetPhaseFrame( + TxStageFrameList&& txs, std::shared_ptr inclusionFeeMap) + : mStages(txs), mInclusionFeeMap(inclusionFeeMap), mIsParallel(true) +{ +} + +TxSetPhaseFrame::Iterator +TxSetPhaseFrame::begin() const +{ + return TxSetPhaseFrame::Iterator(mStages, 0); +} + +TxSetPhaseFrame::Iterator +TxSetPhaseFrame::end() const +{ + return TxSetPhaseFrame::Iterator(mStages, mStages.size()); +} + +size_t +TxSetPhaseFrame::size() const +{ + + size_t size = 0; + for (auto const& stage : mStages) + { + for (auto const& thread : stage) { - txs.emplace_back( - TransactionFrameBase::makeTransactionFromWire(networkID, tx)); + size += thread.size(); } } - return phaseTxs; + return size; } -size_t -TxSetXDRFrame::encodedSize() const +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 mIsParallel; } -void -TxSetXDRFrame::toXDR(GeneralizedTransactionSet& txSet) const +TxStageFrameList const& +TxSetPhaseFrame::getParallelStages() const { - releaseAssert(isGeneralizedTxSet()); - txSet = std::get(mXDRTxSet); + releaseAssert(isParallel()); + return mStages; +} + +TxFrameList const& +TxSetPhaseFrame::getSequentialTxs() const +{ + releaseAssert(!isParallel()); + static TxFrameList empty; + if (mStages.empty()) + { + return empty; + } + return mStages.at(0).at(0); } void -TxSetXDRFrame::storeXDR(StoredTransactionSet& txSet) const +TxSetPhaseFrame::toXDR(TransactionPhase& xdrPhase) const { - if (isGeneralizedTxSet()) + + if (isParallel()) { - txSet.v(1); - txSet.generalizedTxSet() = - std::get(mXDRTxSet); + +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION + parallelPhaseToXdr(mStages, *mInclusionFeeMap, xdrPhase); +#else + releaseAssert(false); +#endif } else { - txSet.v(0); - txSet.txSet() = std::get(mXDRTxSet); + sequentialPhaseToXdr(getSequentialTxs(), *mInclusionFeeMap, xdrPhase); + } +} + +InclusionFeeMap const& +TxSetPhaseFrame::getInclusionFeeMap() const +{ + return *mInclusionFeeMap; +} + +TxSetPhaseFrame +TxSetPhaseFrame::sortedForApply(Hash const& txSetHash) const +{ + if (isParallel()) + { + return TxSetPhaseFrame(sortedForApplyParallel(mStages, txSetHash), + mInclusionFeeMap); + } + else + { + return TxSetPhaseFrame( + sortedForApplySequential(getSequentialTxs(), txSetHash), + mInclusionFeeMap); } } -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) - , mPhaseInclusionFeeMap(mTxPhases.size()) + , mPhases(phases) , mContentsHash(contentsHash) { releaseAssert(previousLedgerHash == @@ -781,12 +1569,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) { } @@ -797,73 +1586,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 @@ -908,10 +1657,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() < @@ -928,13 +1677,33 @@ ApplicableTxSetFrame::checkValid(Application& app, } return true; }; - - if (!checkFeeMap(getInclusionFeeMap(TxSetPhase::CLASSIC))) + // Generalized transaction sets should always have 2 phases by + // construction. + releaseAssert(mPhases.size() == + static_cast(TxSetPhase::PHASE_COUNT)); + 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; } } @@ -952,9 +1721,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) { @@ -976,6 +1745,7 @@ ApplicableTxSetFrame::checkValid(Application& app, } { + LedgerTxn ltx(app.getLedgerTxnRoot()); auto limits = app.getLedgerManager().maxLedgerResources( /* isSoroban */ true); if (anyGreater(*totalTxSetRes, limits)) @@ -988,9 +1758,8 @@ ApplicableTxSetFrame::checkValid(Application& app, } } } - bool allValid = true; - for (auto const& txs : mTxPhases) + for (auto const& txs : mPhases) { if (!phaseTxsAreValid(txs, app, lowerBoundCloseTimeOffset, upperBoundCloseTimeOffset)) @@ -1031,7 +1800,7 @@ size_t ApplicableTxSetFrame::sizeOp(TxSetPhase phase) const { ZoneScoped; - auto const& txs = mTxPhases.at(static_cast(phase)); + auto const& txs = mPhases.at(static_cast(phase)); return std::accumulate(txs.begin(), txs.end(), size_t(0), [&](size_t a, TransactionFrameBasePtr const& tx) { return a + tx->getNumOperations(); @@ -1043,150 +1812,51 @@ ApplicableTxSetFrame::sizeOpTotal() const { ZoneScoped; size_t total = 0; - for (int i = 0; i < mTxPhases.size(); i++) + for (size_t 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 (size_t i = 0; i < mPhases.size(); i++) { total += sizeTx(static_cast(i)); } return total; } -void -ApplicableTxSetFrame::computeTxFeesForNonGeneralizedSet( - LedgerHeader const& lclHeader) -{ - ZoneScoped; - auto ledgerVersion = lclHeader.ledgerVersion; - int64_t lowBaseFee = std::numeric_limits::max(); - releaseAssert(mTxPhases.size() == 1); - for (auto const& 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) -{ - ZoneScoped; - 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 = getInclusionFeeMapMut(TxSetPhase::CLASSIC); - for (auto const& tx : phase) - { - feeMap[tx] = baseFee; - } -} - -void -ApplicableTxSetFrame::computeTxFees( - TxSetPhase phase, LedgerHeader const& ledgerHeader, - SurgePricingLaneConfig const& surgePricingConfig, - std::vector const& lowestLaneFee, - std::vector const& hadTxNotFittingLane) -{ - 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 = getInclusionFeeMapMut(phase); - for (auto const& tx : txs) - { - feeMap[tx] = laneBaseFee[surgePricingConfig.getLane(*tx)]; - } -} - std::optional -ApplicableTxSetFrame::getTxBaseFee(TransactionFrameBaseConstPtr const& tx, - LedgerHeader const& lclHeader) const +ApplicableTxSetFrame::getTxBaseFee(TransactionFrameBaseConstPtr const& tx) const { - 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; } } throw std::runtime_error("Transaction not found in tx set"); - return std::nullopt; } 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))) { @@ -1205,16 +1875,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; } @@ -1223,15 +1890,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; } @@ -1248,7 +1913,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)); } @@ -1287,18 +1953,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 (size_t 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; } @@ -1308,8 +1973,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].getSequentialTxs(), + mPreviousLedgerHash, txSet); } void @@ -1317,10 +1983,9 @@ ApplicableTxSetFrame::toXDR(GeneralizedTransactionSet& generalizedTxSet) const { ZoneScoped; releaseAssert(isGeneralizedTxSet()); - releaseAssert(mTxPhases.size() <= + releaseAssert(mPhases.size() <= static_cast(TxSetPhase::PHASE_COUNT)); - transactionsToGeneralizedTransactionSetXDR(mTxPhases, mPhaseInclusionFeeMap, - mPreviousLedgerHash, + transactionsToGeneralizedTransactionSetXDR(mPhases, mPreviousLedgerHash, generalizedTxSet); } @@ -1349,168 +2014,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()); - auto& inclusionFeeMap = getInclusionFeeMapMut(phase); - 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) - { - inclusionFeeMap[tx] = baseFee; - } - } - return std::is_sorted(phaseTxs.begin() + oldSize, phaseTxs.end(), - &TxSetUtils::hashTxSorter); -} - -void -ApplicableTxSetFrame::applySurgePricing(Application& app) -{ - ZoneScoped; - releaseAssert(mTxPhases.size() <= - static_cast(TxSetPhase::PHASE_COUNT)); - auto const& lclHeader = - app.getLedgerManager().getLastClosedLedgerHeader().header; - for (int i = 0; i < mTxPhases.size(); i++) - { - TxSetPhase phaseType = static_cast(i); - auto& phase = mTxPhases[i]; - 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( - phase, 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( - phase, 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); - } - } -} - -std::unordered_map> const& -ApplicableTxSetFrame::getInclusionFeeMap(TxSetPhase phase) const -{ - size_t phaseId = static_cast(phase); - releaseAssert(phaseId < mPhaseInclusionFeeMap.size()); - return mPhaseInclusionFeeMap[phaseId]; -} - -std::unordered_map>& -ApplicableTxSetFrame::getInclusionFeeMapMut(TxSetPhase phase) -{ - 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 e8942046c9..beb5c65858 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,157 @@ 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 transactions that should +// generally be applied sequentially. However, not all the transactions in +// the thread are necessarily conflicting with each other; it is possible +// that some, or even all transactions in the thread structure can be applied +// in parallel with each other (depending on their footprints). +// +// This structure mimics the XDR structure of the `ParallelTxsComponent`. +using TxThreadFrame = TxFrameList; +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& getSequentialTxs() const; + + // Serializes this phase to the provided XDR. + void toXDR(TransactionPhase& xdrPhase) const; + + // Iterator over 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(TxStageFrameList const& txs, size_t stageIndex); + TxStageFrameList const& mStages; + size_t mStageIndex = 0; + size_t mThreadIndex = 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 const& 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; + + TxStageFrameList mStages; + std::shared_ptr mInclusionFeeMap; + bool mIsParallel; +}; + // Transaction set that is suitable for being applied to the ledger. // // This is not necessarily a fully *valid* transaction set: further validation @@ -201,51 +354,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 all the transactions belonging to this frame in arbitrary order. - TxSetTransactions const& getTxsForPhase(TxSetPhase phase) const; + // Gets the phase frame for the given phase in arbitrary order. + TxSetPhaseFrame const& getPhase(TxSetPhase phase) 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 with transactions in + // arbitrary order. + std::vector const& getPhases() const; + + // Gets all the phases of this transaction set, each phase with + // transactions sorted for apply. + // + // 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. // - // The order satisfies: - // * transactions for an account are sorted by sequence number (ascending) - // * the order between accounts is randomized - TxSetTransactions getTxsInApplyOrder() const; + // 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 +420,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 +439,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,68 +453,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; - void computeTxFeesForNonGeneralizedSet(LedgerHeader const& lclHeader); - - 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); - - void computeTxFees(TxSetPhase phase, LedgerHeader const& ledgerHeader, - SurgePricingLaneConfig const& surgePricingConfig, - std::vector const& lowestLaneFee, - std::vector const& hadTxNotFittingLane); - 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> const& - getInclusionFeeMap(TxSetPhase phase) const; - std::unordered_map>& - getInclusionFeeMapMut(TxSetPhase phase); + 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 are 2 phases (classic and soroban). - std::vector mTxPhases; + // Starting with protocol 20, there are 2 phases (classic and Soroban). + std::vector const mPhases; - 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 7b10651bfc..f81456b584 100644 --- a/src/herder/TxSetUtils.cpp +++ b/src/herder/TxSetUtils.cpp @@ -35,8 +35,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()); @@ -45,7 +45,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) { @@ -105,17 +105,42 @@ 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) + { + std::sort(thread.begin(), thread.end(), TxSetUtils::hashTxSorter); + } + std::sort(stage.begin(), stage.end(), [](auto const& a, auto const& b) { + releaseAssert(!a.empty() && !b.empty()); + return hashTxSorter(a.front(), b.front()); + }); + } + std::sort(sortedStages.begin(), sortedStages.end(), + [](auto const& a, auto 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; @@ -136,8 +161,8 @@ 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) { @@ -149,7 +174,7 @@ TxSetUtils::getInvalidTxList(TxSetTransactions const& txs, Application& app, ls.getLedgerHeader().currentToModify().ledgerSeq = app.getLedgerManager().getLastClosedLedgerNum() + 1; - TxSetTransactions invalidTxs; + TxFrameList invalidTxs; for (auto const& tx : txs) { @@ -165,11 +190,11 @@ 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); diff --git a/src/herder/TxSetUtils.h b/src/herder/TxSetUtils.h index ee6abb6242..a7e94421a6 100644 --- a/src/herder/TxSetUtils.h +++ b/src/herder/TxSetUtils.h @@ -34,25 +34,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); - - 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 acdf87a2a7..58f63b3f7d 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); @@ -364,7 +364,7 @@ testTxSet(uint32 protocolVersion) setSeqNum(std::static_pointer_cast(txPtr), txs[0]->getSeqNum() + 5); - 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.back() = accounts.back().tx( {payment(accounts.back().getPublicKey(), 10000)}); - TxSetTransactions removed; + TxFrameList removed; auto txSet = makeTxSetFromTransactions(txs, *app, 0, 0, removed).second; REQUIRE(removed.size() == 1); @@ -388,7 +388,7 @@ testTxSet(uint32 protocolVersion) 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() == 1); @@ -422,8 +422,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()); @@ -437,7 +437,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}); @@ -449,7 +449,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}); @@ -463,7 +463,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}); @@ -479,7 +479,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}); @@ -492,7 +492,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}); @@ -506,7 +506,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}); @@ -522,7 +522,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}); @@ -588,7 +588,7 @@ TEST_CASE("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; @@ -658,7 +658,7 @@ TEST_CASE("txset with PreconditionsV2", "[herder][txset]") { auto txInvalid = transactionWithV2Precondition( *app, a2, 1, 100, ledgerBoundsCond(lclNum + 2, 0)); - TxSetTransactions removed; + TxFrameList removed; auto txSet = makeTxSetFromTransactions({tx1, txInvalid}, *app, 0, 0, removed); REQUIRE(removed.back() == txInvalid); @@ -675,7 +675,7 @@ TEST_CASE("txset with PreconditionsV2", "[herder][txset]") { auto txInvalid = transactionWithV2Precondition( *app, a2, 1, 100, ledgerBoundsCond(0, lclNum)); - TxSetTransactions removed; + TxFrameList removed; auto txSet = makeTxSetFromTransactions({tx1, txInvalid}, *app, 0, 0, removed); REQUIRE(removed.back() == txInvalid); @@ -704,14 +704,14 @@ TEST_CASE("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); @@ -730,14 +730,14 @@ TEST_CASE("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); @@ -749,7 +749,7 @@ TEST_CASE("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); @@ -759,7 +759,7 @@ TEST_CASE("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()); @@ -774,14 +774,14 @@ TEST_CASE("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); @@ -797,7 +797,7 @@ TEST_CASE("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()); } @@ -1078,7 +1078,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; @@ -1089,17 +1089,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] = @@ -1107,7 +1107,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) { @@ -1148,7 +1148,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; @@ -1166,10 +1166,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; @@ -1222,7 +1222,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; @@ -1287,10 +1287,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; @@ -1303,11 +1303,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 @@ -1317,11 +1317,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") @@ -1329,21 +1329,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).getSequentialTxs(); REQUIRE(classicTxs.size() == 1); REQUIRE(classicTxs[0]->getFullHash() == tx->getFullHash()); - auto const& sorobanTxs = txSet->getTxsForPhase(TxSetPhase::SOROBAN); + auto const& sorobanTxs = + txSet->getPhase(TxSetPhase::SOROBAN).getSequentialTxs(); REQUIRE(sorobanTxs.size() == 1); REQUIRE(sorobanTxs[0]->getFullHash() == sorobanTxHighFee->getFullHash()); @@ -1365,11 +1367,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) @@ -1378,10 +1380,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).getSequentialTxs(); 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 = @@ -1397,11 +1400,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; @@ -1410,9 +1413,9 @@ 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).getSequentialTxs(); auto const& sorobanTxs = - txSet->getTxsForPhase(TxSetPhase::SOROBAN); + txSet->getPhase(TxSetPhase::SOROBAN).getSequentialTxs(); REQUIRE(classicTxs.size() == 1); REQUIRE(classicTxs[0]->getFullHash() == tx->getFullHash()); // Depending on resources generated for each tx, can only @@ -1425,7 +1428,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, @@ -1453,12 +1456,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); @@ -1478,8 +1475,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()) { @@ -1506,7 +1504,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()) { @@ -1517,6 +1515,7 @@ TEST_CASE("surge pricing with DEX separation", "[herder][txset]") REQUIRE(*baseFee == expectedNonDexBaseFee); } } + REQUIRE(cntA == expectedTxsA); REQUIRE(cntB == expectedTxsB); REQUIRE(cntC == expectedTxsC); @@ -1686,19 +1685,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 @@ -1706,6 +1707,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) @@ -2211,7 +2213,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 @@ -2232,10 +2234,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) + .getSequentialTxs(), + *app, closeTimeOffset, closeTimeOffset, removed); REQUIRE(removed.size() == (expectValid ? 0 : 1)); }; @@ -3147,18 +3150,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; }, @@ -4407,7 +4432,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()) @@ -4421,7 +4446,7 @@ externalize(SecretKey const& sk, LedgerManager& lm, HerderImpl& herder, } } - TxSetPhaseTransactions txsPhases{classicTxs}; + PerPhaseTransactionList txsPhases{classicTxs}; txsPhases.emplace_back(sorobanTxs); @@ -4475,12 +4500,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", diff --git a/src/herder/test/TestTxSetUtils.cpp b/src/herder/test/TestTxSetUtils.cpp index e27c43c3d5..302be58dde 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,48 @@ 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& thread = + component.executionStages.emplace_back().emplace_back(); + for (auto const& tx : txs) + { + thread.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 +123,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 53760d1e8b..922016a58e 100644 --- a/src/herder/test/TransactionQueueTests.cpp +++ b/src/herder/test/TransactionQueueTests.cpp @@ -202,7 +202,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 0f90c62921..19a353fc17 100644 --- a/src/herder/test/TxSetTests.cpp +++ b/src/herder/test/TxSetTests.cpp @@ -474,14 +474,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) { @@ -524,6 +527,7 @@ TEST_CASE("generalized tx set XDR conversion", "[txset]") LedgerTxn ltx(app->getLedgerTxnRoot()); applicableFrame = txSetFrame->prepareForApply(*app); } + REQUIRE(applicableFrame->checkValid(*app, 0, 0)); GeneralizedTransactionSet newXdr; applicableFrame->toWireTxSetFrame()->toXDR(newXdr); @@ -651,17 +655,42 @@ 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() == 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); } @@ -680,19 +709,42 @@ 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() == 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); } @@ -717,15 +769,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; @@ -815,10 +883,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); @@ -868,6 +935,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, @@ -880,20 +976,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; @@ -901,18 +984,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") @@ -1198,8 +1286,8 @@ TEST_CASE("txset nomination", "[txset]") sorobanTxs.push_back(tx); } } - TxSetPhaseTransactions txPhases = {classicTxs, sorobanTxs}; - TxSetPhaseTransactions invalidTxs; + PerPhaseTransactionList txPhases = {classicTxs, sorobanTxs}; + PerPhaseTransactionList invalidTxs; invalidTxs.resize(txPhases.size()); auto [xdrTxSetFrame, applicableTxSet] = makeTxSetFromTransactions(txPhases, *app, 0, 0, invalidTxs); @@ -1223,7 +1311,7 @@ TEST_CASE("txset nomination", "[txset]") int64_t totalWriteEntries = 0; int64_t totalTxSizeBytes = 0; for (auto const& tx : - applicableTxSet->getTxsForPhase(TxSetPhase::SOROBAN)) + applicableTxSet->getPhase(TxSetPhase::SOROBAN)) { auto const& resources = tx->sorobanResources(); totalInsns += resources.instructions; @@ -1254,13 +1342,31 @@ TEST_CASE("txset nomination", "[txset]") : 100; } - auto const& sorobanComponents = - xdrTxSet.v1TxSet().phases[1].v0Components(); - REQUIRE(sorobanComponents.size() == 1); - int64_t sorobanBaseFee = - sorobanComponents[0].txsMaybeDiscountedFee().baseFee - ? *sorobanComponents[0].txsMaybeDiscountedFee().baseFee - : 100; + int64_t sorobanBaseFee = 100; + if (protocolVersionIsBefore( + protocolVersion, PARALLEL_SOROBAN_PHASE_PROTOCOL_VERSION)) + { + auto const& sorobanComponents = + xdrTxSet.v1TxSet().phases[1].v0Components(); + REQUIRE(sorobanComponents.size() == 1); + REQUIRE(sorobanComponents[0].type() == 0); + if (sorobanComponents[0].txsMaybeDiscountedFee().baseFee) + { + sorobanBaseFee = + *sorobanComponents[0].txsMaybeDiscountedFee().baseFee; + } + } + else + { +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION + auto const& sorobanComponent = + xdrTxSet.v1TxSet().phases[1].parallelTxsComponent(); + if (sorobanComponent.baseFee) + { + sorobanBaseFee = *sorobanComponent.baseFee; + } +#endif + } oss << binToHex(xdrSha256(xdrTxSet)) << "," << applicableTxSet->getTotalFees( diff --git a/src/herder/test/UpgradesTests.cpp b/src/herder/test/UpgradesTests.cpp index c1f86fb7ce..b57b4365b3 100644 --- a/src/herder/test/UpgradesTests.cpp +++ b/src/herder/test/UpgradesTests.cpp @@ -1959,11 +1959,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 @@ -2085,9 +2084,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 diff --git a/src/history/test/HistoryTests.cpp b/src/history/test/HistoryTests.cpp index 03aa1592f8..a4919e4381 100644 --- a/src/history/test/HistoryTests.cpp +++ b/src/history/test/HistoryTests.cpp @@ -1182,9 +1182,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 908548b433..fc84dc54df 100644 --- a/src/history/test/HistoryTestsUtils.cpp +++ b/src/history/test/HistoryTestsUtils.cpp @@ -503,8 +503,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, getApp(), 0, 0).first; diff --git a/src/ledger/LedgerManagerImpl.cpp b/src/ledger/LedgerManagerImpl.cpp index 8e7c5841b2..7b5cac9973 100644 --- a/src/ledger/LedgerManagerImpl.cpp +++ b/src/ledger/LedgerManagerImpl.cpp @@ -905,22 +905,13 @@ 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); + prefetchTxSourceIds(*applicableTxSet); auto const mutableTxResults = - processFeesSeqNums(txs, ltx, *applicableTxSet, ledgerCloseMeta); + processFeesSeqNums(*applicableTxSet, ltx, ledgerCloseMeta); - TransactionResultSet txResultSet; - txResultSet.results.reserve(txs.size()); - // Subtle: after this call, `header` is invalidated, and is not safe to use - applyTransactions(*applicableTxSet, txs, mutableTxResults, ltx, txResultSet, - ledgerCloseMeta); + auto txResultSet = applyTransactions(*applicableTxSet, mutableTxResults, + ltx, ledgerCloseMeta); if (mApp.getConfig().MODE_STORES_HISTORY_MISC) { auto ledgerSeq = ltx.loadHeader().current().ledgerSeq; @@ -1345,13 +1336,12 @@ mergeOpInTx(std::vector const& ops) std::vector LedgerManagerImpl::processFeesSeqNums( - std::vector const& txs, - AbstractLedgerTxn& ltxOuter, ApplicableTxSetFrame const& txSet, + ApplicableTxSetFrame const& txSet, AbstractLedgerTxn& ltxOuter, std::unique_ptr const& ledgerCloseMeta) { ZoneScoped; std::vector txResults; - txResults.reserve(txs.size()); + txResults.reserve(txSet.sizeTxTotal()); CLOG_DEBUG(Ledger, "processing fees and sequence numbers"); int index = 0; try @@ -1361,42 +1351,43 @@ LedgerManagerImpl::processFeesSeqNums( std::map accToMaxSeq; bool mergeSeen = false; - for (auto tx : txs) + for (auto const& phase : txSet.getPhasesInApplyOrder()) { - LedgerTxn ltxTx(ltx); - - txResults.push_back( - 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); + txResults.push_back( + 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); } + ++index; + ltxTx.commit(); } - - LedgerEntryChanges changes = ltxTx.getChanges(); - if (ledgerCloseMeta) - { - ledgerCloseMeta->pushTxProcessingEntry(); - ledgerCloseMeta->setLastTxProcessingFeeProcessingChanges( - changes); - } - ++index; - ltxTx.commit(); } - if (protocolVersionStartsFrom(ltx.loadHeader().current().ledgerVersion, ProtocolVersion::V_19) && mergeSeen) @@ -1439,24 +1430,25 @@ 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().prefetchClassic(keys); } } void -LedgerManagerImpl::prefetchTransactionData( - std::vector const& txs) +LedgerManagerImpl::prefetchTransactionData(ApplicableTxSetFrame const& txSet) { ZoneScoped; if (mApp.getConfig().PREFETCH_BATCH_SIZE > 0) @@ -1464,22 +1456,25 @@ LedgerManagerImpl::prefetchTransactionData( UnorderedSet sorobanKeys; auto lkMeter = make_unique(); UnorderedSet classicKeys; - for (auto const& tx : txs) + for (auto const& phase : txSet.getPhases()) { - if (tx->isSoroban()) + for (auto const& tx : phase) { - if (mApp.getConfig().isUsingBucketListDB()) + if (tx->isSoroban()) { - tx->insertKeysForTxApply(sorobanKeys, lkMeter.get()); + if (mApp.getConfig().isUsingBucketListDB()) + { + tx->insertKeysForTxApply(sorobanKeys, lkMeter.get()); + } + } + else + { + tx->insertKeysForTxApply(classicKeys, nullptr); } - } - else - { - tx->insertKeysForTxApply(classicKeys, nullptr); } } - // Prefetch classic and soroban keys separately for greater visibility - // into the performance of each mode. + // Prefetch classic and soroban keys separately for greater + // visibility into the performance of each mode. if (mApp.getConfig().isUsingBucketListDB()) { if (!sorobanKeys.empty()) @@ -1492,21 +1487,20 @@ LedgerManagerImpl::prefetchTransactionData( } } -void +TransactionResultSet LedgerManagerImpl::applyTransactions( ApplicableTxSetFrame const& txSet, - std::vector const& txs, std::vector const& mutableTxResults, - AbstractLedgerTxn& ltx, TransactionResultSet& txResultSet, + AbstractLedgerTxn& ltx, std::unique_ptr const& ledgerCloseMeta) { ZoneNamedN(txsZone, "applyTransactions", true); - releaseAssert(txs.size() == mutableTxResults.size()); + size_t numTxs = txSet.sizeTxTotal(); + size_t numOps = txSet.sizeOpTotal(); + releaseAssert(numTxs == mutableTxResults.size()); int index = 0; // Record counts - auto numTxs = txs.size(); - auto numOps = txSet.sizeOpTotal(); if (numTxs > 0) { mTransactionCount.Update(static_cast(numTxs)); @@ -1517,78 +1511,86 @@ 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 (size_t i = 0; i < txs.size(); ++i) + size_t resultIndex = 0; + for (auto const& phase : phases) { - ZoneNamedN(txZone, "applyTransaction", true); - auto tx = txs.at(i); - auto mutableTxResult = mutableTxResults.at(i); - - 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.getAppConnector(), ltx, tm, mutableTxResult, subSeed); - tx->processPostApply(mApp.getAppConnector(), ltx, tm, mutableTxResult); - TransactionResultPair results; - results.transactionHash = tx->getContentsHash(); - results.result = mutableTxResult->getResult(); - if (results.result.result.code() == TransactionResultCode::txSUCCESS) + for (auto const& tx : phase) { + ZoneNamedN(txZone, "applyTransaction", true); + auto mutableTxResult = mutableTxResults.at(resultIndex++); + + 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.getAppConnector(), ltx, tm, mutableTxResult, + subSeed); + tx->processPostApply(mApp.getAppConnector(), ltx, tm, + mutableTxResult); + TransactionResultPair results; + results.transactionHash = tx->getContentsHash(); + results.result = mutableTxResult->getResult(); + if (results.result.result.code() == + TransactionResultCode::txSUCCESS) { - ++sorobanTxFailed; + if (tx->isSoroban()) + { + ++sorobanTxSucceeded; + } + ++txSucceeded; + } + else + { + 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); #ifdef BUILD_TESTS - mLastLedgerTxMeta.push_back(tm); + mLastLedgerTxMeta.push_back(tm); #endif - // 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); + } - ++index; + ++index; + } } mTransactionApplySucceeded.inc(txSucceeded); @@ -1596,6 +1598,7 @@ LedgerManagerImpl::applyTransactions( mSorobanTransactionApplySucceeded.inc(sorobanTxSucceeded); mSorobanTransactionApplyFailed.inc(sorobanTxFailed); logTxApplyMetrics(ltx, numTxs, numOps); + return txResultSet; } void @@ -1629,8 +1632,9 @@ LedgerManagerImpl::storeCurrentLedger(LedgerHeader const& header, { bl = mApp.getBucketManager().getBucketList(); } - // Store the current HAS in the database; this is really just to checkpoint - // the bucketlist so we can survive a restart and re-attach to the buckets. + // Store the current HAS in the database; this is really just to + // checkpoint the bucketlist so we can survive a restart and re-attach + // to the buckets. HistoryArchiveState has(header.ledgerSeq, bl, mApp.getConfig().NETWORK_PASSPHRASE); @@ -1716,24 +1720,26 @@ LedgerManagerImpl::ledgerClosed( ledgerSeq, currLedgerVers); // There is a subtle bug in the upgrade path that wasn't noticed until - // protocol 20. For a ledger that upgrades from protocol vN to vN+1, there - // are two different assumptions in different parts of the ledger-close - // path: - // - In closeLedger we mostly treat the ledger as being on vN, eg. during + // protocol 20. For a ledger that upgrades from protocol vN to vN+1, + // there are two different assumptions in different parts of the + // ledger-close path: + // - In closeLedger we mostly treat the ledger as being on vN, eg. + // during // tx apply and LCM construction. - // - In the final stage, when we call ledgerClosed, we pass vN+1 because - // the upgrade completed and modified the ltx header, and we fish the - // protocol out of the ltx header - // Before LedgerCloseMetaV1, this inconsistency was mostly harmless since - // LedgerCloseMeta was not modified after the LTX header was modified. - // However, starting with protocol 20, LedgerCloseMeta is modified after - // updating the ltx header when populating BucketList related meta. This - // means that this function will attempt to call LedgerCloseMetaV1 - // functions, but ledgerCloseMeta is actually a LedgerCloseMetaV0 because it - // was constructed with the previous protocol version prior to the upgrade. - // Due to this, we must check the initial protocol version of ledger instead - // of the ledger version of the current ltx header, which may have been - // modified via an upgrade. + // - In the final stage, when we call ledgerClosed, we pass vN+1 + // because + // the upgrade completed and modified the ltx header, and we fish + // the protocol out of the ltx header + // Before LedgerCloseMetaV1, this inconsistency was mostly harmless + // since LedgerCloseMeta was not modified after the LTX header was + // modified. However, starting with protocol 20, LedgerCloseMeta is + // modified after updating the ltx header when populating BucketList + // related meta. This means that this function will attempt to call + // LedgerCloseMetaV1 functions, but ledgerCloseMeta is actually a + // LedgerCloseMetaV0 because it was constructed with the previous + // protocol version prior to the upgrade. Due to this, we must check the + // initial protocol version of ledger instead of the ledger version of + // the current ltx header, which may have been modified via an upgrade. transferLedgerEntriesToBucketList( ltx, ledgerCloseMeta, ltx.loadHeader().current(), initialLedgerVers); if (ledgerCloseMeta && diff --git a/src/ledger/LedgerManagerImpl.h b/src/ledger/LedgerManagerImpl.h index 1d16f93f4e..82c9f4fc38 100644 --- a/src/ledger/LedgerManagerImpl.h +++ b/src/ledger/LedgerManagerImpl.h @@ -75,15 +75,13 @@ class LedgerManagerImpl : public LedgerManager std::unique_ptr mNextMetaToEmit; std::vector processFeesSeqNums( - std::vector const& txs, - AbstractLedgerTxn& ltxOuter, ApplicableTxSetFrame const& txSet, + ApplicableTxSetFrame const& txSet, AbstractLedgerTxn& ltxOuter, std::unique_ptr const& ledgerCloseMeta); - void applyTransactions( + TransactionResultSet applyTransactions( ApplicableTxSetFrame const& txSet, - std::vector const& txs, std::vector const& mutableTxResults, - AbstractLedgerTxn& ltx, TransactionResultSet& txResultSet, + AbstractLedgerTxn& ltx, std::unique_ptr const& ledgerCloseMeta); // initialLedgerVers must be the ledger version at the start of the ledger. @@ -96,9 +94,8 @@ class LedgerManagerImpl : public LedgerManager void storeCurrentLedger(LedgerHeader const& header, bool storeHeader, bool appendToCheckpoint); - 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/main.cpp b/src/main/main.cpp index edcd3ffb58..af576043b0 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -350,7 +350,7 @@ main(int argc, char* const* argv) checkStellarCoreMajorVersionProtocolIdentity(); rust_bridge::check_sensible_soroban_config_for_protocol( Config::CURRENT_LEDGER_PROTOCOL_VERSION); - checkXDRFileIdentity(); + // checkXDRFileIdentity(); int res = handleCommandLine(argc, argv); return res; diff --git a/src/test/TxTests.cpp b/src/test/TxTests.cpp index b9a69a8f1b..c68c1ceead 100644 --- a/src/test/TxTests.cpp +++ b/src/test/TxTests.cpp @@ -540,15 +540,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-23-soroban.json b/src/testdata/ledger-close-meta-v1-protocol-23-soroban.json index c3893b7f4d..c7161b7325 100644 --- a/src/testdata/ledger-close-meta-v1-protocol-23-soroban.json +++ b/src/testdata/ledger-close-meta-v1-protocol-23-soroban.json @@ -6,19 +6,19 @@ "v": 0 }, "ledgerHeader": { - "hash": "6ee5024b08095c9d7e2df55efca112c670a09c433bb21614c360d6be556ae57f", + "hash": "718624950177b1d95d106286dfdda902bb499c5a7f9fe73585e180f7ce06c987", "header": { "ledgerVersion": 23, - "previousLedgerHash": "40667fafcadeb2372487f935ce89bfc018f9e60e68bbe01e6de6ad4e607c9645", + "previousLedgerHash": "da2a06517b3b14827922364191f720fa0606a9dce82fe92e7c26d3527f495dc4", "scpValue": { - "txSetHash": "22533247312c90534ebcd9691c93d9e61dc443dd15c522cc913d1d939af90fd1", + "txSetHash": "a5e8b778a6f8308b2e945d50276740e493291884f3fdeb36896376edc67a92d8", "closeTime": 1451692800, "upgrades": [], "ext": { "v": "STELLAR_VALUE_SIGNED", "lcValueSignature": { "nodeID": "GDDOUW25MRFLNXQMN3OODP6JQEXSGLMHAFZV4XPQ2D3GA4QFIDMEJG2O", - "signature": "c9437f7d1bb05bd94f8fa3fc5b1d1d7f6cd59a36bbc26b315fda47b87d62fc37464053aac9ed547dd834c3b4f1707bdfe000c932ae151118e7a1fc1dfe839b0e" + "signature": "cc7adeabf3e56f59e727e2d67fab6d21ff26e9b512b5693e625c97c97f06b121509458c35bc62dd2108a86085960bf05f7f85859dda0ab595567f54095cf6105" } } }, @@ -49,20 +49,19 @@ "txSet": { "v": 1, "v1TxSet": { - "previousLedgerHash": "40667fafcadeb2372487f935ce89bfc018f9e60e68bbe01e6de6ad4e607c9645", + "previousLedgerHash": "da2a06517b3b14827922364191f720fa0606a9dce82fe92e7c26d3527f495dc4", "phases": [ { "v": 0, "v0Components": [] }, { - "v": 0, - "v0Components": [ - { - "type": "TXSET_COMP_TXS_MAYBE_DISCOUNTED_FEE", - "txsMaybeDiscountedFee": { - "baseFee": 100, - "txs": [ + "v": 1, + "parallelTxsComponent": { + "baseFee": 100, + "executionStages": [ + [ + [ { "type": "ENVELOPE_TYPE_TX", "v1": { @@ -493,9 +492,9 @@ } } ] - } - } - ] + ] + ] + } } ] } diff --git a/src/testdata/ledger-close-meta-v1-protocol-23.json b/src/testdata/ledger-close-meta-v1-protocol-23.json index 8ff9a283c1..8646d0ccd7 100644 --- a/src/testdata/ledger-close-meta-v1-protocol-23.json +++ b/src/testdata/ledger-close-meta-v1-protocol-23.json @@ -6,23 +6,23 @@ "v": 0 }, "ledgerHeader": { - "hash": "955521810ce73e205a011f6f7313ff47da2d9ff3779ade702ded8654d6382211", + "hash": "988651312049d3037f749f432a9f4ebb43bc356ea686d1061126d7bc92f1d994", "header": { "ledgerVersion": 23, - "previousLedgerHash": "7a215a87c49cfb4d83abd92241b2059cb2270f2e7d93bd672fa28f700ef7dc20", + "previousLedgerHash": "74997b55636fb03041092253f75e29990adf7492e2dfb6cd8c0ec1cbcb21dd77", "scpValue": { - "txSetHash": "6319480f16c93c4a9b31b0b4dc3646275418630d507a6ec6a2a0b2527a0c88ba", + "txSetHash": "afa22d0c1cf51d4120652b91b9bec4915a8d9b0fe18db44644e673fef42c8f49", "closeTime": 0, "upgrades": [], "ext": { "v": "STELLAR_VALUE_SIGNED", "lcValueSignature": { "nodeID": "GDDOUW25MRFLNXQMN3OODP6JQEXSGLMHAFZV4XPQ2D3GA4QFIDMEJG2O", - "signature": "0372a515f08fb718defc47f5bfb1236dc4cbff9bccdab5514ef71b3bbbe51c49c1bf47abfc166c5782bd42ea20b01dc2614fde91b8fb887b5d4fc43e1dd8c408" + "signature": "80f459e71a026258532a836fcb62a32eabc22aae53a2cf8c7b6ba017614371a0511fb5b76581c31f1b2deee2f741dce46e9f251ddfcc0cfde0524aaabada3c0f" } } }, - "txSetResultHash": "249b974bacf8b5c4a8f0b5598194c1b9eca64af0b5c1506daa871c1533b6baac", + "txSetResultHash": "f66233c106977a4cc148e019411ff6ddfaf76c337d004ed9a304a70407b161d0", "bucketListHash": "486d15e891ed49429510506d016999b3a8b74349c3104000bc0a102e7d34ed81", "ledgerSeq": 7, "totalCoins": 1000000000000000000, @@ -49,7 +49,7 @@ "txSet": { "v": 1, "v1TxSet": { - "previousLedgerHash": "7a215a87c49cfb4d83abd92241b2059cb2270f2e7d93bd672fa28f700ef7dc20", + "previousLedgerHash": "74997b55636fb03041092253f75e29990adf7492e2dfb6cd8c0ec1cbcb21dd77", "phases": [ { "v": 0, @@ -176,8 +176,11 @@ ] }, { - "v": 0, - "v0Components": [] + "v": 1, + "parallelTxsComponent": { + "baseFee": null, + "executionStages": [] + } } ] } @@ -185,22 +188,43 @@ "txProcessing": [ { "result": { - "transactionHash": "0db2322d85e9d8ea2421559922bb6107429650ebdad304c907480853d465c10d", + "transactionHash": "324d0628e2a215d367f181f0e3aacbaa26fa638e676e73fb9ad26a360314a7b7", "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": "b28c171f9658320b5ce8d50e4e1a36b74afbb2a92eec7df92a8981067131b025", + "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 @@ -211,13 +235,13 @@ { "type": "LEDGER_ENTRY_STATE", "state": { - "lastModifiedLedgerSeq": 5, + "lastModifiedLedgerSeq": 4, "data": { "type": "ACCOUNT", "account": { - "accountID": "GC4EFXBN6BEENDAX7PBW5PGIIIVH3INMD3OEPQASXOLGOHVVP7ZEMG7X", - "balance": 999999998999989700, - "seqNum": 3, + "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", + "balance": 400000000, + "seqNum": 17179869184, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -225,31 +249,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": 5, - "seqTime": 0 - } - } - } - } - } + "v": 0 } } }, @@ -265,9 +265,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GC4EFXBN6BEENDAX7PBW5PGIIIVH3INMD3OEPQASXOLGOHVVP7ZEMG7X", - "balance": 999999998999989600, - "seqNum": 3, + "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", + "balance": 399999700, + "seqNum": 17179869184, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -275,31 +275,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": 5, - "seqTime": 0 - } - } - } - } - } + "v": 0 } } }, @@ -323,9 +299,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GC4EFXBN6BEENDAX7PBW5PGIIIVH3INMD3OEPQASXOLGOHVVP7ZEMG7X", - "balance": 999999998999989600, - "seqNum": 3, + "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", + "balance": 399999700, + "seqNum": 17179869184, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -333,31 +309,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": 5, - "seqTime": 0 - } - } - } - } - } + "v": 0 } } }, @@ -373,9 +325,61 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GC4EFXBN6BEENDAX7PBW5PGIIIVH3INMD3OEPQASXOLGOHVVP7ZEMG7X", - "balance": 999999998999989600, - "seqNum": 4, + "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", + "balance": 399999700, + "seqNum": 17179869184, + "numSubEntries": 0, + "inflationDest": null, + "flags": 0, + "homeDomain": "", + "thresholds": "01000000", + "signers": [], + "ext": { + "v": 0 + } + } + }, + "ext": { + "v": 0 + } + } + }, + { + "type": "LEDGER_ENTRY_STATE", + "state": { + "lastModifiedLedgerSeq": 5, + "data": { + "type": "ACCOUNT", + "account": { + "accountID": "GCGE27HU2VYQANKL2VZWLCAOJYMEFST5DXPBWQ7BRRPOHUPK626DNG4Q", + "balance": 200010000, + "seqNum": 21474836480, + "numSubEntries": 0, + "inflationDest": null, + "flags": 0, + "homeDomain": "", + "thresholds": "01000000", + "signers": [], + "ext": { + "v": 0 + } + } + }, + "ext": { + "v": 0 + } + } + }, + { + "type": "LEDGER_ENTRY_UPDATED", + "updated": { + "lastModifiedLedgerSeq": 7, + "data": { + "type": "ACCOUNT", + "account": { + "accountID": "GCGE27HU2VYQANKL2VZWLCAOJYMEFST5DXPBWQ7BRRPOHUPK626DNG4Q", + "balance": 200010000, + "seqNum": 21474836481, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -425,43 +429,18 @@ "state": { "lastModifiedLedgerSeq": 6, "data": { - "type": "ACCOUNT", - "account": { + "type": "TRUSTLINE", + "trustLine": { "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", - "balance": 399999900, - "seqNum": 12884901889, - "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": 6, - "seqTime": 0 - } - } - } - } - } + "v": 0 } } }, @@ -475,43 +454,18 @@ "updated": { "lastModifiedLedgerSeq": 7, "data": { - "type": "ACCOUNT", - "account": { + "type": "TRUSTLINE", + "trustLine": { "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", - "balance": 400000900, - "seqNum": 12884901889, - "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": 6, - "seqTime": 0 - } - } - } - } - } + "v": 0 } } }, @@ -519,49 +473,28 @@ "v": 0 } } - }, + } + ] + }, + { + "changes": [ { "type": "LEDGER_ENTRY_STATE", "state": { "lastModifiedLedgerSeq": 7, "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": 7, - "seqTime": 0 - } - } - } - } - } + "v": 0 } } }, @@ -575,43 +508,18 @@ "updated": { "lastModifiedLedgerSeq": 7, "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": 7, - "seqTime": 0 - } - } - } - } - } + "v": 0 } } }, @@ -630,43 +538,22 @@ }, { "result": { - "transactionHash": "324d0628e2a215d367f181f0e3aacbaa26fa638e676e73fb9ad26a360314a7b7", + "transactionHash": "0db2322d85e9d8ea2421559922bb6107429650ebdad304c907480853d465c10d", "result": { - "feeCharged": 300, + "feeCharged": 100, "result": { - "code": "txFEE_BUMP_INNER_SUCCESS", - "innerResultPair": { - "transactionHash": "b28c171f9658320b5ce8d50e4e1a36b74afbb2a92eec7df92a8981067131b025", - "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 @@ -677,13 +564,13 @@ { "type": "LEDGER_ENTRY_STATE", "state": { - "lastModifiedLedgerSeq": 4, + "lastModifiedLedgerSeq": 5, "data": { "type": "ACCOUNT", "account": { - "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", - "balance": 400000000, - "seqNum": 17179869184, + "accountID": "GC4EFXBN6BEENDAX7PBW5PGIIIVH3INMD3OEPQASXOLGOHVVP7ZEMG7X", + "balance": 999999998999989700, + "seqNum": 3, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -691,7 +578,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": 5, + "seqTime": 0 + } + } + } + } + } } } }, @@ -707,9 +618,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", - "balance": 399999700, - "seqNum": 17179869184, + "accountID": "GC4EFXBN6BEENDAX7PBW5PGIIIVH3INMD3OEPQASXOLGOHVVP7ZEMG7X", + "balance": 999999998999989600, + "seqNum": 3, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -717,85 +628,57 @@ "thresholds": "01000000", "signers": [], "ext": { - "v": 0 - } - } - }, - "ext": { - "v": 0 - } - } - } - ], - "txApplyProcessing": { - "v": 3, - "v3": { - "ext": { - "v": 0 - }, - "txChangesBefore": [ - { - "type": "LEDGER_ENTRY_STATE", - "state": { - "lastModifiedLedgerSeq": 7, - "data": { - "type": "ACCOUNT", - "account": { - "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", - "balance": 399999700, - "seqNum": 17179869184, - "numSubEntries": 0, - "inflationDest": null, - "flags": 0, - "homeDomain": "", - "thresholds": "01000000", - "signers": [], - "ext": { - "v": 0 - } - } - }, - "ext": { - "v": 0 - } - } - }, - { - "type": "LEDGER_ENTRY_UPDATED", - "updated": { - "lastModifiedLedgerSeq": 7, - "data": { - "type": "ACCOUNT", - "account": { - "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", - "balance": 399999700, - "seqNum": 17179869184, - "numSubEntries": 0, - "inflationDest": null, - "flags": 0, - "homeDomain": "", - "thresholds": "01000000", - "signers": [], + "v": 1, + "v1": { + "liabilities": { + "buying": 0, + "selling": 0 + }, "ext": { - "v": 0 + "v": 2, + "v2": { + "numSponsored": 0, + "numSponsoring": 0, + "signerSponsoringIDs": [], + "ext": { + "v": 3, + "v3": { + "ext": { + "v": 0 + }, + "seqLedger": 5, + "seqTime": 0 + } + } + } } } - }, - "ext": { - "v": 0 } } }, + "ext": { + "v": 0 + } + } + } + ], + "txApplyProcessing": { + "v": 3, + "v3": { + "ext": { + "v": 0 + }, + "txChangesBefore": [ { "type": "LEDGER_ENTRY_STATE", "state": { - "lastModifiedLedgerSeq": 5, + "lastModifiedLedgerSeq": 7, "data": { "type": "ACCOUNT", "account": { - "accountID": "GCGE27HU2VYQANKL2VZWLCAOJYMEFST5DXPBWQ7BRRPOHUPK626DNG4Q", - "balance": 200010000, - "seqNum": 21474836480, + "accountID": "GC4EFXBN6BEENDAX7PBW5PGIIIVH3INMD3OEPQASXOLGOHVVP7ZEMG7X", + "balance": 999999998999989600, + "seqNum": 3, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -803,7 +686,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": 5, + "seqTime": 0 + } + } + } + } + } } } }, @@ -819,9 +726,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GCGE27HU2VYQANKL2VZWLCAOJYMEFST5DXPBWQ7BRRPOHUPK626DNG4Q", - "balance": 200010000, - "seqNum": 21474836481, + "accountID": "GC4EFXBN6BEENDAX7PBW5PGIIIVH3INMD3OEPQASXOLGOHVVP7ZEMG7X", + "balance": 999999998999989600, + "seqNum": 4, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -871,18 +778,43 @@ "state": { "lastModifiedLedgerSeq": 6, "data": { - "type": "TRUSTLINE", - "trustLine": { + "type": "ACCOUNT", + "account": { "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", - "asset": { - "assetCode": "CUR1", - "issuer": "GCGE27HU2VYQANKL2VZWLCAOJYMEFST5DXPBWQ7BRRPOHUPK626DNG4Q" - }, - "balance": 0, - "limit": 100, - "flags": 1, + "balance": 399999900, + "seqNum": 12884901889, + "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": 6, + "seqTime": 0 + } + } + } + } + } } } }, @@ -896,18 +828,43 @@ "updated": { "lastModifiedLedgerSeq": 7, "data": { - "type": "TRUSTLINE", - "trustLine": { + "type": "ACCOUNT", + "account": { "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", - "asset": { - "assetCode": "CUR1", - "issuer": "GCGE27HU2VYQANKL2VZWLCAOJYMEFST5DXPBWQ7BRRPOHUPK626DNG4Q" - }, - "balance": 50, - "limit": 100, - "flags": 1, + "balance": 400000900, + "seqNum": 12884901889, + "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": 6, + "seqTime": 0 + } + } + } + } + } } } }, @@ -915,28 +872,49 @@ "v": 0 } } - } - ] - }, - { - "changes": [ + }, { "type": "LEDGER_ENTRY_STATE", "state": { "lastModifiedLedgerSeq": 7, "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": 7, + "seqTime": 0 + } + } + } + } + } } } }, @@ -950,18 +928,43 @@ "updated": { "lastModifiedLedgerSeq": 7, "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": 7, + "seqTime": 0 + } + } + } + } + } } } }, diff --git a/src/testdata/txset/v_next.csv b/src/testdata/txset/v_next.csv index 4c6c7ee712..4a15dd6405 100644 --- a/src/testdata/txset/v_next.csv +++ b/src/testdata/txset/v_next.csv @@ -1,50 +1,50 @@ xdr_hash,total_fees,total_inclusion_fees,classic_ops,classic_non_dex_txs,classic_non_dex_txs_base_fee,classic_dex_txs,classic_dex_txs_base_fee,soroban_ops,soroban_base_fee,insns,read_bytes,write_bytes,read_entries,write_entries,tx_size_bytes -8451d4c57d8034f00cd4cc964a0da6338d8e48937fd751cf68ace28fa43305bb,700048846,118323,116,5,408,0,0,11,138,119175300,75731,70977,182,115,502588 -0de25a61bf760287481064a109b32f277c3ed2c962ccc35b69613e2648d8888a,900043318,80160,299,87,100,35,206,12,279,636495051,122125,55684,373,135,646972 -f61ddbb450cf3c6f34bc44b1696b10b58aeffc401f46c50439ecaee0abaa22b9,500035835,182672,294,29,100,0,0,9,715,463534214,27084,229046,130,15,160552 -cfc80d8f9c4b081fe464d0fef60071b099bc8dad9edcf45bcd0850c0783c35bb,500341107,569773,356,7,505,5,1412,9,793,160617852,163613,235874,339,79,295604 -b5a86fe58d55b6f9eb06487cca2b03c34f7da724480055d7ddc74115bc764c8d,800019739,105077,138,5,100,1,709,8,514,641289063,89122,38111,382,193,488364 -e06404a5116b04e8b86a0993afd3bf4950373aa7d007df22208ba4c3d02c8dc0,1100121517,200231,155,1,100,5,757,15,673,665751421,113149,58880,404,240,598116 -ee40e2d5844007a61f2ba5f546e32cd21fd28e6e047d922d6491b89374e65030,1300031368,157256,214,22,100,0,0,16,623,789821305,205586,56101,578,204,661172 -62d8b2dfa50afab4ef6f97f442d9fe28e90da0345c7fe711b988e1d9ffb4bee2,1200104392,140248,110,8,920,0,0,24,133,378515857,82072,89881,440,180,598096 -77ff79874f556f9ec525e68e685c9af56ea529e2e06dad5194f8a851bee6cc33,1300214462,255638,109,5,1800,0,0,23,794,194185314,174015,158273,568,206,265588 -e637d7a0713e30b0c9546422ad1dff3e681f4ae6cf9651bbc43b129e219945ba,500033460,73962,315,31,100,0,0,7,280,209658720,68388,38848,177,75,840260 -8cd6d0ba42f02b2e50c5f3bd72487b8d4a00d0a7ac6d2a0f5cf0f3921d4e7596,700271194,443510,336,35,781,0,0,14,627,504544894,190880,233282,693,114,513712 -156681a6b9c2aeb20ec2d60653abbecc67940aa073f077ecd237b5e83a70d458,600015836,40851,48,2,100,5,424,8,124,442409709,244012,64005,268,159,357956 -b72f0b5b5aa4e3fa86f11344a717226322d1ca600fbc0bffe6cf1ea032f45c1b,1000168452,319086,318,20,494,0,0,20,568,104562952,81963,127733,632,125,658140 -fe3141a5f7a98756f4800267b195b544f9afa6a5377d9958caf1d90fd28c1a73,1200092120,217867,372,26,187,8,383,12,622,110659936,244854,188961,589,175,713360 -2bcb78485c5e4a30f26d8992d2661f4b94c81f1440fe66d0dec5721205f0d339,900045282,61188,127,15,327,0,0,9,417,308841465,30128,149503,391,250,629340 -4ec62c3c0d3cb13a5a0f04d8d180e49af3cd6907240f31b2833ccf4a0ddb4be5,600014500,14582,139,7,100,0,0,6,100,100591244,55800,59980,619,83,846636 -c888cb239d733c0d1e02a88baea4f67ec6d9fb007a0d1123c869d6512670858d,700187355,450169,399,9,371,3,719,11,1234,157757140,127459,79098,239,113,309360 -d002b66356930313fce4d6e8ac737c6bf06637721acca8642c487a047668608e,700023369,215706,160,4,100,1,945,11,209,564818545,250754,32463,619,84,305808 -9e06fd5731230f5aee3a0a304fbb7169a34d1efddfb98c1be84e1ede3de5ca10,700291384,441673,379,148,569,111,979,7,1389,88871160,82276,294087,656,78,505500 -8e549a10c44abca850ab16bdebaa87c50a8ec4e09a7101f69040fd62aab32e7c,900299003,560815,295,4,744,8,1076,13,1035,993078879,136375,92526,386,85,449988 -8845bc39fe48222a649325af5b13c2cbf5949aa985e441a03cbca496c173652c,800091636,516376,293,27,100,3,2182,16,773,462811324,151693,202525,312,128,407332 -3a8eee200a0248b0aa761bcdc29f5ccbfd586e192a21ae411373faf867f607ef,1200280035,416458,151,3,1829,0,0,16,241,384422657,132275,56129,544,266,646480 -a33dd84aa29478644cb624476216ccdee7dac89874537ab0fed1d222cc246aa3,900132400,704422,430,26,271,0,0,15,1058,268867531,116024,221104,875,275,319680 -f3a942d5cd6ba0fb26f8c0bb40d534d4497973409f3e23a8b1d37ff46c65e729,600051203,406706,235,23,148,0,0,11,1493,304485693,63114,93754,348,75,343320 -54d85544598dc4d48a8e4344732f2b9e5927e619bee61b61df253b3c2b272fa9,1200443108,629908,317,5,1079,1,2452,24,1179,464391790,119101,46503,334,129,899924 -d272d7c46e778201c1943b90bfaf600bebe1ce438b0ce99d5aceefcdbe53cdac,600318670,561118,357,52,860,0,0,10,1165,732259358,25326,49951,838,89,236416 -050df102e5ff13501e9c1b3542f6b6d72f8cc598aeb9ce164378bc865ad30f25,1300028985,83694,117,10,185,0,0,20,367,404659635,57634,263263,463,214,774396 -e7c6571c709865a10002af930aeedf377772a50cfb734bc0935f1173b7b692dc,700055728,62602,106,13,491,0,0,7,526,468302474,154925,171334,440,139,912344 -1f5299eee1bfb1e538ce0453c087358c4878cdf1c508a1e6553f0bb88c750641,800068713,125615,411,28,163,0,0,10,172,163932473,206816,31718,358,59,894396 -82fbc7404a323924b6f468a66c8e8861326b1d01f67699b2372e51094885ec11,600204581,316548,359,12,547,0,0,12,684,490132607,70328,69130,436,116,218164 -de52505bb8dd5e2a8a09b14c7fc75601eb2a70c9fb39e1d88e29572dbf205cc1,1000098363,126826,281,5,328,0,0,15,413,421615776,254543,272661,618,252,500612 -1ece8724cf04ea9b3206f58478d549b1e0df2bd604eb0488fcb92425bad44cfd,1000067924,153638,336,8,100,9,272,20,323,624788777,70147,74282,572,249,395184 -a34310a2f2d299b2a4fb1b1d7fe9cfa8daf96a7289e92b9a706a3bd9b5cf840b,600172984,537286,184,7,898,0,0,12,646,405013352,111002,54124,543,34,200720 -243b8882bfe0131e86fec4d3c49de55d54b21aa69b7496b8239448455851ece8,400097143,127813,242,4,262,7,599,4,768,183278829,106869,278025,487,101,178176 -b3d53624b759351849893d36c97ff9ebdc83adfe8445b55d64fc21fbd209af2a,700051284,88386,407,20,122,0,0,10,163,295644767,85470,83932,771,408,586388 -37cc9fee0928cb6e522582c2b1c3129767a7fabcca71fcbeb40e1f13e968777a,800092808,101254,105,2,828,0,0,12,489,217913330,251766,143144,457,76,302120 -e0851847fa2ebfe9984df2c5d1140086b9643bcce338ea88b00e078f417fff8b,1000160005,418909,306,7,387,6,590,13,1075,175647333,149493,49817,318,137,565348 -f6e40d9a1e385c8389426ce96e360b69ae29f9af80a365aee77e9c17e833d384,700042537,81421,76,2,100,3,1113,12,801,307849140,124071,227806,609,120,780000 -f4fa159b22b7aef291ce62dc87df85dca639f8fa0fec447c76b45ead153dcf6b,800157522,547616,288,39,475,0,0,13,1594,820556518,203342,157838,445,206,451628 -3fd7680a868d4d25d616c5b30070278384dfcb073a1df10dd5e35574aca0448d,600041892,201992,336,5,100,0,0,12,691,396838591,186399,85280,696,118,166804 -52f255faafa088557fd705130bfffe35cb821c087724d2517244efc31ddef01a,400037024,107725,313,66,100,25,141,6,380,227273319,284981,28225,729,57,522524 -8cff8121da75a177ceae2117a8438fd4b42ed600ebec66e2af8cef69b076e1b4,1000023845,80656,178,5,115,0,0,15,225,476000681,45473,100486,500,135,908712 -4538259bbab59227fecb16f351b42e41caea2651e9905e219b5a8dbae6b0a973,600042027,55248,132,4,200,3,371,12,262,182552167,110506,198641,436,162,252596 -8a8e9567b695f9f74ff04454f655f7c5693fdd369e9a8502cf27ccb00de65e9e,800046580,101260,153,8,100,7,524,8,730,687453132,87196,141959,448,164,99736 -0794a9cb967ddc4a4443561ab1947c75afdcd2e397b55ed856004f6c9a3b1f5e,900390608,542920,208,16,1799,0,0,16,1026,477044171,40253,87794,558,135,141760 -4846535330fafb4959b1c1516c93c1bdbdf3f969f028bfe6a40740410808f720,800024373,62070,69,4,100,3,585,13,486,348084832,88527,73521,752,196,736592 -6849318dc6959ee09ebc95b608747e572a2dab2ecf2f49b3fd580439fa84f6f6,700045717,153924,259,6,154,0,0,7,833,682284676,219069,160295,311,70,794936 -f80d16af06997717529e9d9ffb606b0c14ba69b3a9ac60c1a6b59c6bf1d5dc22,1200277212,365048,178,4,1236,2,1872,24,449,339408661,106862,105073,318,77,804908 -a70064d8a54866061975623f3cdef17903db4f8d5012ad01b06c32c2f2830aae,800024825,48161,227,17,105,0,0,9,110,325664583,165718,107435,301,144,327632 +d1b5ecefdc2ff0edb4a7ddbda8775a4d37d0f21147c87cbad07723e174316c29,700048846,118323,116,5,408,0,0,11,138,119175300,75731,70977,182,115,502588 +cdb3a654b7d7cc67a709a2ba1fbcb87188e4ca77aafdf42dc94fd451a74950cd,900043318,80160,299,87,100,35,206,12,279,636495051,122125,55684,373,135,646972 +2de0028bba89ee28f432ed5c881835230963472fd01d4807676df7fabb85d423,500035835,182672,294,29,100,0,0,9,715,463534214,27084,229046,130,15,160552 +9d7b9ebc43779b528aea7043cae33229a21733c70d42c3984853bdecf1ee0c74,500341107,569773,356,7,505,5,1412,9,793,160617852,163613,235874,339,79,295604 +8334619287ceff77620e959ab8e8f6af343d99b8a8e4afd9f3ed528276222e1b,800019739,105077,138,5,100,1,709,8,514,641289063,89122,38111,382,193,488364 +c875b06c3c1b01ddc8142950cced395c49315a1859851d68fb89a126f09ec6ae,1100121517,200231,155,1,100,5,757,15,673,665751421,113149,58880,404,240,598116 +ec70b12295a9b95356d403635621aea72361d5a6f1cf79efa4f2c9c3e48097f4,1300031368,157256,214,22,100,0,0,16,623,789821305,205586,56101,578,204,661172 +914fa951c4959e69fe9a232a18d84c1db354400b64f87e877e7de172f82bb0ff,1200104392,140248,110,8,920,0,0,24,133,378515857,82072,89881,440,180,598096 +35dd7f039f6c24304913cc7fde1edc8ea26d98f28e8dddea9a80c4a4384a8dc2,1300214462,255638,109,5,1800,0,0,23,794,194185314,174015,158273,568,206,265588 +284bb1ec3d1454da9dbf6acc0bdc1ccc699f1b8920fb510cd307159891f6a20b,500033460,73962,315,31,100,0,0,7,280,209658720,68388,38848,177,75,840260 +f13c341198a1d2da4e57ea93ec18d0942eaec8da297e45bf42b6fc46f969c51e,700271194,443510,336,35,781,0,0,14,627,504544894,190880,233282,693,114,513712 +1f4f97738c01d2208f3ac2971a73002158a2da9680237bc248a17b07f7f8b8ba,600015836,40851,48,2,100,5,424,8,124,442409709,244012,64005,268,159,357956 +1008eed57a9118e23c916358c43765e3f1c5d07762adf84b4fd3a2309b5d3171,1000168452,319086,318,20,494,0,0,20,568,104562952,81963,127733,632,125,658140 +70a88fea69dc2346532242a2c024670abded4ae68543f860e999b2268f2287a0,1200092120,217867,372,26,187,8,383,12,622,110659936,244854,188961,589,175,713360 +ca1600c5bbbd2d54fe6b066305b2f8547db44cbd698997a46ea59b7edd79e4ed,900045282,61188,127,15,327,0,0,9,417,308841465,30128,149503,391,250,629340 +803bca56b8b72533d1c47640104e915b49ef551d570c3736d8057f4e17756e2a,600014500,14582,139,7,100,0,0,6,100,100591244,55800,59980,619,83,846636 +df80c063efd609f9fd2d710faf0afff068436ec8fca22eb027f862579df638ea,700187355,450169,399,9,371,3,719,11,1234,157757140,127459,79098,239,113,309360 +35c1d7b05a6c5cfa8f28e6825ef71a1eb3f86e0184bbdb08514d3fc74da34aa6,700023369,215706,160,4,100,1,945,11,209,564818545,250754,32463,619,84,305808 +f9209c344bdab81e934a4dab4890aff088989d3dc0cac0426e77a127df9c79d4,700291384,441673,379,148,569,111,979,7,1389,88871160,82276,294087,656,78,505500 +f56ce61148173312fc58e744b5ce06e75305b97fe8103b6c5dfec9d84cd78424,900299003,560815,295,4,744,8,1076,13,1035,993078879,136375,92526,386,85,449988 +50e51d35283913c89552fe93cca4c7501b5df856669db75e9e6d64cf8905a950,800091636,516376,293,27,100,3,2182,16,773,462811324,151693,202525,312,128,407332 +09ac14de299a9110127cf60722ca9ff74187d30a2011d5cdda29f9d9c79f4dfe,1200280035,416458,151,3,1829,0,0,16,241,384422657,132275,56129,544,266,646480 +d8ba6a94e41398b635a7a2d4119a505c7562d0eef1dfeaf856ed1fb8e14e8426,900132400,704422,430,26,271,0,0,15,1058,268867531,116024,221104,875,275,319680 +3659b367913a3df7ca1330968d09ad56a9e095d0d4d3a4fc7c267eb1aa35c798,600051203,406706,235,23,148,0,0,11,1493,304485693,63114,93754,348,75,343320 +b9dcd22d9af3b86db0ed8a0f83d36fe501ca2e88118b7f0e0d4a28687229e352,1200443108,629908,317,5,1079,1,2452,24,1179,464391790,119101,46503,334,129,899924 +762e06129a7105a0f60a5f2924b137cd987f69747e20698dbdf152ef8ae13d4f,600318670,561118,357,52,860,0,0,10,1165,732259358,25326,49951,838,89,236416 +68c54f55b6c97d614e57c5a02391fcc3837d959675035e36a74448801c73d26c,1300028985,83694,117,10,185,0,0,20,367,404659635,57634,263263,463,214,774396 +f45e1583b035cd38f12b18b0db99fd3ac6c84475a27b364fa5934e3f8977f71e,700055728,62602,106,13,491,0,0,7,526,468302474,154925,171334,440,139,912344 +c668ce128152ac998e0c32de116159389697cda8c8951764e9bae1b99c3cbada,800068713,125615,411,28,163,0,0,10,172,163932473,206816,31718,358,59,894396 +ec11e4ac49ab0a0720f009fd6c65c5602a2e5e5be7a63cb02b1709d0acb91267,600204581,316548,359,12,547,0,0,12,684,490132607,70328,69130,436,116,218164 +fca1d808926a1bff963a4c3f1af2d96701d6ac2ca6cd86734136dd2722b1226e,1000098363,126826,281,5,328,0,0,15,413,421615776,254543,272661,618,252,500612 +d91105aabf45468ac78eb7680a97efbc5f32f94a1e7bc8dd7bdd86cbe23357d6,1000067924,153638,336,8,100,9,272,20,323,624788777,70147,74282,572,249,395184 +b0dba2af4cdf8b96bcf2fcc81a44b063974da1a7b0e96e634799345bdae64549,600172984,537286,184,7,898,0,0,12,646,405013352,111002,54124,543,34,200720 +e73b19d510c027e455ecfc5262bac38e6dd05d55b5712964c97839cde6e778d3,400097143,127813,242,4,262,7,599,4,768,183278829,106869,278025,487,101,178176 +e3577df781e3c043827b014b360c4e66a98af55627ca0b73e1a86d3ebdd7d100,700051284,88386,407,20,122,0,0,10,163,295644767,85470,83932,771,408,586388 +0ade0026b8955c7f82c0dc3433af946a9fb7d4d13964b76fcf7acf0dce6c843d,800092808,101254,105,2,828,0,0,12,489,217913330,251766,143144,457,76,302120 +6ed6e4493703fdef5cc9a18ff452e32131090e119f412fd5de05dcb86add718c,1000160005,418909,306,7,387,6,590,13,1075,175647333,149493,49817,318,137,565348 +8e928976ada9a2b9b7ca61c4e344b55d94a2af344e726fc171f0ea137783ea74,700042537,81421,76,2,100,3,1113,12,801,307849140,124071,227806,609,120,780000 +ef93ea12f97842b35b8f073ff0da735bbb819789b6d15b7c9f09bce3c03f8bc2,800157522,547616,288,39,475,0,0,13,1594,820556518,203342,157838,445,206,451628 +5022016d144970dd8f9dea6ffffff71c05fcf023c18dd679fb29a3956df86576,600041892,201992,336,5,100,0,0,12,691,396838591,186399,85280,696,118,166804 +934ba66b09c49450a92657c8945e3bc37db7c8aca92be2453851fa2f006224d8,400037024,107725,313,66,100,25,141,6,380,227273319,284981,28225,729,57,522524 +8815afbe292ddcf6fdcadf316ffbfa1dbf79f83f5d08def45844f50511d4f027,1000023845,80656,178,5,115,0,0,15,225,476000681,45473,100486,500,135,908712 +e1cc9f5ac430109da26edde0e5c0291bf3c7c128f1ca22219f2ef87094dba01b,600042027,55248,132,4,200,3,371,12,262,182552167,110506,198641,436,162,252596 +cff8cca97b83252ada6f55c306b0251fd33e9291299d0ef1eb8267c02a497147,800046580,101260,153,8,100,7,524,8,730,687453132,87196,141959,448,164,99736 +787ca72c6155ba1f00970c1191b0cfacca7d90a7c4627968e669584984a21add,900390608,542920,208,16,1799,0,0,16,1026,477044171,40253,87794,558,135,141760 +ad4fb333111db7a518c9098dea60ea83d4f9cb59eab33bfcaf39b8a703d11720,800024373,62070,69,4,100,3,585,13,486,348084832,88527,73521,752,196,736592 +5a6f193f213c1f58d8bbaa7b8b6d2f75923cebd9df5fc70681a3c8d478a0ca1d,700045717,153924,259,6,154,0,0,7,833,682284676,219069,160295,311,70,794936 +464748ba94d4b8d847b904f6de018c19132af9c622c7e2c4a0fe4fe39323c5a9,1200277212,365048,178,4,1236,2,1872,24,449,339408661,106862,105073,318,77,804908 +6a535c00ea9b447cf4dd9482c709b438c1098434417d4ee489139fcca29b44fe,800024825,48161,227,17,105,0,0,9,110,325664583,165718,107435,301,144,327632 diff --git a/src/transactions/test/TxEnvelopeTests.cpp b/src/transactions/test/TxEnvelopeTests.cpp index b60f3038ea..9c4a9cc517 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)] + .getSequentialTxs(); 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 b908b8f4a9..6fbeb957f8 100644 --- a/src/util/ProtocolVersion.h +++ b/src/util/ProtocolVersion.h @@ -34,7 +34,8 @@ enum class ProtocolVersion : uint32_t V_19, V_20, V_21, - V_22 + V_22, + V_23 }; // Checks whether provided protocolVersion is before (i.e. strictly lower than) @@ -50,4 +51,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_23; }