diff --git a/src/herder/HerderImpl.cpp b/src/herder/HerderImpl.cpp index 6d490a6721..81cc5fd74f 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 61bfae0e62..ba5c44acce 100644 --- a/src/herder/TransactionQueue.cpp +++ b/src/herder/TransactionQueue.cpp @@ -940,11 +940,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 0266c276cc..ca40142998 100644 --- a/src/herder/test/HerderTests.cpp +++ b/src/herder/test/HerderTests.cpp @@ -304,7 +304,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); @@ -316,7 +316,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); @@ -328,7 +328,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); @@ -340,7 +340,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); @@ -374,8 +374,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()); @@ -389,7 +389,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}); @@ -401,7 +401,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}); @@ -415,7 +415,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}); @@ -431,7 +431,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}); @@ -444,7 +444,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}); @@ -458,7 +458,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}); @@ -474,7 +474,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}); @@ -540,7 +540,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; @@ -610,7 +610,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); @@ -627,7 +627,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); @@ -656,14 +656,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); @@ -682,14 +682,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); @@ -701,7 +701,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); @@ -711,7 +711,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()); @@ -726,14 +726,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); @@ -749,7 +749,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()); } @@ -1031,7 +1031,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; @@ -1042,17 +1042,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] = @@ -1060,7 +1060,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) { @@ -1101,7 +1101,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; @@ -1119,10 +1119,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; @@ -1175,7 +1175,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; @@ -1240,10 +1240,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; @@ -1256,11 +1256,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 @@ -1270,11 +1270,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") @@ -1282,21 +1282,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()); @@ -1318,11 +1320,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) @@ -1331,10 +1333,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 = @@ -1350,11 +1353,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; @@ -1363,9 +1366,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 @@ -1378,7 +1381,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, @@ -1406,12 +1409,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); @@ -1431,8 +1428,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()) { @@ -1459,7 +1457,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()) { @@ -1470,6 +1468,7 @@ TEST_CASE("surge pricing with DEX separation", "[herder][txset]") REQUIRE(*baseFee == expectedNonDexBaseFee); } } + REQUIRE(cntA == expectedTxsA); REQUIRE(cntB == expectedTxsB); REQUIRE(cntC == expectedTxsC); @@ -1639,19 +1638,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 @@ -1659,6 +1660,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) @@ -2164,7 +2166,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 @@ -2185,10 +2187,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)); }; @@ -3095,18 +3098,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; }, @@ -4221,7 +4246,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()) @@ -4235,7 +4260,7 @@ externalize(SecretKey const& sk, LedgerManager& lm, HerderImpl& herder, } } - TxSetPhaseTransactions txsPhases{classicTxs}; + PerPhaseTransactionList txsPhases{classicTxs}; txsPhases.emplace_back(sorobanTxs); @@ -4289,12 +4314,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..41692761d9 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()); + } } } } @@ -78,19 +121,32 @@ makeNonValidatedTxSet(std::vector const& txs, std::pair makeNonValidatedGeneralizedTxSet( std::vector const& txsPerBaseFee, Application& app, - Hash const& previousLedgerHash) + Hash const& previousLedgerHash, std::optional useParallelSorobanPhase) { - auto xdrTxSet = makeGeneralizedTxSetXDR(txsPerBaseFee, previousLedgerHash); + if (!useParallelSorobanPhase.has_value()) + { + 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..b87e3a92d0 100644 --- a/src/herder/test/TestTxSetUtils.h +++ b/src/herder/test/TestTxSetUtils.h @@ -17,11 +17,12 @@ using ComponentPhases = std::vector< std::pair makeNonValidatedGeneralizedTxSet( std::vector const& txsPerBaseFee, Application& app, - Hash const& previousLedgerHash); + Hash const& previousLedgerHash, + std::optional useParallelSorobanPhase = std::nullopt); 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 19b6de4890..64caea683e 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 a3d5c70dab..d83ef7cf1c 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,85 @@ 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("soroban phase version validation", "[txset][soroban]") +{ + auto runTest = [](uint32_t protocolVersion, + bool useParallelSorobanPhase) -> bool { + VirtualClock clock; + auto cfg = getTestConfig(); + cfg.LEDGER_PROTOCOL_VERSION = protocolVersion; + cfg.TESTING_UPGRADE_LEDGER_PROTOCOL_VERSION = protocolVersion; + auto app = createTestApplication(clock, cfg); + auto txSet = + testtxset::makeNonValidatedGeneralizedTxSet( + {{}, {}}, *app, + app->getLedgerManager().getLastClosedLedgerHeader().hash, + useParallelSorobanPhase) + .second; + REQUIRE(txSet); + return txSet->checkValid(*app, 0, 0); + }; + SECTION("sequential phase") + { + SECTION("valid before parallel tx set protocol version") + { + REQUIRE(runTest( + static_cast(PARALLEL_SOROBAN_PHASE_PROTOCOL_VERSION) - + 1, + false)); + } + } +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION + SECTION("sequential phase invalid at parallel tx set protocol version") + { + REQUIRE(!runTest( + static_cast(PARALLEL_SOROBAN_PHASE_PROTOCOL_VERSION), + false)); + } + SECTION("parallel phase") + { + SECTION("valid before parallel tx set protocol version") + { + REQUIRE(!runTest( + static_cast(PARALLEL_SOROBAN_PHASE_PROTOCOL_VERSION) - + 1, + true)); + } + SECTION("invalid at parallel tx set protocol version") + { + REQUIRE(runTest( + static_cast(PARALLEL_SOROBAN_PHASE_PROTOCOL_VERSION), + true)); + } + } +#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 +937,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 +989,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 +1030,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 +1038,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 +1340,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 +1365,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 +1396,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 3c530fb3be..03d118ef48 100644 --- a/src/herder/test/UpgradesTests.cpp +++ b/src/herder/test/UpgradesTests.cpp @@ -1958,11 +1958,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 059aa19a97..e2ea59c6db 100644 --- a/src/history/test/HistoryTests.cpp +++ b/src/history/test/HistoryTests.cpp @@ -1180,9 +1180,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 f6132ebcec..501e7016c2 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 a221aa6b7c..ca0b0bf949 100644 --- a/src/ledger/LedgerManagerImpl.cpp +++ b/src/ledger/LedgerManagerImpl.cpp @@ -897,23 +897,14 @@ 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); - - auto const mutableTxResults = processFeesSeqNums( - txs, ltx, *applicableTxSet, ledgerCloseMeta, ledgerData); - - TransactionResultSet txResultSet; - txResultSet.results.reserve(txs.size()); + prefetchTxSourceIds(*applicableTxSet); + auto const mutableTxResults = + processFeesSeqNums(*applicableTxSet, ltx, ledgerCloseMeta, ledgerData); // 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; @@ -1352,14 +1343,13 @@ 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, LedgerCloseData const& ledgerData) { ZoneScoped; std::vector txResults; - txResults.reserve(txs.size()); + txResults.reserve(txSet.sizeTxTotal()); CLOG_DEBUG(Ledger, "processing fees and sequence numbers"); int index = 0; try @@ -1383,54 +1373,55 @@ LedgerManagerImpl::processFeesSeqNums( #endif 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))); -#ifdef BUILD_TESTS - if (expectedResultsIter) + for (auto const& tx : phase) { - releaseAssert(*expectedResultsIter != - expectedResults->results.end()); - releaseAssert((*expectedResultsIter)->transactionHash == - tx->getContentsHash()); - txResults.back()->setReplayTransactionResult( - (*expectedResultsIter)->result); - ++(*expectedResultsIter); - } + LedgerTxn ltxTx(ltx); + txResults.push_back( + tx->processFeeSeqNum(ltxTx, txSet.getTxBaseFee(tx))); +#ifdef BUILD_TESTS + if (expectedResultsIter) + { + releaseAssert(*expectedResultsIter != + expectedResults->results.end()); + releaseAssert((*expectedResultsIter)->transactionHash == + tx->getContentsHash()); + txResults.back()->setReplayTransactionResult( + (*expectedResultsIter)->result); + ++(*expectedResultsIter); + } #endif // BUILD_TESTS - if (protocolVersionStartsFrom( - ltxTx.loadHeader().current().ledgerVersion, - ProtocolVersion::V_19)) - { - auto res = - accToMaxSeq.emplace(tx->getSourceID(), tx->getSeqNum()); - if (!res.second) + 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) @@ -1473,24 +1464,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) @@ -1498,19 +1490,22 @@ LedgerManagerImpl::prefetchTransactionData( UnorderedSet sorobanKeys; auto lkMeter = make_unique(); UnorderedSet classicKeys; - for (auto const& tx : txs) + for (auto const& phase : txSet.getPhases()) { - if (tx->isSoroban()) - { - tx->insertKeysForTxApply(sorobanKeys, lkMeter.get()); - } - else + for (auto const& tx : phase) { - tx->insertKeysForTxApply(classicKeys, nullptr); + if (tx->isSoroban()) + { + tx->insertKeysForTxApply(sorobanKeys, lkMeter.get()); + } + 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 (!sorobanKeys.empty()) { mApp.getLedgerTxnRoot().prefetchSoroban(sorobanKeys, lkMeter.get()); @@ -1520,21 +1515,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)); @@ -1545,80 +1539,88 @@ 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()) + for (auto const& tx : phase) { - SHA256 subSeedSha; - subSeedSha.add(sorobanBasePrngSeed); - subSeedSha.add(xdr::xdr_to_opaque(txNum)); - subSeed = subSeedSha.finish(); - } - ++txNum; + 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()) + { + SHA256 subSeedSha; + subSeedSha.add(sorobanBasePrngSeed); + subSeedSha.add(xdr::xdr_to_opaque(txNum)); + subSeed = subSeedSha.finish(); + } + ++txNum; - TransactionResultPair results; - results.transactionHash = tx->getContentsHash(); + TransactionResultPair results; + results.transactionHash = tx->getContentsHash(); - tx->apply(mApp.getAppConnector(), ltx, tm, mutableTxResult, subSeed); - tx->processPostApply(mApp.getAppConnector(), ltx, tm, mutableTxResult); + tx->apply(mApp.getAppConnector(), ltx, tm, mutableTxResult, + subSeed); + tx->processPostApply(mApp.getAppConnector(), ltx, tm, + mutableTxResult); - results.result = mutableTxResult->getResult(); - if (results.result.result.code() == TransactionResultCode::txSUCCESS) - { - if (tx->isSoroban()) + results.result = mutableTxResult->getResult(); + if (results.result.result.code() == + TransactionResultCode::txSUCCESS) { - ++sorobanTxSucceeded; + if (tx->isSoroban()) + { + ++sorobanTxSucceeded; + } + ++txSucceeded; } - ++txSucceeded; - } - else - { - if (tx->isSoroban()) + else { - ++sorobanTxFailed; + if (tx->isSoroban()) + { + ++sorobanTxFailed; + } + ++txFailed; } - ++txFailed; - } - - // First gather the TransactionResultPair into the TxResultSet for - // hashing into the ledger header. - txResultSet.results.emplace_back(results); + // First gather the TransactionResultPair into the TxResultSet for + // hashing into the ledger header. + txResultSet.results.emplace_back(results); #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); @@ -1626,6 +1628,7 @@ LedgerManagerImpl::applyTransactions( mSorobanTransactionApplySucceeded.inc(sorobanTxSucceeded); mSorobanTransactionApplyFailed.inc(sorobanTxFailed); logTxApplyMetrics(ltx, numTxs, numOps); + return txResultSet; } void @@ -1659,8 +1662,9 @@ LedgerManagerImpl::storeCurrentLedger(LedgerHeader const& header, { bl = mApp.getBucketManager().getLiveBucketList(); } - // 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); @@ -1747,24 +1751,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 5a9743a145..5408c917c8 100644 --- a/src/ledger/LedgerManagerImpl.h +++ b/src/ledger/LedgerManagerImpl.h @@ -89,16 +89,14 @@ 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, LedgerCloseData const& ledgerData); - 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. @@ -111,9 +109,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/test/TxTests.cpp b/src/test/TxTests.cpp index 25f6fbf8d3..9cdcac2d2c 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 943063068f..117751c6b8 100644 --- a/src/testdata/ledger-close-meta-v1-protocol-23-soroban.json +++ b/src/testdata/ledger-close-meta-v1-protocol-23-soroban.json @@ -6,23 +6,23 @@ "v": 0 }, "ledgerHeader": { - "hash": "ca60f54d9329381ca6d530366a1db8a91fc559302c9ec64e534764a62d1adf69", + "hash": "a6fbcc8f60da431ed4fb4ced5b84b54489b0e331b8a9a2b1e85c9c22a19551fa", "header": { "ledgerVersion": 23, - "previousLedgerHash": "4aefc4467fab107446aeef0ba765631ec3e229ec08e4b29a5341ac38fdf59f9a", + "previousLedgerHash": "6994ce5b967538e38ef4e55d3d98fda420a10a323736a85ec02decae70faac8e", "scpValue": { - "txSetHash": "8c0d57e026680029e6628fb1b7c68d9b2861fde3481215d8e42b136a466b6fbe", + "txSetHash": "79dc76ffd553e86f2cb9b63042a8f89a706f3cc55fec8cac7865c2363725abb7", "closeTime": 1451692800, "upgrades": [], "ext": { "v": "STELLAR_VALUE_SIGNED", "lcValueSignature": { "nodeID": "GDDOUW25MRFLNXQMN3OODP6JQEXSGLMHAFZV4XPQ2D3GA4QFIDMEJG2O", - "signature": "b53daa10435473233214d2862f9af7cd5f71ff6b162717124700add4d9a5de24f45303c46ef78feb2aa724348b2771555549689bc3e54a5ad6fbd4e02d4e6f0d" + "signature": "13eb24d05caeb4e721fbbe96bd6170d90da02f42567e519528177c4efae238365a87425cef90db445ba2bc28585022435fa17545f9bed1051510a940ab23c600" } } }, - "txSetResultHash": "5edc137152a404b1ea08e31b691098b8b8ab53a30041af25868849694535ecf5", + "txSetResultHash": "65b6fe91abfe43ed98fa2163f08fdf3f2f3231101bba05102521186c25a1cc4b", "bucketListHash": "d71ea87f579389c8c976fca3cf1b755de14330d7aaa35e6544411ec2bd1cf443", "ledgerSeq": 28, "totalCoins": 1000000000000000000, @@ -49,20 +49,19 @@ "txSet": { "v": 1, "v1TxSet": { - "previousLedgerHash": "4aefc4467fab107446aeef0ba765631ec3e229ec08e4b29a5341ac38fdf59f9a", + "previousLedgerHash": "6994ce5b967538e38ef4e55d3d98fda420a10a323736a85ec02decae70faac8e", "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 @@ } } ] - } - } - ] + ] + ] + } } ] } @@ -503,18 +502,18 @@ "txProcessing": [ { "result": { - "transactionHash": "364ec41dce0a678476ea3ebfc5caa28165ef3bf0976071d858b1c4044f187d25", + "transactionHash": "62d28c373389d447341e9d75bc84e2c91437169a2a70d3606c8b3aa7d198ef5c", "result": { - "feeCharged": 60559, + "feeCharged": 42954, "result": { - "code": "txSUCCESS", + "code": "txFAILED", "results": [ { "code": "opINNER", "tr": { - "type": "EXTEND_FOOTPRINT_TTL", - "extendFootprintTTLResult": { - "code": "EXTEND_FOOTPRINT_TTL_SUCCESS" + "type": "INVOKE_HOST_FUNCTION", + "invokeHostFunctionResult": { + "code": "INVOKE_HOST_FUNCTION_RESOURCE_LIMIT_EXCEEDED" } } } @@ -529,13 +528,13 @@ { "type": "LEDGER_ENTRY_STATE", "state": { - "lastModifiedLedgerSeq": 9, + "lastModifiedLedgerSeq": 12, "data": { "type": "ACCOUNT", "account": { - "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", + "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", "balance": 400000000, - "seqNum": 38654705664, + "seqNum": 51539607552, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -559,9 +558,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", - "balance": 398999900, - "seqNum": 38654705664, + "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", + "balance": 398956045, + "seqNum": 51539607552, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -593,9 +592,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", - "balance": 398999900, - "seqNum": 38654705664, + "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", + "balance": 398956045, + "seqNum": 51539607552, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -619,9 +618,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", - "balance": 398999900, - "seqNum": 38654705665, + "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", + "balance": 398956045, + "seqNum": 51539607553, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -663,76 +662,7 @@ } } ], - "operations": [ - { - "changes": [ - { - "type": "LEDGER_ENTRY_STATE", - "state": { - "lastModifiedLedgerSeq": 6, - "data": { - "type": "TTL", - "ttl": { - "keyHash": "091ddece931776a53f93869b82c24e132cc12d00d961fac09bc3b9cb9021c62d", - "liveUntilLedgerSeq": 10006 - } - }, - "ext": { - "v": 0 - } - } - }, - { - "type": "LEDGER_ENTRY_UPDATED", - "updated": { - "lastModifiedLedgerSeq": 28, - "data": { - "type": "TTL", - "ttl": { - "keyHash": "091ddece931776a53f93869b82c24e132cc12d00d961fac09bc3b9cb9021c62d", - "liveUntilLedgerSeq": 10028 - } - }, - "ext": { - "v": 0 - } - } - }, - { - "type": "LEDGER_ENTRY_STATE", - "state": { - "lastModifiedLedgerSeq": 6, - "data": { - "type": "TTL", - "ttl": { - "keyHash": "60313f9b273db0b14c3e503cf6cc152dd14a0c57e5e81a23e86b4e27a23a2c06", - "liveUntilLedgerSeq": 10006 - } - }, - "ext": { - "v": 0 - } - } - }, - { - "type": "LEDGER_ENTRY_UPDATED", - "updated": { - "lastModifiedLedgerSeq": 28, - "data": { - "type": "TTL", - "ttl": { - "keyHash": "60313f9b273db0b14c3e503cf6cc152dd14a0c57e5e81a23e86b4e27a23a2c06", - "liveUntilLedgerSeq": 10028 - } - }, - "ext": { - "v": 0 - } - } - } - ] - } - ], + "operations": [], "txChangesAfter": [ { "type": "LEDGER_ENTRY_STATE", @@ -741,9 +671,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", - "balance": 398999900, - "seqNum": 38654705665, + "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", + "balance": 398956045, + "seqNum": 51539607553, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -791,9 +721,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", - "balance": 399939441, - "seqNum": 38654705665, + "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", + "balance": 399957046, + "seqNum": 51539607553, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -851,19 +781,18 @@ }, { "result": { - "transactionHash": "e310227a8c0d8d1f78632e65ebca281cd60d8619c9afc64491bcce98e7cd7ee3", + "transactionHash": "bb0a6b13caea6b015555dfd332aca1099e8654896bf7d1bcce8432e833a2572a", "result": { - "feeCharged": 106775, + "feeCharged": 61612, "result": { - "code": "txSUCCESS", + "code": "txFAILED", "results": [ { "code": "opINNER", "tr": { "type": "INVOKE_HOST_FUNCTION", "invokeHostFunctionResult": { - "code": "INVOKE_HOST_FUNCTION_SUCCESS", - "success": "cbbc48750debb8535093b3deaf88ac7f4cff87425576a58de2bac754acdb4616" + "code": "INVOKE_HOST_FUNCTION_TRAPPED" } } } @@ -878,13 +807,13 @@ { "type": "LEDGER_ENTRY_STATE", "state": { - "lastModifiedLedgerSeq": 10, + "lastModifiedLedgerSeq": 11, "data": { "type": "ACCOUNT", "account": { - "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", + "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", "balance": 400000000, - "seqNum": 42949672960, + "seqNum": 47244640256, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -908,9 +837,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", - "balance": 399873274, - "seqNum": 42949672960, + "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", + "balance": 399898388, + "seqNum": 47244640256, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -942,9 +871,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", - "balance": 399873274, - "seqNum": 42949672960, + "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", + "balance": 399898388, + "seqNum": 47244640256, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -968,9 +897,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", - "balance": 399873274, - "seqNum": 42949672961, + "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", + "balance": 399898388, + "seqNum": 47244640257, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1012,55 +941,7 @@ } } ], - "operations": [ - { - "changes": [ - { - "type": "LEDGER_ENTRY_CREATED", - "created": { - "lastModifiedLedgerSeq": 28, - "data": { - "type": "CONTRACT_DATA", - "contractData": { - "ext": { - "v": 0 - }, - "contract": "CAA3QKIP2SNVXUJTB4HKOGF55JTSSMQGED3FZYNHMNSXYV3DRRMAWA3Y", - "key": { - "type": "SCV_SYMBOL", - "sym": "key" - }, - "durability": "PERSISTENT", - "val": { - "type": "SCV_U64", - "u64": 42 - } - } - }, - "ext": { - "v": 0 - } - } - }, - { - "type": "LEDGER_ENTRY_CREATED", - "created": { - "lastModifiedLedgerSeq": 28, - "data": { - "type": "TTL", - "ttl": { - "keyHash": "764f4e59e20ac1a357f9f26ab0eaf46d196ab74822db44f039353a6f114864aa", - "liveUntilLedgerSeq": 47 - } - }, - "ext": { - "v": 0 - } - } - } - ] - } - ], + "operations": [], "txChangesAfter": [ { "type": "LEDGER_ENTRY_STATE", @@ -1069,9 +950,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", - "balance": 399873274, - "seqNum": 42949672961, + "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", + "balance": 399898388, + "seqNum": 47244640257, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1119,9 +1000,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", - "balance": 399893225, - "seqNum": 42949672961, + "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", + "balance": 399938388, + "seqNum": 47244640257, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1169,7 +1050,8 @@ }, "events": [], "returnValue": { - "type": "SCV_VOID" + "type": "SCV_BOOL", + "b": "FALSE" }, "diagnosticEvents": [] } @@ -1178,18 +1060,19 @@ }, { "result": { - "transactionHash": "ee68d27257fa137933de22b3fdfbc4a736ec01af29a9e25e5b807252b1a1ca0a", + "transactionHash": "e310227a8c0d8d1f78632e65ebca281cd60d8619c9afc64491bcce98e7cd7ee3", "result": { - "feeCharged": 51547, + "feeCharged": 106775, "result": { "code": "txSUCCESS", "results": [ { "code": "opINNER", "tr": { - "type": "RESTORE_FOOTPRINT", - "restoreFootprintResult": { - "code": "RESTORE_FOOTPRINT_SUCCESS" + "type": "INVOKE_HOST_FUNCTION", + "invokeHostFunctionResult": { + "code": "INVOKE_HOST_FUNCTION_SUCCESS", + "success": "cbbc48750debb8535093b3deaf88ac7f4cff87425576a58de2bac754acdb4616" } } } @@ -1204,13 +1087,13 @@ { "type": "LEDGER_ENTRY_STATE", "state": { - "lastModifiedLedgerSeq": 8, + "lastModifiedLedgerSeq": 10, "data": { "type": "ACCOUNT", "account": { - "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", + "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", "balance": 400000000, - "seqNum": 34359738368, + "seqNum": 42949672960, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1234,9 +1117,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", - "balance": 398999900, - "seqNum": 34359738368, + "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", + "balance": 399873274, + "seqNum": 42949672960, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1268,9 +1151,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", - "balance": 398999900, - "seqNum": 34359738368, + "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", + "balance": 399873274, + "seqNum": 42949672960, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1294,9 +1177,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", - "balance": 398999900, - "seqNum": 34359738369, + "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", + "balance": 399873274, + "seqNum": 42949672961, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1342,14 +1225,25 @@ { "changes": [ { - "type": "LEDGER_ENTRY_STATE", - "state": { - "lastModifiedLedgerSeq": 7, + "type": "LEDGER_ENTRY_CREATED", + "created": { + "lastModifiedLedgerSeq": 28, "data": { - "type": "TTL", - "ttl": { - "keyHash": "4791962cd1e2c7b8f8af3f96514f9777f0156a48261fb885a571a7f69b33a058", - "liveUntilLedgerSeq": 26 + "type": "CONTRACT_DATA", + "contractData": { + "ext": { + "v": 0 + }, + "contract": "CAA3QKIP2SNVXUJTB4HKOGF55JTSSMQGED3FZYNHMNSXYV3DRRMAWA3Y", + "key": { + "type": "SCV_SYMBOL", + "sym": "key" + }, + "durability": "PERSISTENT", + "val": { + "type": "SCV_U64", + "u64": 42 + } } }, "ext": { @@ -1358,13 +1252,13 @@ } }, { - "type": "LEDGER_ENTRY_UPDATED", - "updated": { + "type": "LEDGER_ENTRY_CREATED", + "created": { "lastModifiedLedgerSeq": 28, "data": { "type": "TTL", "ttl": { - "keyHash": "4791962cd1e2c7b8f8af3f96514f9777f0156a48261fb885a571a7f69b33a058", + "keyHash": "764f4e59e20ac1a357f9f26ab0eaf46d196ab74822db44f039353a6f114864aa", "liveUntilLedgerSeq": 47 } }, @@ -1384,9 +1278,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", - "balance": 398999900, - "seqNum": 34359738369, + "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", + "balance": 399873274, + "seqNum": 42949672961, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1434,9 +1328,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", - "balance": 399948453, - "seqNum": 34359738369, + "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", + "balance": 399893225, + "seqNum": 42949672961, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1484,8 +1378,7 @@ }, "events": [], "returnValue": { - "type": "SCV_BOOL", - "b": "FALSE" + "type": "SCV_VOID" }, "diagnosticEvents": [] } @@ -1494,18 +1387,18 @@ }, { "result": { - "transactionHash": "62d28c373389d447341e9d75bc84e2c91437169a2a70d3606c8b3aa7d198ef5c", + "transactionHash": "364ec41dce0a678476ea3ebfc5caa28165ef3bf0976071d858b1c4044f187d25", "result": { - "feeCharged": 42954, + "feeCharged": 60559, "result": { - "code": "txFAILED", + "code": "txSUCCESS", "results": [ { "code": "opINNER", "tr": { - "type": "INVOKE_HOST_FUNCTION", - "invokeHostFunctionResult": { - "code": "INVOKE_HOST_FUNCTION_RESOURCE_LIMIT_EXCEEDED" + "type": "EXTEND_FOOTPRINT_TTL", + "extendFootprintTTLResult": { + "code": "EXTEND_FOOTPRINT_TTL_SUCCESS" } } } @@ -1520,13 +1413,13 @@ { "type": "LEDGER_ENTRY_STATE", "state": { - "lastModifiedLedgerSeq": 12, + "lastModifiedLedgerSeq": 9, "data": { "type": "ACCOUNT", "account": { - "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", + "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", "balance": 400000000, - "seqNum": 51539607552, + "seqNum": 38654705664, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1550,9 +1443,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", - "balance": 398956045, - "seqNum": 51539607552, + "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", + "balance": 398999900, + "seqNum": 38654705664, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1584,9 +1477,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", - "balance": 398956045, - "seqNum": 51539607552, + "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", + "balance": 398999900, + "seqNum": 38654705664, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1610,9 +1503,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", - "balance": 398956045, - "seqNum": 51539607553, + "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", + "balance": 398999900, + "seqNum": 38654705665, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1654,7 +1547,76 @@ } } ], - "operations": [], + "operations": [ + { + "changes": [ + { + "type": "LEDGER_ENTRY_STATE", + "state": { + "lastModifiedLedgerSeq": 6, + "data": { + "type": "TTL", + "ttl": { + "keyHash": "091ddece931776a53f93869b82c24e132cc12d00d961fac09bc3b9cb9021c62d", + "liveUntilLedgerSeq": 10006 + } + }, + "ext": { + "v": 0 + } + } + }, + { + "type": "LEDGER_ENTRY_UPDATED", + "updated": { + "lastModifiedLedgerSeq": 28, + "data": { + "type": "TTL", + "ttl": { + "keyHash": "091ddece931776a53f93869b82c24e132cc12d00d961fac09bc3b9cb9021c62d", + "liveUntilLedgerSeq": 10028 + } + }, + "ext": { + "v": 0 + } + } + }, + { + "type": "LEDGER_ENTRY_STATE", + "state": { + "lastModifiedLedgerSeq": 6, + "data": { + "type": "TTL", + "ttl": { + "keyHash": "60313f9b273db0b14c3e503cf6cc152dd14a0c57e5e81a23e86b4e27a23a2c06", + "liveUntilLedgerSeq": 10006 + } + }, + "ext": { + "v": 0 + } + } + }, + { + "type": "LEDGER_ENTRY_UPDATED", + "updated": { + "lastModifiedLedgerSeq": 28, + "data": { + "type": "TTL", + "ttl": { + "keyHash": "60313f9b273db0b14c3e503cf6cc152dd14a0c57e5e81a23e86b4e27a23a2c06", + "liveUntilLedgerSeq": 10028 + } + }, + "ext": { + "v": 0 + } + } + } + ] + } + ], "txChangesAfter": [ { "type": "LEDGER_ENTRY_STATE", @@ -1663,9 +1625,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", - "balance": 398956045, - "seqNum": 51539607553, + "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", + "balance": 398999900, + "seqNum": 38654705665, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1713,9 +1675,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", - "balance": 399957046, - "seqNum": 51539607553, + "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", + "balance": 399939441, + "seqNum": 38654705665, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1773,18 +1735,18 @@ }, { "result": { - "transactionHash": "bb0a6b13caea6b015555dfd332aca1099e8654896bf7d1bcce8432e833a2572a", + "transactionHash": "ee68d27257fa137933de22b3fdfbc4a736ec01af29a9e25e5b807252b1a1ca0a", "result": { - "feeCharged": 61612, + "feeCharged": 51547, "result": { - "code": "txFAILED", + "code": "txSUCCESS", "results": [ { "code": "opINNER", "tr": { - "type": "INVOKE_HOST_FUNCTION", - "invokeHostFunctionResult": { - "code": "INVOKE_HOST_FUNCTION_TRAPPED" + "type": "RESTORE_FOOTPRINT", + "restoreFootprintResult": { + "code": "RESTORE_FOOTPRINT_SUCCESS" } } } @@ -1799,13 +1761,13 @@ { "type": "LEDGER_ENTRY_STATE", "state": { - "lastModifiedLedgerSeq": 11, + "lastModifiedLedgerSeq": 8, "data": { "type": "ACCOUNT", "account": { - "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", + "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", "balance": 400000000, - "seqNum": 47244640256, + "seqNum": 34359738368, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1829,9 +1791,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", - "balance": 399898388, - "seqNum": 47244640256, + "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", + "balance": 398999900, + "seqNum": 34359738368, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1863,9 +1825,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", - "balance": 399898388, - "seqNum": 47244640256, + "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", + "balance": 398999900, + "seqNum": 34359738368, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1889,9 +1851,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", - "balance": 399898388, - "seqNum": 47244640257, + "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", + "balance": 398999900, + "seqNum": 34359738369, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1933,7 +1895,44 @@ } } ], - "operations": [], + "operations": [ + { + "changes": [ + { + "type": "LEDGER_ENTRY_STATE", + "state": { + "lastModifiedLedgerSeq": 7, + "data": { + "type": "TTL", + "ttl": { + "keyHash": "4791962cd1e2c7b8f8af3f96514f9777f0156a48261fb885a571a7f69b33a058", + "liveUntilLedgerSeq": 26 + } + }, + "ext": { + "v": 0 + } + } + }, + { + "type": "LEDGER_ENTRY_UPDATED", + "updated": { + "lastModifiedLedgerSeq": 28, + "data": { + "type": "TTL", + "ttl": { + "keyHash": "4791962cd1e2c7b8f8af3f96514f9777f0156a48261fb885a571a7f69b33a058", + "liveUntilLedgerSeq": 47 + } + }, + "ext": { + "v": 0 + } + } + } + ] + } + ], "txChangesAfter": [ { "type": "LEDGER_ENTRY_STATE", @@ -1942,9 +1941,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", - "balance": 399898388, - "seqNum": 47244640257, + "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", + "balance": 398999900, + "seqNum": 34359738369, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1992,9 +1991,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", - "balance": 399938388, - "seqNum": 47244640257, + "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", + "balance": 399948453, + "seqNum": 34359738369, "numSubEntries": 0, "inflationDest": null, "flags": 0, diff --git a/src/testdata/ledger-close-meta-v1-protocol-23.json b/src/testdata/ledger-close-meta-v1-protocol-23.json index 0369774ffd..a1e1c675ce 100644 --- a/src/testdata/ledger-close-meta-v1-protocol-23.json +++ b/src/testdata/ledger-close-meta-v1-protocol-23.json @@ -6,19 +6,19 @@ "v": 0 }, "ledgerHeader": { - "hash": "ad4e4f78e0ef6393e3c3f8dec293ee556975f6d875c693e7007d8743492bc519", + "hash": "055a504e9c7b73d7cee8d2f7eb24f884273bb4381e96fd8a4f5ac4b210d9c1b1", "header": { "ledgerVersion": 23, - "previousLedgerHash": "fe02c7ce491811bb67777598e698a8f900bab0394eb867bce954de9e92ab3238", + "previousLedgerHash": "eccc8c2717c84b51a5a64efae54330933b1a5e70c001cd8342d6073319298568", "scpValue": { - "txSetHash": "fa9e787622c23e48d464a24a9afd46003a24aa2e0a6174da2d81d3bb7db6708a", + "txSetHash": "d79023fe99fb77aed2df7663d843c20c0ea33d60a9be005905dadd7f206e4726", "closeTime": 0, "upgrades": [], "ext": { "v": "STELLAR_VALUE_SIGNED", "lcValueSignature": { "nodeID": "GDDOUW25MRFLNXQMN3OODP6JQEXSGLMHAFZV4XPQ2D3GA4QFIDMEJG2O", - "signature": "cfa36fec93baadf4370108f9e0f6c6cc8d5a431300610e855920e0436fd1f61045e632e45c1fa5bc01759f2d86d75b43f2c57a212e087becc32a4305e1b94400" + "signature": "54566eb5e8c73f2068965562fe3f2363561bc7d7faa64bcfb11ad1c1b73d801fa073b03de529ea45b42c1967d6ac18dfe40e7c0f06d67a7f0eb7a5f90c678a0d" } } }, @@ -49,7 +49,7 @@ "txSet": { "v": 1, "v1TxSet": { - "previousLedgerHash": "fe02c7ce491811bb67777598e698a8f900bab0394eb867bce954de9e92ab3238", + "previousLedgerHash": "eccc8c2717c84b51a5a64efae54330933b1a5e70c001cd8342d6073319298568", "phases": [ { "v": 0, @@ -176,8 +176,11 @@ ] }, { - "v": 0, - "v0Components": [] + "v": 1, + "parallelTxsComponent": { + "baseFee": null, + "executionStages": [] + } } ] } 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 8f06cfc64c..5964e6cdcd 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 32341840c9..6fbeb957f8 100644 --- a/src/util/ProtocolVersion.h +++ b/src/util/ProtocolVersion.h @@ -51,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; }