From 4cdc91b19fb69d699f9e65dbb512157ddcc021ef Mon Sep 17 00:00:00 2001 From: dmkozh Date: Mon, 3 Aug 2026 17:39:42 -0400 Subject: [PATCH 1/2] Parallelize Soroban pre-apply step. While the current logic intertwines reads and writes, in fact it can be cleanly separated into a read-only validation step, and a sequential commit step that simply bumps the sequence numbers and removes pre-authorized tx signers. This is possible because that while the writes change the entries that take part in validation, none of these changes are relevant during the validation. Specifically, sequence number bump is only observable by a single transaction (the one that has the respective account as a source), and the pre-authorized tx signer by definition belongs to a single transaction. There is also a subtle caveat to the latter operation: it increases the available balance of the signer owner (or its sponsor), but since at the pre-apply time the fees have already been charged, we're only checking that the account available balance is non-negative, which is an invariant that must always hold in the current protocol. The change is not protocol-gated because it's not a protocol change for the *current* protocol. It was technically a protocol change prior to p26 where we had a bug that actually did allow overcharging the fee bump source accounts and thus making their available balance to go negative. However, the bug has been fixed without the behavior ever triggering on-chain, and thus this replay-only behavior change should be non-observable. This change significantly speeds up the pre-apply step. On the local high TPL benchmarks I'm getting 30-60ms improvement locally compared to the main branch version. --- src/ledger/ImmutableLedgerView.cpp | 70 + src/ledger/ImmutableLedgerView.h | 35 + src/transactions/FeeBumpTransactionFrame.cpp | 39 +- src/transactions/FeeBumpTransactionFrame.h | 13 +- src/transactions/ParallelApplyUtils.cpp | 125 +- src/transactions/ParallelApplyUtils.h | 8 +- src/transactions/TransactionFrame.cpp | 183 +- src/transactions/TransactionFrame.h | 53 +- src/transactions/TransactionFrameBase.h | 19 +- .../test/InvokeHostFunctionTests.cpp | 448 +++- src/transactions/test/ParallelApplyTest.cpp | 304 +++ src/transactions/test/SorobanTxTestUtils.cpp | 9 +- .../test/TransactionTestFrame.cpp | 18 +- src/transactions/test/TransactionTestFrame.h | 13 +- src/util/BatchExecutor.cpp | 29 + src/util/BatchExecutor.h | 17 + .../InvokeHostFunctionTests.json | 1845 +++++++++++++++ .../InvokeHostFunctionTests.json | 2050 +++++++++++++++++ 18 files changed, 5132 insertions(+), 146 deletions(-) diff --git a/src/ledger/ImmutableLedgerView.cpp b/src/ledger/ImmutableLedgerView.cpp index 796e51c5d0..3bc3247627 100644 --- a/src/ledger/ImmutableLedgerView.cpp +++ b/src/ledger/ImmutableLedgerView.cpp @@ -188,6 +188,12 @@ CheckValidLedgerViewWrapper::CheckValidLedgerViewWrapper( { } +CheckValidLedgerViewWrapper::CheckValidLedgerViewWrapper( + std::unique_ptr getter) + : mGetter(std::move(getter)) +{ +} + LedgerHeaderWrapper CheckValidLedgerViewWrapper::getLedgerHeader() const { @@ -355,6 +361,70 @@ ImmutableLedgerView::executeWithMaybeInnerSnapshot( "ImmutableLedgerView::executeWithMaybeInnerSnapshot is illegal: " "ImmutableLedgerView has no nested snapshots"); } +SorobanPreApplyLedgerView::SorobanPreApplyLedgerView( + std::shared_ptr header, AbstractLedgerTxn& ltx, + ApplyLedgerView const& lclView) + : mHeader(std::move(header)), mLtx(ltx), mLclView(lclView) +{ +} + +LedgerHeaderWrapper +SorobanPreApplyLedgerView::getLedgerHeader() const +{ + return LedgerHeaderWrapper(mHeader); +} + +LedgerEntryWrapper +SorobanPreApplyLedgerView::getAccount(AccountID const& account) const +{ + return load(accountKey(account)); +} + +LedgerEntryWrapper +SorobanPreApplyLedgerView::getAccount(LedgerHeaderWrapper const& header, + TransactionFrame const& tx) const +{ + return getAccount(tx.getSourceID()); +} + +LedgerEntryWrapper +SorobanPreApplyLedgerView::getAccount(LedgerHeaderWrapper const& header, + TransactionFrame const& tx, + AccountID const& accountID) const +{ + return getAccount(accountID); +} + +LedgerEntryWrapper +SorobanPreApplyLedgerView::load(LedgerKey const& key) const +{ + auto entryPair = mLtx.getNewestVersionBelowRoot(key); + if (entryPair.first) + { + // Modified in this ledger, so the ltx has the authoritative version. + // A null entry means it has been deleted. + if (!entryPair.second) + { + return LedgerEntryWrapper(nullptr); + } + // Alias the entry owned by the ltx instead of copying it: the aliasing + // constructor shares ownership with the InternalLedgerEntry while + // pointing at the LedgerEntry nested inside it. + return LedgerEntryWrapper(std::shared_ptr( + entryPair.second, &entryPair.second->ledgerEntry())); + } + // Not modified in this ledger, so the last closed ledger snapshot is + // up to date. + return LedgerEntryWrapper(mLclView.loadLiveEntry(key)); +} + +void +SorobanPreApplyLedgerView::executeWithMaybeInnerSnapshot( + std::function f) const +{ + throw std::runtime_error("SorobanPreApplyLedgerView::" + "executeWithMaybeInnerSnapshot is not supported"); +} // === Live BucketList wrapper methods === diff --git a/src/ledger/ImmutableLedgerView.h b/src/ledger/ImmutableLedgerView.h index 3f6a523ea4..52b30f0321 100644 --- a/src/ledger/ImmutableLedgerView.h +++ b/src/ledger/ImmutableLedgerView.h @@ -219,6 +219,39 @@ class ApplyLedgerView : private ImmutableLedgerView, using ImmutableLedgerView::scanLiveEntriesOfType; }; +// An ledger view used by the read-only phase of the Soroban pre-apply. +// +// It's a thin wrapper around the LTX representing the current ledger state, +// and the LCL view, which allows the pre-apply phase to observe the changes +// that happened in the classic phase. +// +// Lookups are first attempted in the LTX *newest version* only (which is thread +// safe as long as we don't mutate the LTX), and only then in the LCL view. +class SorobanPreApplyLedgerView : public AbstractLedgerView +{ + public: + SorobanPreApplyLedgerView(std::shared_ptr header, + AbstractLedgerTxn& ltx, + ApplyLedgerView const& lclView); + + LedgerHeaderWrapper getLedgerHeader() const override; + LedgerEntryWrapper getAccount(AccountID const& account) const override; + LedgerEntryWrapper getAccount(LedgerHeaderWrapper const& header, + TransactionFrame const& tx) const override; + LedgerEntryWrapper getAccount(LedgerHeaderWrapper const& header, + TransactionFrame const& tx, + AccountID const& accountID) const override; + LedgerEntryWrapper load(LedgerKey const& key) const override; + void executeWithMaybeInnerSnapshot( + std::function f) + const override; + + private: + std::shared_ptr mHeader; + AbstractLedgerTxn& mLtx; + ApplyLedgerView mLclView; +}; + // A helper class to create and query read-only snapshots // Automatically decides whether to create a BucketList (recommended), or SQL // snapshot (deprecated, but currently supported) @@ -235,6 +268,8 @@ class CheckValidLedgerViewWrapper : public NonMovableOrCopyable CheckValidLedgerViewWrapper(AbstractLedgerTxn& ltx); CheckValidLedgerViewWrapper(Application& app); explicit CheckValidLedgerViewWrapper(ImmutableLedgerView const& ledgerView); + explicit CheckValidLedgerViewWrapper( + std::unique_ptr getter); #ifdef BUILD_TESTS // Set by overlay-only mode call sites so commonValid skips the seqnum // equality check: on-disk seqnums are frozen at genesis while diff --git a/src/transactions/FeeBumpTransactionFrame.cpp b/src/transactions/FeeBumpTransactionFrame.cpp index 9d62ddba1f..37aa9d3026 100644 --- a/src/transactions/FeeBumpTransactionFrame.cpp +++ b/src/transactions/FeeBumpTransactionFrame.cpp @@ -83,39 +83,52 @@ FeeBumpTransactionFrame::FeeBumpTransactionFrame( #endif void -FeeBumpTransactionFrame::preParallelApply( - AppConnector& app, AbstractLedgerTxn& ltx, TransactionMetaBuilder& meta, - MutableTransactionResultBase& txResult, +FeeBumpTransactionFrame::preParallelApplyReadOnly( + AppConnector& app, CheckValidLedgerViewWrapper const& ls, + TransactionMetaBuilder& meta, MutableTransactionResultBase& txResult, SorobanNetworkConfig const& sorobanConfig) const { try { - LedgerTxn ltxTx(ltx); - removeOneTimeSignerKeyFromFeeSource(ltxTx); - meta.pushTxChangesBefore(ltxTx); - ltxTx.commit(); + mInnerTx->preParallelApplyReadOnlyWithOptionallyChargedFee( + /*chargeFee=*/false, app, ls, meta, txResult, sorobanConfig, + getContentsHash()); } catch (std::exception& e) { - printErrorAndAbort("Exception in preParallelApply ", e.what()); + printErrorAndAbort("Exception during read-only preParallelApply: ", + e.what()); } catch (...) { - printErrorAndAbort("Unknown exception in preParallelApply"); + printErrorAndAbort( + "Unknown exception during read-only preParallelApply"); } +} +void +FeeBumpTransactionFrame::preParallelApplyWrite( + AppConnector& app, AbstractLedgerTxn& ltx, TransactionMetaBuilder& meta, + MutableTransactionResultBase const& txResult) const +{ try { - mInnerTx->preParallelApply(/*chargeFee=*/false, app, ltx, meta, - txResult, sorobanConfig, getContentsHash()); + { + LedgerTxn ltxTx(ltx); + removeOneTimeSignerKeyFromFeeSource(ltxTx); + meta.pushTxChangesBefore(ltxTx); + ltxTx.commit(); + } + mInnerTx->preParallelApplyWrite(app, ltx, meta, txResult); } catch (std::exception& e) { - printErrorAndAbort("Exception during preParallelApply: ", e.what()); + printErrorAndAbort("Exception during preParallelApply writes: ", + e.what()); } catch (...) { - printErrorAndAbort("Unknown exception during preParallelApply"); + printErrorAndAbort("Unknown exception during preParallelApply writes"); } } diff --git a/src/transactions/FeeBumpTransactionFrame.h b/src/transactions/FeeBumpTransactionFrame.h index 0d13134229..eccce70180 100644 --- a/src/transactions/FeeBumpTransactionFrame.h +++ b/src/transactions/FeeBumpTransactionFrame.h @@ -90,11 +90,14 @@ class FeeBumpTransactionFrame : public TransactionFrameBase ~FeeBumpTransactionFrame() override = default; - void - preParallelApply(AppConnector& app, AbstractLedgerTxn& ltx, - TransactionMetaBuilder& meta, - MutableTransactionResultBase& txResult, - SorobanNetworkConfig const& sorobanConfig) const override; + void preParallelApplyReadOnly( + AppConnector& app, CheckValidLedgerViewWrapper const& ls, + TransactionMetaBuilder& meta, MutableTransactionResultBase& txResult, + SorobanNetworkConfig const& sorobanConfig) const override; + + void preParallelApplyWrite( + AppConnector& app, AbstractLedgerTxn& ltx, TransactionMetaBuilder& meta, + MutableTransactionResultBase const& txResult) const override; std::optional parallelApply( AppConnector& app, ThreadParallelApplyLedgerState const& threadState, diff --git a/src/transactions/ParallelApplyUtils.cpp b/src/transactions/ParallelApplyUtils.cpp index 618901d499..701dfa2883 100644 --- a/src/transactions/ParallelApplyUtils.cpp +++ b/src/transactions/ParallelApplyUtils.cpp @@ -8,14 +8,18 @@ #include "ledger/LedgerTxn.h" #include "ledger/NetworkConfig.h" #include "main/AppConnector.h" +#include "transactions/OperationFrame.h" #include "transactions/ParallelApplyStage.h" #include "transactions/TransactionFrameBase.h" +#include "transactions/TransactionUtils.h" +#include "util/BatchExecutor.h" #include "util/GlobalChecks.h" #include "xdr/Stellar-ledger-entries.h" #include "xdrpp/printer.h" #include #include #include +#include namespace { @@ -174,8 +178,21 @@ updateMaxOfRoTTLBump(UnorderedMap& roTTLBumps, } } +void +commitPreParallelApplyWrites(AppConnector& app, AbstractLedgerTxn& ltx, + std::vector const& txBundles) +{ + ZoneScoped; + for (auto const* txBundle : txBundles) + { + txBundle->getTx()->preParallelApplyWrite( + app, ltx, txBundle->getEffects().getMeta(), + txBundle->getResPayload()); + } } +} // namespace + namespace stellar { @@ -319,14 +336,13 @@ GlobalParallelApplyLedgerState::GlobalParallelApplyLedgerState( // had their sequence numbers bumped and fees charged. preParallelApply will // update sequence numbers so it needs to be called before we check // LedgerTxn. - preParallelApplyAndCollectModifiedClassicEntries(app, ltx, stages); + preApplyAndCollectModifiedClassicEntries(app, ltx, stages); } void -GlobalParallelApplyLedgerState:: - preParallelApplyAndCollectModifiedClassicEntries( - AppConnector& app, AbstractLedgerTxn& ltx, - std::vector const& stages) +GlobalParallelApplyLedgerState::preApplyAndCollectModifiedClassicEntries( + AppConnector& app, AbstractLedgerTxn& ltx, + std::vector const& stages) { auto fetchInMemoryClassicEntries = [&](xdr::xvector const& keys) { @@ -353,34 +369,99 @@ GlobalParallelApplyLedgerState:: } }; - // First call preParallelApply on all transactions, - // and then load from footprints. This order is important - // because preParallelApply modifies the fee source accounts - // and those accounts could show up in the footprint - // of a different transaction. + std::vector txBundles; for (auto const& stage : stages) { for (auto const& txBundle : stage) { - // Make sure to call preParallelApply on all txs because this will - // modify the fee source accounts sequence numbers. - txBundle.getTx()->preParallelApply( - app, ltx, txBundle.getEffects().getMeta(), - txBundle.getResPayload(), mSorobanConfig); + txBundles.emplace_back(&txBundle); } } - for (auto const& stage : stages) + // Pre-apply all the transactions before loading the footprint entries. This + // order is important because the pre-apply modifies the source accounts, + // and those accounts could show up in the footprint of a transaction + // applied by a different thread, thus breaking the invariant that + // transactions are independent of each other across threads. + // + // The pre-apply process is done in two phases: a parallel read-only phase + // where the transactions are validated, and a serial write phase where the + // writes are committed to the ledger. + // + // This phase separatation hinges on the fact that the validation outcome + // of any Soroban transaction can't be influenced by the pre-apply writes + // performed by another Soroban transaction. Specifically, pre-apply writes + // only include: + // - The source account sequence number bumps - this is fine because we + // have only a single transaction per source account per ledger + // - The removal of one-time pre-authorized tx signers - this is also fine + // because any given transaction in a ledger is unique, and increasing the + // sub-entry count of a source/sponsor account is not relevant at that + // point, as the fees have already been successfully charged. + + auto header = + std::make_shared(ltx.loadHeader().current()); + readOnlyParallelPreApply(app, txBundles, header, ltx); + commitPreParallelApplyWrites(app, ltx, txBundles); + + for (auto const& txBundle : txBundles) { - for (auto const& txBundle : stage) - { - auto const& footprint = - txBundle.getTx()->sorobanResources().footprint; + auto const& footprint = txBundle->getTx()->sorobanResources().footprint; + fetchInMemoryClassicEntries(footprint.readWrite); + fetchInMemoryClassicEntries(footprint.readOnly); + } +} - fetchInMemoryClassicEntries(footprint.readWrite); - fetchInMemoryClassicEntries(footprint.readOnly); +void +GlobalParallelApplyLedgerState::readOnlyParallelPreApply( + AppConnector& app, std::vector const& txBundles, + std::shared_ptr header, AbstractLedgerTxn& ltx) +{ + ZoneScoped; + if (txBundles.empty()) + { + return; + } + + // Run pre-apply for [begin, end) transaction indices. + auto runRange = [&](size_t begin, size_t end) { + // NB: mLCLApplyView is not thread-safe, so we need to copy it into a + // thread-local view. + CheckValidLedgerViewWrapper ledgerView( + std::make_unique(header, ltx, + mLCLApplyView)); + for (size_t i = begin; i < end; ++i) + { + auto const* txBundle = txBundles[i]; + txBundle->getTx()->preParallelApplyReadOnly( + app, ledgerView, txBundle->getEffects().getMeta(), + txBundle->getResPayload(), mSorobanConfig); } + }; + + size_t taskCount = app.getBatchExecutor().preferredTaskCount(); + if (taskCount <= 1) + { + runRange(0, txBundles.size()); + return; + } + + std::vector> tasks; + tasks.reserve(taskCount); + size_t begin = 0; + size_t baseChunk = txBundles.size() / taskCount; + size_t remainder = txBundles.size() % taskCount; + for (size_t i = 0; i < taskCount; ++i) + { + size_t end = begin + baseChunk + (i < remainder ? 1 : 0); + tasks.emplace_back([runRange, begin, end]() { + runRange(begin, end); + return 0; + }); + begin = end; } + releaseAssert(begin == txBundles.size()); + app.getBatchExecutor().executeBatch(std::move(tasks)); } void diff --git a/src/transactions/ParallelApplyUtils.h b/src/transactions/ParallelApplyUtils.h index 7e7fd8b743..62497713c1 100644 --- a/src/transactions/ParallelApplyUtils.h +++ b/src/transactions/ParallelApplyUtils.h @@ -219,10 +219,16 @@ class GlobalParallelApplyLedgerState // after -- as well as written back to the ltx at the phase's end. ParallelApplyEntryMap mGlobalEntryMap; - void preParallelApplyAndCollectModifiedClassicEntries( + void preApplyAndCollectModifiedClassicEntries( AppConnector& app, AbstractLedgerTxn& ltx, std::vector const& stages); + // Runs the read-only pre-apply stage for every bundle. + void readOnlyParallelPreApply(AppConnector& app, + std::vector const& txBundles, + std::shared_ptr header, + AbstractLedgerTxn& ltx); + bool maybeMergeRoTTLBumps(LedgerKey const& key, GlobalParallelApplyEntry const& newEntry, diff --git a/src/transactions/TransactionFrame.cpp b/src/transactions/TransactionFrame.cpp index 6d35934553..b50f7115dd 100644 --- a/src/transactions/TransactionFrame.cpp +++ b/src/transactions/TransactionFrame.cpp @@ -99,6 +99,26 @@ getNumDiskReadEntries(SorobanResources const& resources, return count; } + +// Returns true if the transaction result indicates that the source account +// sequence number should be updated for that transaction. +// There are only a few possible reasons for why we would *not* update the +// sequence number: +// - There is no source account at all at this point (due to another transaction +// in the same ledger deleting the source account) +// - The sequence number is bad (due to another transaction in the same ledger +// performing a sequence bump) +// In any other scenario we should update the sequence number, and its highly +// unlikely that there would be any new reasons in the future. +// Note, that this logic makes sense for the apply step only where the +// transactions are already expected to be valid w.r.t LCL (so that they can +// only be invalidated by the other transactions in the same ledger). +bool +shouldUpdateSeqNumInPreApply(MutableTransactionResultBase const& txResult) +{ + auto code = txResult.getInnermostResultCode(); + return code != txBAD_SEQ && code != txNO_ACCOUNT; +} } // namespace using namespace std; @@ -1545,11 +1565,14 @@ TransactionFrame::processSeqNum(AbstractLedgerTxn& ltx) const bool TransactionFrame::processSignatures( ValidationType cv, SignatureChecker& signatureChecker, - AbstractLedgerTxn& ltxOuter, MutableTransactionResultBase& txResult) const + CheckValidLedgerViewWrapper const& ledgerView, + MutableTransactionResultBase& txResult, + AbstractLedgerTxn* ltxForWrites) const { ZoneScoped; bool maybeValid = (cv == ValidationType::kMaybeValid); - uint32_t ledgerVersion = ltxOuter.loadHeader().current().ledgerVersion; + uint32_t ledgerVersion = + ledgerView.getLedgerHeader().current().ledgerVersion; if (protocolVersionIsBefore(ledgerVersion, ProtocolVersion::V_10)) { return maybeValid; @@ -1559,7 +1582,10 @@ TransactionFrame::processSignatures( if (protocolVersionStartsFrom(ledgerVersion, ProtocolVersion::V_13) && !maybeValid) { - removeOneTimeSignerFromAllSourceAccounts(ltxOuter); + if (ltxForWrites) + { + removeOneTimeSignerFromAllSourceAccounts(*ltxForWrites); + } return false; } // older versions of the protocol only fast fail in a subset of cases @@ -1576,12 +1602,14 @@ TransactionFrame::processSignatures( if (auto code = txResult.getInnermostResultCode(); code == txSUCCESS || code == txFAILED) { - CheckValidLedgerViewWrapper ledgerView(ltxOuter); allOpsValid = checkOperationSignatures(signatureChecker, ledgerView, &txResult); } - removeOneTimeSignerFromAllSourceAccounts(ltxOuter); + if (ltxForWrites) + { + removeOneTimeSignerFromAllSourceAccounts(*ltxForWrites); + } if (!allOpsValid) { @@ -2041,14 +2069,16 @@ maybeTriggerTestInternalError(TransactionEnvelope const& env) std::unique_ptr TransactionFrame::commonPreApply(bool chargeFee, AppConnector& app, - AbstractLedgerTxn& ltx, + CheckValidLedgerViewWrapper const& ledgerView, TransactionMetaBuilder& meta, MutableTransactionResultBase& txResult, SorobanNetworkConfig const* sorobanConfig, - Hash const& envelopeContentsHash) const + Hash const& envelopeContentsHash, + AbstractLedgerTxn* ltxForWrites) const { mCachedAccountPreProtocol8.reset(); - uint32_t ledgerVersion = ltx.loadHeader().current().ledgerVersion; + uint32_t ledgerVersion = + ledgerView.getLedgerHeader().current().ledgerVersion; std::unique_ptr signatureChecker; #ifdef BUILD_TESTS // If the txResult has a replay result (catchup in skip mode is @@ -2087,23 +2117,18 @@ TransactionFrame::commonPreApply(bool chargeFee, AppConnector& app, // Pass in nullopt, we always use the header ledgerSeq in the apply path for // validation. - LedgerTxn ltxTx(ltx); - CheckValidLedgerViewWrapper lsTx(ltxTx); auto cv = - commonValid(app, sorobanConfig, *signatureChecker, lsTx, 0, true, + commonValid(app, sorobanConfig, *signatureChecker, ledgerView, 0, true, chargeFee, 0, 0, envelopeContentsHash, sorobanResourceFee, txResult, meta.getDiagnosticEventManager(), /*validationLedgerSeq=*/std::nullopt); - if (cv >= ValidationType::kInvalidUpdateSeqNum) + if (ltxForWrites && cv >= ValidationType::kInvalidUpdateSeqNum) { - processSeqNum(ltxTx); + processSeqNum(*ltxForWrites); } - bool signaturesValid = - processSignatures(cv, *signatureChecker, ltxTx, txResult); - - meta.pushTxChangesBefore(ltxTx); - ltxTx.commit(); + bool signaturesValid = processSignatures(cv, *signatureChecker, ledgerView, + txResult, ltxForWrites); if (signaturesValid && cv == ValidationType::kMaybeValid) { @@ -2116,67 +2141,92 @@ TransactionFrame::commonPreApply(bool chargeFee, AppConnector& app, } void -TransactionFrame::preParallelApply( - AppConnector& app, AbstractLedgerTxn& ltx, TransactionMetaBuilder& meta, - MutableTransactionResultBase& resPayload, +TransactionFrame::preParallelApplyReadOnly( + AppConnector& app, CheckValidLedgerViewWrapper const& ls, + TransactionMetaBuilder& meta, MutableTransactionResultBase& txResult, SorobanNetworkConfig const& sorobanConfig) const { - preParallelApply(true, app, ltx, meta, resPayload, sorobanConfig, - getContentsHash()); + try + { + preParallelApplyReadOnlyWithOptionallyChargedFee( + /*chargeFee=*/true, app, ls, meta, txResult, sorobanConfig, + getContentsHash()); + } + catch (std::exception& e) + { + printErrorAndAbort("Exception during read-only preParallelApply: ", + e.what()); + } + catch (...) + { + printErrorAndAbort( + "Unknown exception during read-only preParallelApply"); + } } void -TransactionFrame::preParallelApply(bool chargeFee, AppConnector& app, - AbstractLedgerTxn& ltx, - TransactionMetaBuilder& meta, - MutableTransactionResultBase& txResult, - SorobanNetworkConfig const& sorobanConfig, - Hash const& envelopeContentsHash) const +TransactionFrame::preParallelApplyReadOnlyWithOptionallyChargedFee( + bool chargeFee, AppConnector& app, + CheckValidLedgerViewWrapper const& ledgerView, TransactionMetaBuilder& meta, + MutableTransactionResultBase& txResult, + SorobanNetworkConfig const& sorobanConfig, + Hash const& envelopeContentsHash) const { ZoneScoped; - releaseAssert(threadIsMain() || - app.threadIsType(Application::ThreadType::APPLY)); - try + + releaseAssertOrThrow(isSoroban()); + + auto signatureChecker = + commonPreApply(chargeFee, app, ledgerView, meta, txResult, + &sorobanConfig, envelopeContentsHash, + /*ltxForWrites=*/nullptr); + bool ok = signatureChecker != nullptr; + if (ok) { - releaseAssertOrThrow(isSoroban()); + updateSorobanMetrics(app); - auto signatureChecker = - commonPreApply(chargeFee, app, ltx, meta, txResult, &sorobanConfig, - envelopeContentsHash); - bool ok = signatureChecker != nullptr; - if (ok) + auto& opResult = txResult.getOpResultAt(0); + ok = mOperations.front()->checkValid( + app, *signatureChecker, &sorobanConfig, ledgerView, true, opResult, + meta.getDiagnosticEventManager()); + if (!ok) { - updateSorobanMetrics(app); + txResult.setInnermostError(txFAILED); + } + } - auto& opResult = txResult.getOpResultAt(0); + // If validation fails, we check the result code in the parallel + // step to make sure we don't apply the transaction. + releaseAssertOrThrow(ok == txResult.isSuccess()); +} - // Pre parallel soroban, OperationFrame::checkValid is called - // right before OperationFrame::doApply, but we do it here - // instead to avoid making OperationFrame::checkValid thread - // safe. - ok = mOperations.front()->checkValid( - app, *signatureChecker, &sorobanConfig, ltx, true, opResult, - meta.getDiagnosticEventManager()); - if (!ok) - { - txResult.setInnermostError(txFAILED); - } +void +TransactionFrame::preParallelApplyWrite( + AppConnector& app, AbstractLedgerTxn& ltx, TransactionMetaBuilder& meta, + MutableTransactionResultBase const& txResult) const +{ + ZoneScoped; + releaseAssert(threadIsMain() || + app.threadIsType(Application::ThreadType::APPLY)); + try + { + LedgerTxn ltxTx(ltx); + if (shouldUpdateSeqNumInPreApply(txResult)) + { + processSeqNum(ltxTx); } - - // If validation fails, we check the result code in the parallel - // step to make sure we don't apply the transaction. - releaseAssertOrThrow(ok == txResult.isSuccess()); + removeOneTimeSignerFromAllSourceAccounts(ltxTx); + meta.pushTxChangesBefore(ltxTx); + ltxTx.commit(); } catch (std::exception& e) { - printErrorAndAbort("Exception after processing fees but before " - "processing sequence number: ", + printErrorAndAbort("Exception during preParallelApply writes: ", e.what()); } catch (...) { - printErrorAndAbort("Unknown exception after processing fees but before " - "processing sequence number"); + printErrorAndAbort("Unknown exception during preParallelApply writes"); } } @@ -2497,10 +2547,17 @@ TransactionFrame::apply( ZoneScoped; try { - auto signatureChecker = - commonPreApply(chargeFee, app, ltx, meta, txResult, - sorobanConfig ? &sorobanConfig.value() : nullptr, - envelopeContentsHash); + auto signatureChecker = [&] { + LedgerTxn ltxTx(ltx); + CheckValidLedgerViewWrapper lsTx(ltxTx); + auto checker = + commonPreApply(chargeFee, app, lsTx, meta, txResult, + sorobanConfig ? &sorobanConfig.value() : nullptr, + envelopeContentsHash, <xTx); + meta.pushTxChangesBefore(ltxTx); + ltxTx.commit(); + return checker; + }(); bool ok = signatureChecker != nullptr; try { diff --git a/src/transactions/TransactionFrame.h b/src/transactions/TransactionFrame.h index f46716a8dd..f0d37a631d 100644 --- a/src/transactions/TransactionFrame.h +++ b/src/transactions/TransactionFrame.h @@ -147,10 +147,15 @@ class TransactionFrame : public TransactionFrameBase void processSeqNum(AbstractLedgerTxn& ltx) const; + // Processes the transaction signatures and returns `true` on success. + // If `ltxForWrites` is nullptr, this function will be read-only and the + // caller is expected to defer any writes until after the + // `processSignatures` call. bool processSignatures(ValidationType cv, SignatureChecker& signatureChecker, - AbstractLedgerTxn& ltxOuter, - MutableTransactionResultBase& txResult) const; + CheckValidLedgerViewWrapper const& ledgerView, + MutableTransactionResultBase& txResult, + AbstractLedgerTxn* ltxForWrites) const; std::optional const getTimeBounds() const; std::optional const getLedgerBounds() const; @@ -290,36 +295,46 @@ class TransactionFrame : public TransactionFrameBase processFeeSeqNum(AbstractLedgerTxn& ltx, std::optional baseFee) const override; - // preApply runs all pre-application steps that are common between + // `commonPreApply` runs all pre-application steps that are common between // parallelApply and (sequential) apply: // // - building a signature checker // - calling commonValid - // - calling processSeqNum - // - calling processSignatures + // - (if writes are allowed) calling processSeqNum + // - calling processSignatures (in RO or RW mode) // - // If all of this succeeds it returns a non-nullptr pointer to the + // If `ltxForWrites` is nullptr, then this function becomes read-only and + // the caller is expected to defer any writes until after the + // `commonPreApply` call. + // + // If all of this succeeds, it returns a non-nullptr pointer to the // signature checker, to be used elsewhere in the txn. If anything // fails it returns nullptr. It does all of its work in a sub-ltx - // so the passed ltx is unchanged on failure. + // so the passed `ltxForWrites` is unchanged on failure. std::unique_ptr - commonPreApply(bool chargeFee, AppConnector& app, AbstractLedgerTxn& ltx, + commonPreApply(bool chargeFee, AppConnector& app, + CheckValidLedgerViewWrapper const& ledgerView, TransactionMetaBuilder& meta, MutableTransactionResultBase& txResult, SorobanNetworkConfig const* sorobanConfig, - Hash const& envelopeContentsHash) const; + Hash const& envelopeContentsHash, + AbstractLedgerTxn* ltxForWrites) const; - void preParallelApply(bool chargeFee, AppConnector& app, - AbstractLedgerTxn& ltx, TransactionMetaBuilder& meta, - MutableTransactionResultBase& txResult, - SorobanNetworkConfig const& sorobanConfig, - Hash const& envelopeContentsHash) const; + void preParallelApplyReadOnlyWithOptionallyChargedFee( + bool chargeFee, AppConnector& app, + CheckValidLedgerViewWrapper const& ls, TransactionMetaBuilder& meta, + MutableTransactionResultBase& txResult, + SorobanNetworkConfig const& sorobanConfig, + Hash const& envelopeContentsHash) const; - void - preParallelApply(AppConnector& app, AbstractLedgerTxn& ltx, - TransactionMetaBuilder& meta, - MutableTransactionResultBase& txResult, - SorobanNetworkConfig const& sorobanConfig) const override; + void preParallelApplyReadOnly( + AppConnector& app, CheckValidLedgerViewWrapper const& ls, + TransactionMetaBuilder& meta, MutableTransactionResultBase& txResult, + SorobanNetworkConfig const& sorobanConfig) const override; + + void preParallelApplyWrite( + AppConnector& app, AbstractLedgerTxn& ltx, TransactionMetaBuilder& meta, + MutableTransactionResultBase const& txResult) const override; std::optional parallelApply( AppConnector& app, ThreadParallelApplyLedgerState const& threadState, diff --git a/src/transactions/TransactionFrameBase.h b/src/transactions/TransactionFrameBase.h index 0e45aa97ea..3d2a84dc01 100644 --- a/src/transactions/TransactionFrameBase.h +++ b/src/transactions/TransactionFrameBase.h @@ -156,11 +156,20 @@ class TransactionFrameBase std::optional const& sorobanConfig, Hash const& sorobanBasePrngSeed) const = 0; - virtual void - preParallelApply(AppConnector& app, AbstractLedgerTxn& ltx, - TransactionMetaBuilder& meta, - MutableTransactionResultBase& txResult, - SorobanNetworkConfig const& sorobanConfig) const = 0; + // The read-only half of the Soroban pre-apply: validation, signature checks + // and the operation's checkValid. Performs no writes. Safe to run + // concurrently for distinct transactions, provided `ls` supports concurrent + // reads. + virtual void preParallelApplyReadOnly( + AppConnector& app, CheckValidLedgerViewWrapper const& ls, + TransactionMetaBuilder& meta, MutableTransactionResultBase& txResult, + SorobanNetworkConfig const& sorobanConfig) const = 0; + + // The write half of the Soroban pre-apply. Has to run on the thread that + // owns `ltx`, serially across transactions, in canonical transaction order. + virtual void preParallelApplyWrite( + AppConnector& app, AbstractLedgerTxn& ltx, TransactionMetaBuilder& meta, + MutableTransactionResultBase const& txResult) const = 0; // If the transaction fails during parallel apply, returns std::nullopt. // Otherwise returns a ParallelTxSuccessVal containing the modified entries diff --git a/src/transactions/test/InvokeHostFunctionTests.cpp b/src/transactions/test/InvokeHostFunctionTests.cpp index 18cfe946f2..41205b1e59 100644 --- a/src/transactions/test/InvokeHostFunctionTests.cpp +++ b/src/transactions/test/InvokeHostFunctionTests.cpp @@ -38,6 +38,7 @@ #include "test/TxTests.h" #include "transactions/InvokeHostFunctionOpFrame.h" #include "transactions/SignatureUtils.h" +#include "transactions/SponsorshipUtils.h" #include "transactions/TransactionUtils.h" #include "transactions/test/SorobanTxTestUtils.h" #include "transactions/test/SponsorshipTestUtils.h" @@ -73,9 +74,24 @@ checkResults(TransactionResultSet& r, int expectedSuccess, int expectedFailed) } REQUIRE(successCounter == expectedSuccess); - REQUIRE(expectedFailed == expectedFailed); + REQUIRE(failedCounter == expectedFailed); }; +// closeLedger applies transactions in the transaction set's own order, so +// results have to be matched back to transactions by hash. +TransactionResult const& +resultFor(TransactionResultSet const& resultSet, + TransactionFrameBasePtr const& tx) +{ + auto it = + std::find_if(resultSet.results.begin(), resultSet.results.end(), + [&tx](TransactionResultPair const& pair) { + return pair.transactionHash == tx->getContentsHash(); + }); + REQUIRE(it != resultSet.results.end()); + return it->result; +} + uint32_t getParallelSorobanTestProtocolVersion() { @@ -7891,10 +7907,9 @@ TEST_CASE("Module cache miss on immediate execution", auto invokeFailTx = makeAddTx(contract, INVOKE_ADD_UNCACHED_COST_FAIL, C); - // Transaction 4: invocation (with inadequate instructions to - // succeed) + // Transaction 4: invocation (with adequate instructions to succeed). auto invokePassTx = - makeAddTx(contract, INVOKE_ADD_UNCACHED_COST_PASS, C); + makeAddTx(contract, INVOKE_ADD_UNCACHED_COST_PASS, D); // Run single ledger with all 4 txs. First 2 should pass, 3rd should // fail, 4th should pass. @@ -11171,3 +11186,428 @@ TEST_CASE("create and invoke external ref contract", "[tx][soroban]") REQUIRE(invocation.invoke()); REQUIRE(invocation.getReturnValue().i32() == 7); } + +TEST_CASE_VERSIONS("Soroban pre-apply removes pre-auth tx signers", + "[tx][soroban][preapply]") +{ + VirtualClock clock; + auto app = createTestApplication(clock, getTestConfig()); + + for_versions_from(20, *app, [&] { + // Number of transactions that all use the same op source with the + // respective pre-authorized transaction signers. + int const SHARED_SIGNER_COUNT = 20; + // Number of transactions with its own source account pre-authorizing + // the transaction. + int const OWN_SIGNER_COUNT = 10; + + SorobanTest test(app); + modifySorobanNetworkConfig(*app, [](SorobanNetworkConfig& cfg) { + cfg.mStateArchivalSettings.minPersistentTTL = 1000; + }); + auto& root = test.getRoot(); + + auto addPreAuthTxSigner = [&](Application& app, TestAccount& sponsor, + TestAccount& account, Hash const& txHash, + bool sponsored = false) { + SignerKey signer(SIGNER_KEY_TYPE_PRE_AUTH_TX); + signer.preAuthTx() = txHash; + auto signerOp = setOptions(setSigner(Signer{signer, 1})); + signerOp.sourceAccount.activate() = toMuxedAccount(account); + + std::vector ops; + if (sponsored) + { + ops.push_back( + beginSponsoringFutureReserves(account.getPublicKey())); + ops.push_back(signerOp); + auto endOp = endSponsoringFutureReserves(); + endOp.sourceAccount.activate() = toMuxedAccount(account); + ops.push_back(endOp); + } + else + { + ops.push_back(signerOp); + } + + auto signerTx = sponsor.tx(ops); + signerTx->addSignature(account.getSecretKey()); + + auto resultSet = closeLedger(app, {signerTx}); + REQUIRE(isSuccessResult(resultSet.results.front().result)); + + if (sponsored) + { + auto ledgerView = + app.getLedgerManager().copyImmutableLedgerView(); + auto accountEntry = + ledgerView.load(accountKey(account.getPublicKey())); + REQUIRE(getNumSponsored(accountEntry.current()) == 1); + auto sponsorEntry = + ledgerView.load(accountKey(sponsor.getPublicKey())); + REQUIRE(getNumSponsoring(sponsorEntry.current()) == 1); + } + }; + + int64_t const startingBalance = + app->getLedgerManager().getLastMinBalance(50); + + auto sponsor = test.getRoot().create("sponsor", startingBalance); + // Account holding a pre-auth signer for each of the shared-signer + // transactions below. + auto sharedSigner = + test.getRoot().create("sharedSigner", startingBalance); + auto feeBumper = test.getRoot().create("feeBumper", startingBalance); + + std::vector txSources; + for (int i = 0; i < SHARED_SIGNER_COUNT + OWN_SIGNER_COUNT; ++i) + { + txSources.push_back( + root.create(fmt::format("txSource{}", i), startingBalance)); + } + + auto& contract = + test.deployWasmContract(rust_bridge::get_test_wasm_add_i32()); + + auto spec = SorobanInvocationSpec() + .setInstructions(2'000'000) + .setReadBytes(10'000) + .setInclusionFee(1000) + .setNonRefundableResourceFee(100'000) + .setRefundableResourceFee(200'000); + + std::vector txs; + + // Create transactions to pre-authorize by the sharedSigner account. + std::vector sharedSignerTxHashes; + for (int i = 0; i < SHARED_SIGNER_COUNT; ++i) + { + auto tx = + contract + .prepareInvocation("add", {makeI32(1), makeI32(2)}, spec) + .withOpSourceAccount(sharedSigner.getPublicKey()) + .createTx(&txSources[i]); + sharedSignerTxHashes.push_back(tx->getContentsHash()); + txs.push_back(tx); + } + + // Add the pre-auth signers to the sharedSigner account. + std::vector signerOps; + int sponsoredCount = 0; + for (int i = 0; i < SHARED_SIGNER_COUNT; ++i) + { + SignerKey signer(SIGNER_KEY_TYPE_PRE_AUTH_TX); + signer.preAuthTx() = sharedSignerTxHashes[i]; + auto signerOp = setOptions(setSigner(Signer{signer, 1})); + signerOp.sourceAccount.activate() = toMuxedAccount(sharedSigner); + // Make half of the signers sponsored. + if (i % 2 == 0) + { + ++sponsoredCount; + signerOps.push_back( + beginSponsoringFutureReserves(sharedSigner.getPublicKey())); + signerOps.push_back(signerOp); + auto endOp = endSponsoringFutureReserves(); + endOp.sourceAccount.activate() = toMuxedAccount(sharedSigner); + signerOps.push_back(endOp); + } + else + { + signerOps.push_back(signerOp); + } + } + auto signerTx = sponsor.tx(signerOps); + signerTx->addSignature(sharedSigner.getSecretKey()); + REQUIRE(isSuccessResult( + closeLedger(*app, {signerTx}).results.front().result)); + { + auto ledgerView = app->getLedgerManager().copyImmutableLedgerView(); + auto entry = + ledgerView.load(accountKey(sharedSigner.getPublicKey())); + REQUIRE(entry.current().data.account().signers.size() == + SHARED_SIGNER_COUNT); + REQUIRE(getNumSponsored(entry.current()) == sponsoredCount); + auto sponsorEntry = + ledgerView.load(accountKey(sponsor.getPublicKey())); + REQUIRE(getNumSponsoring(sponsorEntry.current()) == sponsoredCount); + } + + // Create transactions and accounts that are pre-authorized at the + // tx source level. + std::vector ownSponsors; + for (int i = 0; i < OWN_SIGNER_COUNT; ++i) + { + auto& source = txSources[SHARED_SIGNER_COUNT + i]; + TransactionFrameBasePtr tx = + contract + .prepareInvocation("add", {makeI32(1), makeI32(2)}, spec) + .createTx(&source); + auto txEnvelope = tx->getEnvelope(); + // No need for the actual signatures, as we're using pre-auth tx + // signer. + txEnvelope.v1().signatures.clear(); + tx = TransactionFrameBase::makeTransactionFromWire( + app->getNetworkID(), txEnvelope); + ownSponsors.push_back( + root.create(fmt::format("ownSponsor{}", i), startingBalance)); + // Make some of the signers sponsored. + addPreAuthTxSigner(*app, ownSponsors.back(), source, + tx->getContentsHash(), + /*sponsored=*/i % 2 == 0); + // Make some of transactions fee-bumped. + if (i % 2 == 1) + { + tx = feeBump(*app, feeBumper, tx, 10000); + } + txs.push_back(tx); + } + + auto r = closeLedger(*app, txs); + REQUIRE(r.results.size() == txs.size()); + for (auto const& tx : txs) + { + REQUIRE(isSuccessResult(resultFor(r, tx))); + } + + auto ledgerView = app->getLedgerManager().copyImmutableLedgerView(); + // Every one-time signer is gone for the sharedSigner, and all the + // sponsorships are removed. + auto sharedEntry = + ledgerView.load(accountKey(sharedSigner.getPublicKey())); + REQUIRE(sharedEntry.current().data.account().signers.empty()); + REQUIRE(getNumSponsored(sharedEntry.current()) == 0); + auto sponsorEntry = ledgerView.load(accountKey(sponsor.getPublicKey())); + REQUIRE(getNumSponsoring(sponsorEntry.current()) == 0); + + // Every one-time signer is gone the tx sources, and all the + // sponsorships are removed. + for (int i = 0; i < txs.size(); ++i) + { + auto& source = txSources[i]; + auto entry = ledgerView.load(accountKey(source.getPublicKey())); + REQUIRE(entry.current().data.account().signers.empty()); + REQUIRE(getNumSponsored(entry.current()) == 0); + + if (i >= SHARED_SIGNER_COUNT) + { + auto ownSponsorEntry = ledgerView.load(accountKey( + ownSponsors[i - SHARED_SIGNER_COUNT].getPublicKey())); + REQUIRE(getNumSponsoring(ownSponsorEntry.current()) == 0); + } + } + }); +} + +TEST_CASE_VERSIONS( + "Soroban operation source created and removed in classic phase", + "[tx][soroban][preapply]") +{ + VirtualClock clock; + auto app = createTestApplication(clock, getTestConfig()); + + for_versions_from(20, *app, [&] { + // Number of transactions that create and remove accounts in the classic + // phase (and then are used as operation sources). + int const TX_COUNT_PER_ACCOUNT_CHANGE = 20; + + SorobanTest test(app); + auto makeOpSourceInvocation = [&](TestContract& contract, + TestAccount& txSource, + AccountID const& opSource, + SecretKey const& opKey) { + auto spec = SorobanInvocationSpec() + .setInstructions(1'000'000) + .setReadBytes(10'000) + .setInclusionFee(1000) + .setNonRefundableResourceFee(100'000) + .setRefundableResourceFee(200'000); + auto tx = + contract + .prepareInvocation("add", {makeI32(1), makeI32(2)}, spec) + .withOpSourceAccount(opSource) + .createTx(&txSource); + tx->addSignature(opKey); + return tx; + }; + + auto& root = test.getRoot(); + auto accountBalance = app->getLedgerManager().getLastMinBalance(2); + + auto sorobanTxCount = 3 * TX_COUNT_PER_ACCOUNT_CHANGE; + + // Setup all the accounts/keys necessary: accounts that create new + // accounts, accounts that keys of the created accounts, and accounts + // that are merged. + std::vector creators; + std::vector createdAccountKeys; + std::vector mergedAccounts; + for (int i = 0; i < TX_COUNT_PER_ACCOUNT_CHANGE; ++i) + { + creators.push_back( + root.create(fmt::format("creator{}", i), accountBalance * 100)); + mergedAccounts.push_back( + root.create(fmt::format("merged{}", i), accountBalance)); + createdAccountKeys.push_back( + getAccount(fmt::format("created{}", i))); + } + // Setup the source account for Soroban txs (nothing interesting happens + // to these). + std::vector sorobanSources; + for (int i = 0; i < sorobanTxCount; ++i) + { + sorobanSources.push_back( + root.create(fmt::format("sorobanSrc{}", i), accountBalance)); + } + + auto& contract = + test.deployWasmContract(rust_bridge::get_test_wasm_add_i32()); + + std::vector txs; + // Create classic txs: account creations and merges. + for (int i = 0; i < TX_COUNT_PER_ACCOUNT_CHANGE; ++i) + { + txs.push_back(creators[i].tx({createAccount( + createdAccountKeys[i].getPublicKey(), accountBalance)})); + txs.push_back( + mergedAccounts[i].tx({accountMerge(root.getPublicKey())})); + } + auto classicTxCount = txs.size(); + + for (int i = 0; i < TX_COUNT_PER_ACCOUNT_CHANGE; ++i) + { + // Tx that uses a created account as the operation source. + txs.push_back(makeOpSourceInvocation( + contract, sorobanSources[i], + createdAccountKeys[i].getPublicKey(), createdAccountKeys[i])); + // Tx that uses a merged account as the operation source (which + // should fail). + txs.push_back(makeOpSourceInvocation( + contract, sorobanSources[TX_COUNT_PER_ACCOUNT_CHANGE + i], + mergedAccounts[i].getPublicKey(), + mergedAccounts[i].getSecretKey())); + // 'Baseline' tx that just uses an existing account as the operation + // source (but the source account was also used in the classic + // phase). + txs.push_back(makeOpSourceInvocation( + contract, sorobanSources[2 * TX_COUNT_PER_ACCOUNT_CHANGE + i], + creators[i].getPublicKey(), creators[i].getSecretKey())); + } + + auto r = closeLedger(*app, txs); + REQUIRE(r.results.size() == txs.size()); + + for (int i = 0; i < classicTxCount; ++i) + { + INFO("classic tx " << i); + REQUIRE(isSuccessResult(resultFor(r, txs[i]))); + } + for (int i = 0; i < TX_COUNT_PER_ACCOUNT_CHANGE; ++i) + { + INFO("created account " << i); + REQUIRE(isSuccessResult(resultFor(r, txs[classicTxCount + 3 * i]))); + INFO("merged account " << i); + auto const& accountMergedRes = + resultFor(r, txs[classicTxCount + 3 * i + 1]); + REQUIRE(accountMergedRes.result.code() == txFAILED); + REQUIRE(accountMergedRes.result.results()[0].code() == + opNO_ACCOUNT); + INFO("existing account " << i); + REQUIRE( + isSuccessResult(resultFor(r, txs[classicTxCount + 3 * i + 2]))); + } + }); +} + +TEST_CASE_VERSIONS("classic phase bumps sequence of Soroban tx source account", + "[tx][soroban][preapply]") +{ + VirtualClock clock; + auto app = createTestApplication(clock, getTestConfig()); + + for_versions_from(20, *app, [&] { + int const BUMPED_ACCOUNT_COUNT = 20; + int const NORMAL_ACCOUNT_COUNT = 10; + + SorobanTest test(app); + auto& root = test.getRoot(); + auto accountBalance = app->getLedgerManager().getLastMinBalance(10); + + std::vector bumpSources; + std::vector bumpedAccounts; + for (int i = 0; i < BUMPED_ACCOUNT_COUNT; ++i) + { + bumpSources.push_back( + root.create(fmt::format("bumpsrc{}", i), accountBalance)); + bumpedAccounts.push_back( + root.create(fmt::format("bumped{}", i), accountBalance)); + } + std::vector normalAccounts; + for (int i = 0; i < NORMAL_ACCOUNT_COUNT; ++i) + { + normalAccounts.push_back( + root.create(fmt::format("normal{}", i), accountBalance)); + } + + auto& contract = + test.deployWasmContract(rust_bridge::get_test_wasm_add_i32()); + + auto makeInvocation = [&](TestAccount& source) { + auto spec = SorobanInvocationSpec() + .setInstructions(1'000'000) + .setReadBytes(10'000) + .setInclusionFee(1000) + .setNonRefundableResourceFee(100'000) + .setRefundableResourceFee(200'000); + return contract + .prepareInvocation("add", {makeI32(1), makeI32(2)}, spec) + .createTx(&source); + }; + + std::vector txs; + // Build the Soroban transactions first, so that they capture the + // pre-bump sequence numbers. + std::vector sorobanTxs; + for (int i = 0; i < BUMPED_ACCOUNT_COUNT; ++i) + { + txs.push_back(makeInvocation(bumpedAccounts[i])); + } + for (int i = 0; i < NORMAL_ACCOUNT_COUNT; ++i) + { + txs.push_back(makeInvocation(normalAccounts[i])); + } + auto sorobanTxCount = txs.size(); + + // Build the classic sequence bump transactions now, which will bump the + // sequence numbers of the bumpedAccounts via operation source accounts. + for (int i = 0; i < BUMPED_ACCOUNT_COUNT; ++i) + { + auto bumpOp = + bumpSequence(bumpedAccounts[i].getLastSequenceNumber() + 1000); + bumpOp.sourceAccount.activate() = + toMuxedAccount(bumpedAccounts[i].getPublicKey()); + auto bumpTx = bumpSources[i].tx({bumpOp}); + bumpTx->addSignature(bumpedAccounts[i].getSecretKey()); + txs.push_back(bumpTx); + } + + auto r = closeLedger(*app, txs); + REQUIRE(r.results.size() == txs.size()); + + for (size_t i = 0; i < BUMPED_ACCOUNT_COUNT; ++i) + { + INFO("classic tx " << i); + REQUIRE(isSuccessResult(resultFor(r, txs[sorobanTxCount + i]))); + } + for (int i = 0; i < BUMPED_ACCOUNT_COUNT; ++i) + { + INFO("bumped account " << i); + REQUIRE(resultFor(r, txs[i]).result.code() == txBAD_SEQ); + } + for (int i = 0; i < NORMAL_ACCOUNT_COUNT; ++i) + { + INFO("normal account " << i); + REQUIRE( + isSuccessResult(resultFor(r, txs[BUMPED_ACCOUNT_COUNT + i]))); + } + }); +} diff --git a/src/transactions/test/ParallelApplyTest.cpp b/src/transactions/test/ParallelApplyTest.cpp index 8c9a212cf6..c1350fe7b6 100644 --- a/src/transactions/test/ParallelApplyTest.cpp +++ b/src/transactions/test/ParallelApplyTest.cpp @@ -17,6 +17,7 @@ #include "transactions/OperationFrame.h" #include "transactions/TransactionFrameBase.h" #include "transactions/test/SorobanTxTestUtils.h" +#include "util/BatchExecutor.h" #include "util/UnorderedSet.h" using namespace stellar; @@ -1313,4 +1314,307 @@ TEST_CASE("parallel soroban application results are independent of transaction " runTestCase(testConfig); } +struct PreApplyScenarioResult +{ + TransactionResultSet mResults; + LedgerCloseMeta mMeta; + std::vector>> mEntries; +}; + +// Builds and applies a single ledger with classic phase that mutates a pool of +// accounts (creating, merging, sequence bumping and paying them) and with a +// Soroban phase then uses those same accounts as transaction sources, +// operation sources, and native SAC transfer destinations. +// +// `multiplier` defines the size of the test scenario, and `preApplyTaskCount` +// defines the number of worker threads that the pre-apply phase should use. +// +// The scenario is parametrized by the `seed` and `multiplier`, and it's +// expected that that no matter the `preApplyTaskCount`, the final ledger state +// is identical for the same seed. +PreApplyScenarioResult +runPreApplyScenario(int64_t seed, int multiplier, size_t preApplyTaskCount) +{ + // Number of accounts with the respective classic phase treatment. + int const MERGED_COUNT = multiplier; + int const BUMPED_COUNT = multiplier; + int const CREATED_COUNT = multiplier; + int const NORMAL_COUNT = 2 * multiplier; + + // Number of clusters to use during the apply phase itself - setting it to + // non-1 just to make coverage more realistic. + int const APPLY_CLUSTER_COUNT = 4; + + std::mt19937 rng(seed); + + Config cfg = getTestConfig(); + cfg.LEDGER_PROTOCOL_VERSION = Config::CURRENT_LEDGER_PROTOCOL_VERSION; + cfg.TESTING_UPGRADE_LEDGER_PROTOCOL_VERSION = + Config::CURRENT_LEDGER_PROTOCOL_VERSION; + cfg.TESTING_UPGRADE_MAX_TX_SET_SIZE = multiplier * 3; + + SorobanTest test(cfg, true, [&](SorobanNetworkConfig& sorobanCfg) { + sorobanCfg.mLedgerMaxDependentTxClusters = APPLY_CLUSTER_COUNT; + sorobanCfg.mLedgerMaxTxCount = multiplier * 5; + }); + test.getApp().getBatchExecutor().setPreferredTaskCountForTesting( + preApplyTaskCount); + + auto& root = test.getRoot(); + int64_t accountBalance = + test.getApp().getLedgerManager().getLastMinBalance(20); + + int sorobanTxCount = MERGED_COUNT + BUMPED_COUNT + NORMAL_COUNT; + + std::vector classicSources; + std::vector mergedAccounts; + std::vector bumpedAccounts; + std::vector normalAccounts; + std::vector createdKeys; + for (int i = 0; i < MERGED_COUNT + BUMPED_COUNT + CREATED_COUNT; ++i) + { + classicSources.push_back(root.create(fmt::format("classicSource{}", i), + accountBalance * 10)); + } + for (int i = 0; i < MERGED_COUNT; ++i) + { + mergedAccounts.push_back( + root.create(fmt::format("merged{}", i), accountBalance)); + } + for (int i = 0; i < BUMPED_COUNT; ++i) + { + bumpedAccounts.push_back( + root.create(fmt::format("bumped{}", i), accountBalance)); + } + for (int i = 0; i < NORMAL_COUNT; ++i) + { + normalAccounts.push_back( + root.create(fmt::format("normal{}", i), accountBalance)); + } + for (int i = 0; i < CREATED_COUNT; ++i) + { + createdKeys.push_back(getAccount(fmt::format("created{}", i))); + } + auto feeBumper = root.create("feeBumper", accountBalance * 1000); + + AssetContractTestClient sac(test, txtest::makeNativeAsset()); + + // Destinations for the Soroban transfers: the accounts that the classic + // phase modifies as classic transaction sources, plus the accounts it + // creates. Each destination is used at most once. + std::vector destinations; + for (auto const& acc : classicSources) + { + destinations.push_back(acc.getPublicKey()); + } + for (auto const& key : createdKeys) + { + destinations.push_back(key.getPublicKey()); + } + stellar::shuffle(destinations.begin(), destinations.end(), rng); + REQUIRE(destinations.size() >= static_cast(sorobanTxCount)); + + // Classic phase setup + std::vector txs; + for (int i = 0; i < MERGED_COUNT; ++i) + { + // Merge via an operation source so that the merged account itself + // stays free to source a Soroban transaction. + auto mergeOp = accountMerge(root.getPublicKey()); + mergeOp.sourceAccount.activate() = + toMuxedAccount(mergedAccounts[i].getPublicKey()); + auto tx = classicSources[i].tx({mergeOp}); + tx->addSignature(mergedAccounts[i].getSecretKey()); + txs.push_back(tx); + } + for (int i = 0; i < BUMPED_COUNT; ++i) + { + auto bumpOp = + bumpSequence(bumpedAccounts[i].getLastSequenceNumber() + 1000); + bumpOp.sourceAccount.activate() = + toMuxedAccount(bumpedAccounts[i].getPublicKey()); + auto tx = classicSources[MERGED_COUNT + i].tx({bumpOp}); + tx->addSignature(bumpedAccounts[i].getSecretKey()); + txs.push_back(tx); + } + for (int i = 0; i < CREATED_COUNT; ++i) + { + txs.push_back(classicSources[MERGED_COUNT + BUMPED_COUNT + i].tx( + {createAccount(createdKeys[i].getPublicKey(), accountBalance)})); + } + auto classicTxCount = txs.size(); + + // Soroban phase: native SAC transfers out of every account category. + std::vector sorobanSources; + for (auto& acc : mergedAccounts) + { + sorobanSources.push_back(&acc); + } + for (auto& acc : bumpedAccounts) + { + sorobanSources.push_back(&acc); + } + for (auto& acc : normalAccounts) + { + sorobanSources.push_back(&acc); + } + + // Manually validate that the footprints of the Soroban transactions + // are disjoint: these only mutate the SAC transfer source and destination, + // and thus it's sufficient to check that the source and destination + // account sets are disjoint (every source and destination is used just + // once per transaction). + { + UnorderedSet sourceIDs; + for (auto const* acc : sorobanSources) + { + sourceIDs.insert(acc->getPublicKey()); + } + for (auto const& dest : destinations) + { + REQUIRE(sourceIDs.count(dest) == 0); + } + } + + stellar::uniform_int_distribution feeBumpDist(0, 3); + stellar::uniform_int_distribution amountDist(1, 10000); + for (int i = 0; i < sorobanTxCount; ++i) + { + TransactionFrameBasePtr tx = sac.getTransferTx( + *sorobanSources[i], makeAccountAddress(destinations[i]), + amountDist(rng)); + // Fee bump some transactions that we expect to succeed. + if (i >= MERGED_COUNT + BUMPED_COUNT && feeBumpDist(rng) == 0) + { + tx = feeBump(test.getApp(), feeBumper, tx, 100'000); + } + txs.push_back(tx); + } + + // Validate every transaction manually, as we skip validation due to fixed + // Soroban apply order. + { + CheckValidLedgerViewWrapper ledgerView(test.getApp()); + auto diag = DiagnosticEventManager::createDisabled(); + for (auto const& tx : txs) + { + REQUIRE(tx->checkValid(test.getApp().getAppConnector(), ledgerView, + 0, 0, 0, diag) + ->isSuccess()); + } + } + + // A single stage with a fixed number of clusters. Note, that the indices + // are relative to the Soroban phase, not to the full transaction list. + ParallelSorobanOrder sorobanApplyOrder( + 1, std::vector>(APPLY_CLUSTER_COUNT)); + for (int i = 0; i < sorobanTxCount; ++i) + { + sorobanApplyOrder[0][i % APPLY_CLUSTER_COUNT].push_back(i); + } + + PreApplyScenarioResult res; + res.mResults = closeLedger(test.getApp(), txs, sorobanApplyOrder); + REQUIRE(res.mResults.results.size() == txs.size()); + res.mMeta = test.getLastLcm().getXDR(); + + // Make sure the scenario actually exercises what it is supposed to: the + // classic phase has to succeed, and the Soroban phase has to observe its + // effects on the accounts it removed and sequence bumped. + { + auto resultFor = [&res](TransactionFrameBasePtr const& tx) { + auto const& results = res.mResults.results; + auto it = std::find_if(results.begin(), results.end(), + [&tx](TransactionResultPair const& pair) { + return pair.transactionHash == + tx->getContentsHash(); + }); + REQUIRE(it != results.end()); + return it->result; + }; + for (int i = 0; i < classicTxCount; ++i) + { + INFO("classic tx " << i); + REQUIRE(isSuccessResult(resultFor(txs[i]))); + } + for (int i = 0; i < sorobanTxCount; ++i) + { + INFO("soroban tx " << i); + auto result = resultFor(txs[classicTxCount + i]); + auto code = result.result.code(); + if (i < MERGED_COUNT) + { + REQUIRE(code == txNO_ACCOUNT); + } + else if (i < MERGED_COUNT + BUMPED_COUNT) + { + REQUIRE(code == txBAD_SEQ); + } + else + { + REQUIRE(isSuccessResult(result)); + } + } + } + + // Snapshot every account the scenario could have touched. + std::vector observedAccounts; + auto addAccounts = [&observedAccounts](auto const& accounts) { + for (auto const& acc : accounts) + { + observedAccounts.push_back(acc.getPublicKey()); + } + }; + addAccounts(classicSources); + addAccounts(mergedAccounts); + addAccounts(bumpedAccounts); + addAccounts(normalAccounts); + addAccounts(createdKeys); + observedAccounts.push_back(feeBumper.getPublicKey()); + observedAccounts.push_back(root.getPublicKey()); + + auto ledgerView = + test.getApp().getLedgerManager().copyImmutableLedgerView(); + for (auto const& accountID : observedAccounts) + { + auto key = accountKey(accountID); + std::optional entry; + if (auto e = ledgerView.load(key)) + { + entry = e.current(); + } + res.mEntries.emplace_back(key, entry); + } + return res; +} + +TEST_CASE("Soroban pre-apply results are independent of the worker count", + "[soroban][preapply][acceptance]") +{ + int const SEED_COUNT = 5; + int const SCENARIOS[] = {1, 5, 20, 100}; + int const MAX_WORKER_COUNT = 8; + + for (int runIdx = 0; runIdx < SEED_COUNT; ++runIdx) + { + int64_t seed = Catch::rng()(); + CAPTURE(seed); + for (int scenario : SCENARIOS) + { + CAPTURE(scenario); + auto baseResult = runPreApplyScenario(seed, scenario, 1); + for (int workerCount = 2; workerCount <= MAX_WORKER_COUNT; + ++workerCount) + { + CAPTURE(workerCount); + auto runResult = + runPreApplyScenario(seed, scenario, workerCount); + REQUIRE(runResult.mResults == baseResult.mResults); + REQUIRE(runResult.mMeta == baseResult.mMeta); + REQUIRE(runResult.mEntries == baseResult.mEntries); + } + } + } +} + } // namespace diff --git a/src/transactions/test/SorobanTxTestUtils.cpp b/src/transactions/test/SorobanTxTestUtils.cpp index 4615593862..7e8b3eace9 100644 --- a/src/transactions/test/SorobanTxTestUtils.cpp +++ b/src/transactions/test/SorobanTxTestUtils.cpp @@ -736,17 +736,17 @@ TestContract::Invocation::deduplicateFootprint() xdr::xvector readWrite; UnorderedSet keys; - auto deduplicate = [&](auto const& fp) { + auto deduplicate = [&](auto const& fp, auto& output) { for (auto const& key : fp) { if (keys.insert(key).second) { - readWrite.push_back(key); + output.push_back(key); } } }; - deduplicate(mSpec.getResources().footprint.readWrite); - deduplicate(mSpec.getResources().footprint.readOnly); + deduplicate(mSpec.getResources().footprint.readWrite, readWrite); + deduplicate(mSpec.getResources().footprint.readOnly, readOnly); mSpec = mSpec.setReadOnlyFootprint(readOnly).setReadWriteFootprint(readWrite); } @@ -1708,6 +1708,7 @@ AssetContractTestClient::getTransferTx(TestAccount& fromAcc, mContract .prepareInvocation("transfer", {fromVal, toVal, makeI128(amount)}, spec) + .withDeduplicatedFootprint() .withAuthorizedTopCall(); if (!sourceIsRoot) { diff --git a/src/transactions/test/TransactionTestFrame.cpp b/src/transactions/test/TransactionTestFrame.cpp index 8a94d93d80..41c50f0d09 100644 --- a/src/transactions/test/TransactionTestFrame.cpp +++ b/src/transactions/test/TransactionTestFrame.cpp @@ -372,13 +372,21 @@ TransactionTestFrame::insertKeysForTxApply(UnorderedSet& keys) const } void -TransactionTestFrame::preParallelApply( - AppConnector& app, AbstractLedgerTxn& ltx, TransactionMetaBuilder& meta, - MutableTransactionResultBase& resPayload, +TransactionTestFrame::preParallelApplyReadOnly( + AppConnector& app, CheckValidLedgerViewWrapper const& ls, + TransactionMetaBuilder& meta, MutableTransactionResultBase& resPayload, SorobanNetworkConfig const& sorobanConfig) const { - mTransactionFrame->preParallelApply(app, ltx, meta, resPayload, - sorobanConfig); + mTransactionFrame->preParallelApplyReadOnly(app, ls, meta, resPayload, + sorobanConfig); +} + +void +TransactionTestFrame::preParallelApplyWrite( + AppConnector& app, AbstractLedgerTxn& ltx, TransactionMetaBuilder& meta, + MutableTransactionResultBase const& txResult) const +{ + mTransactionFrame->preParallelApplyWrite(app, ltx, meta, txResult); } std::optional diff --git a/src/transactions/test/TransactionTestFrame.h b/src/transactions/test/TransactionTestFrame.h index acb3228f83..77b40c81b6 100644 --- a/src/transactions/test/TransactionTestFrame.h +++ b/src/transactions/test/TransactionTestFrame.h @@ -157,11 +157,14 @@ class TransactionTestFrame : public TransactionFrameBase insertKeysForFeeProcessing(UnorderedSet& keys) const override; void insertKeysForTxApply(UnorderedSet& keys) const override; - void - preParallelApply(AppConnector& app, AbstractLedgerTxn& ltx, - TransactionMetaBuilder& meta, - MutableTransactionResultBase& resPayload, - SorobanNetworkConfig const& sorobanConfig) const override; + void preParallelApplyReadOnly( + AppConnector& app, CheckValidLedgerViewWrapper const& ls, + TransactionMetaBuilder& meta, MutableTransactionResultBase& resPayload, + SorobanNetworkConfig const& sorobanConfig) const override; + + void preParallelApplyWrite( + AppConnector& app, AbstractLedgerTxn& ltx, TransactionMetaBuilder& meta, + MutableTransactionResultBase const& txResult) const override; std::optional parallelApply( AppConnector& app, ThreadParallelApplyLedgerState const& threadState, diff --git a/src/util/BatchExecutor.cpp b/src/util/BatchExecutor.cpp index 324efc429b..9bc3885d19 100644 --- a/src/util/BatchExecutor.cpp +++ b/src/util/BatchExecutor.cpp @@ -210,6 +210,35 @@ BatchExecutor::pinWorker(size_t index) #endif } +size_t +BatchExecutor::preferredTaskCount() const +{ +#ifdef BUILD_TESTS + if (mPreferredTaskCountForTesting) + { + return *mPreferredTaskCountForTesting; + } +#endif + // As this is meant to be used for parallelizing CPU-heavy work, we want to + // only run the tasks on the physical cores (when physical core info is + // available). + auto concurrency = mPhysicalCoreCount > 0 + ? mPhysicalCoreCount + : std::thread::hardware_concurrency(); + // We want to leave at least one core free to not compete with the main + // thread and other background work. + return concurrency > 1 ? concurrency - 1 : 1; +} + +#ifdef BUILD_TESTS +void +BatchExecutor::setPreferredTaskCountForTesting(size_t count) +{ + releaseAssert(count > 0); + mPreferredTaskCountForTesting = count; +} +#endif + void BatchExecutor::runBatchImpl(size_t numTasks, std::function const& runTask) diff --git a/src/util/BatchExecutor.h b/src/util/BatchExecutor.h index 652dfc357c..fc5c01ffe0 100644 --- a/src/util/BatchExecutor.h +++ b/src/util/BatchExecutor.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -56,6 +57,18 @@ class BatchExecutor : private NonMovableOrCopyable template std::vector executeBatch(std::vector> tasks); + // Returns the maximum number of tasks to use in `executeBatch` without + // oversubscribing physical cores. + // Use this many tasks whenever possible to maximize parallelism and avoid + // oversubscription. + size_t preferredTaskCount() const; + +#ifdef BUILD_TESTS + // Overrides the value returned by `preferredTaskCount`, so that tests can + // pin the parallelism independently of the machine they run on. + void setPreferredTaskCountForTesting(size_t count); +#endif + private: // Runs `runTask(0)..runTask(numTasks-1)` across `numTasks` pinned workers // and blocks until all complete. Rethrows the first exception captured from @@ -96,6 +109,10 @@ class BatchExecutor : private NonMovableOrCopyable // Marks that a batch is currently running, to prevent concurrent // executeBatch calls. std::atomic mBatchRunning{false}; + +#ifdef BUILD_TESTS + std::optional mPreferredTaskCountForTesting; +#endif }; template diff --git a/test-tx-meta-baseline-current/InvokeHostFunctionTests.json b/test-tx-meta-baseline-current/InvokeHostFunctionTests.json index 6a2f0d598a..a5f5930a52 100644 --- a/test-tx-meta-baseline-current/InvokeHostFunctionTests.json +++ b/test-tx-meta-baseline-current/InvokeHostFunctionTests.json @@ -1339,6 +1339,1365 @@ "Soroban non-refundable resource fees are stable|protocol version 26" : [ "+cR3oq2qY0I=" ], "Soroban non-refundable resource fees are stable|protocol version 27" : [ "+cR3oq2qY0I=" ], "Soroban non-refundable resource fees are stable|protocol version 28" : [ "+cR3oq2qY0I=" ], + "Soroban operation source created and removed in classic phase|protocol version 20" : + [ + "bKDF6V5IzTo=", + "ocDSzYTKRxc=", + "x7pY9qZs+ww=", + "pl7yH4GxRFQ=", + "x0yt/kMxerA=", + "2J2lcePHCl0=", + "y+l/7b5BtHw=", + "PJoWBQjHG+8=", + "fFul/ank5Ug=", + "Y2wC5nPoMrw=", + "npK31zqyR9k=", + "2bEpfIKOW8M=", + "2fKoTmSijT0=", + "bLLYYTXiK5M=", + "zd2B7j7hYm8=", + "bQBW+8LXBpU=", + "DaCAag7HYyA=", + "Ls987ixcjsk=", + "RVwvcamX2zQ=", + "pK6dZrXn7tc=", + "KwX2ieEOIM4=", + "KssrhNmE6gQ=", + "ECMbl1rfvkI=", + "mWm3w129yYA=", + "T5TGEqorPA0=", + "C5NyNd3cL+M=", + "IN9qoUwnjl4=", + "qWHwM+Cwsnw=", + "MjVTRJBTTnE=", + "elVHZwI2XCU=", + "+9FSiKk7fXI=", + "Ci0AdmLRgx4=", + "0OSPUrD+VPE=", + "zmqEUhSPbw0=", + "1M+NpduwYMw=", + "iTxGjPm3S4c=", + "zDjM6F9PYIA=", + "BIGclaLIwGk=", + "NGduAG2a87k=", + "fpIm6VOnXec=", + "PXWzTCHXINU=", + "CF0dvEe/zEU=", + "EDPq9VN9FBs=", + "tAjCk6bSL5M=", + "ip/VIOG9QqY=", + "TbWAkcq8y0M=", + "0RXV1B5La98=", + "leaGw26oZuc=", + "VclaMVMhpFo=", + "qQtstBDf9Jc=", + "KONtDnAcOy0=", + "Wl4HOBTvK/Q=", + "+7pNvIZg3Fc=", + "TBn/l3NsyPg=", + "HLL52HhsHGg=", + "RmAB3vLLhWo=", + "N9x9vEZ2fPU=", + "qDuKIhch5f0=", + "2ORdTBzlKco=", + "QIlfJESQTCQ=", + "CrkmaOSPafE=", + "Ytgd0ScwB00=", + "MQFZ4YH3pw8=", + "OUxmL9js4DU=", + "+K4P8h5TqAg=", + "hGNfri2SzJE=", + "B9iRqTncc5U=", + "nEKmWZ75XcQ=", + "XPV5D6izRXk=", + "qlRRw8u4FPc=", + "60y/MYC7xzo=", + "2sxy6PueHq4=", + "XSlCJFxSxIQ=", + "KZcoFOZsArA=", + "y9+jCTKPB8U=", + "gZ8axkqf1gs=", + "jFtqLKJ1B8g=", + "KmD8ngIQNVE=", + "Sz8WZKdGgZ4=", + "Kk/VI45ftPA=", + "SE+6yhTVW88=", + "A/K6mCMMTEM=", + "k1CKfI5Z6Ts=", + "xN144MqXL1o=", + "J3wueY7hFUA=", + "JqRgmBk8WVk=", + "bGk7w5/T5ow=", + "3lJGKmoswBI=", + "g1/d5UgqoGE=", + "Jc0cnsXz3fo=", + "vGxWtpicBnw=", + "XobVeJMz94g=", + "TuCK/UVOHOc=", + "GwZy2YmjNdc=", + "zxgOFDpX5PM=", + "wMUL1rXGiFU=", + "aokk0zAKxqw=", + "pub5+5x7WKs=", + "0pzgjTY0KOA=", + "pSX8z9KMKy8=", + "GSx7GUS4Kpo=" + ], + "Soroban operation source created and removed in classic phase|protocol version 21" : + [ + "bKDF6V5IzTo=", + "ocDSzYTKRxc=", + "x7pY9qZs+ww=", + "pl7yH4GxRFQ=", + "x0yt/kMxerA=", + "2J2lcePHCl0=", + "y+l/7b5BtHw=", + "PJoWBQjHG+8=", + "fFul/ank5Ug=", + "Y2wC5nPoMrw=", + "npK31zqyR9k=", + "2bEpfIKOW8M=", + "2fKoTmSijT0=", + "bLLYYTXiK5M=", + "zd2B7j7hYm8=", + "bQBW+8LXBpU=", + "DaCAag7HYyA=", + "Ls987ixcjsk=", + "RVwvcamX2zQ=", + "pK6dZrXn7tc=", + "KwX2ieEOIM4=", + "KssrhNmE6gQ=", + "ECMbl1rfvkI=", + "mWm3w129yYA=", + "T5TGEqorPA0=", + "C5NyNd3cL+M=", + "IN9qoUwnjl4=", + "qWHwM+Cwsnw=", + "MjVTRJBTTnE=", + "elVHZwI2XCU=", + "+9FSiKk7fXI=", + "Ci0AdmLRgx4=", + "0OSPUrD+VPE=", + "zmqEUhSPbw0=", + "1M+NpduwYMw=", + "iTxGjPm3S4c=", + "zDjM6F9PYIA=", + "BIGclaLIwGk=", + "NGduAG2a87k=", + "fpIm6VOnXec=", + "PXWzTCHXINU=", + "CF0dvEe/zEU=", + "EDPq9VN9FBs=", + "tAjCk6bSL5M=", + "ip/VIOG9QqY=", + "TbWAkcq8y0M=", + "0RXV1B5La98=", + "leaGw26oZuc=", + "VclaMVMhpFo=", + "qQtstBDf9Jc=", + "KONtDnAcOy0=", + "Wl4HOBTvK/Q=", + "+7pNvIZg3Fc=", + "TBn/l3NsyPg=", + "HLL52HhsHGg=", + "RmAB3vLLhWo=", + "N9x9vEZ2fPU=", + "qDuKIhch5f0=", + "2ORdTBzlKco=", + "QIlfJESQTCQ=", + "CrkmaOSPafE=", + "Ytgd0ScwB00=", + "MQFZ4YH3pw8=", + "OUxmL9js4DU=", + "+K4P8h5TqAg=", + "hGNfri2SzJE=", + "B9iRqTncc5U=", + "nEKmWZ75XcQ=", + "XPV5D6izRXk=", + "qlRRw8u4FPc=", + "60y/MYC7xzo=", + "2sxy6PueHq4=", + "XSlCJFxSxIQ=", + "KZcoFOZsArA=", + "y9+jCTKPB8U=", + "gZ8axkqf1gs=", + "jFtqLKJ1B8g=", + "KmD8ngIQNVE=", + "Sz8WZKdGgZ4=", + "Kk/VI45ftPA=", + "SE+6yhTVW88=", + "A/K6mCMMTEM=", + "k1CKfI5Z6Ts=", + "xN144MqXL1o=", + "J3wueY7hFUA=", + "JqRgmBk8WVk=", + "bGk7w5/T5ow=", + "3lJGKmoswBI=", + "g1/d5UgqoGE=", + "Jc0cnsXz3fo=", + "vGxWtpicBnw=", + "XobVeJMz94g=", + "TuCK/UVOHOc=", + "GwZy2YmjNdc=", + "zxgOFDpX5PM=", + "wMUL1rXGiFU=", + "aokk0zAKxqw=", + "pub5+5x7WKs=", + "0pzgjTY0KOA=", + "pSX8z9KMKy8=", + "GSx7GUS4Kpo=" + ], + "Soroban operation source created and removed in classic phase|protocol version 22" : + [ + "bKDF6V5IzTo=", + "5ayMpvekkrI=", + "KpwqxixB1QU=", + "ninzTyfpD8c=", + "0xhX4klidW0=", + "DriP1FYyxvU=", + "syrbRtKc6Og=", + "s7cRiScIDRs=", + "ieMtr3xVwzs=", + "84z/mwe7bLw=", + "bolWl1e2MiI=", + "gSe0PUmaBeE=", + "XWGUN5oRCLQ=", + "giqBXEVckLs=", + "7tc351zel1s=", + "5q0dPSZe6xs=", + "108jh0FFUwQ=", + "YWPZ3Z0AALE=", + "NhbQs97HRHg=", + "4dsyL5+6R3c=", + "/i24ku1BXIk=", + "Dx/7Y1hH1JM=", + "2TqSGPO22xI=", + "5/Z6E0KBrqg=", + "ViUuIKaofeI=", + "leJeeGnjaUY=", + "3CphD5FSwJM=", + "+iy3NPt3ytA=", + "R/VTFpMVli4=", + "y+x7zHxNBmA=", + "4Yi2qwwZIa8=", + "/czKal/hKLc=", + "wCzZUJdypmU=", + "OUQvX9unks8=", + "blcnSidcFAA=", + "iKzCzPo0d6E=", + "wlGGD9kFHsU=", + "usj4INAIQLE=", + "KYhzuloANJg=", + "yBH0FljHOAE=", + "2pEDdVBCPUg=", + "RLQSuE0VRKQ=", + "ltRb1/FUQ9I=", + "eCwc/WSUNCw=", + "l2jIldD5lKI=", + "3GhYy8sX54Q=", + "LcaC2CLknZo=", + "RSb9KWtnQm8=", + "pmfIWlBW6iA=", + "/x4WW5L+tCQ=", + "5fkBtnXRe/0=", + "z62MBXihCyY=", + "CNZp6sFSoXM=", + "ZGfMkEu8Ulw=", + "gxsZCR7REC4=", + "FzKKFViimiQ=", + "2fjvelCaJrE=", + "D6jj6tAnifM=", + "y6I8zVgn67I=", + "fFxwT4onCuo=", + "zmTIbczXn0k=", + "MiTslt8iyzM=", + "jfpyVY+vrMU=", + "azA+s6oxbOg=", + "ZmUPbn7/xc0=", + "ZucccJTapDk=", + "ieWc2QQjYwg=", + "iFmCgHZLhRI=", + "4kpdCiyd6+U=", + "zkWAzUNq6g0=", + "KlkrtrwMZSA=", + "GSXS/naFC7o=", + "UwQSKeqWeuE=", + "FBOP79OXINA=", + "EcchsVBcwjA=", + "2C41h+WmaFc=", + "GstNub02AMU=", + "YNufYkDHgA8=", + "JpAFUvNARFM=", + "jPlMpkSbnnk=", + "lV1IdI9sdOI=", + "GftzwB+pCTQ=", + "hR5d6pVHc40=", + "qLHKxSvGU3E=", + "t721A004I+I=", + "HGtmt02Q6JU=", + "iaQzSi66wcs=", + "rbowyW4jCBs=", + "WJs4iuxCdaU=", + "CGmvdBp/5zs=", + "AMzWei96FSc=", + "G5oFpONpPZ0=", + "mnUDr5XOkiQ=", + "UBrHhLuwCS4=", + "tXDs520pIXI=", + "0gIZ9k20dos=", + "sb6iULDIZIk=", + "/9TZzHvXMJE=", + "U6/5/AUYgvg=", + "xIdb9LXLdE0=", + "LXkHFxv8xCI=" + ], + "Soroban operation source created and removed in classic phase|protocol version 23" : + [ + "+cR3oq2qY0I=", + "nz9v1PzpgfI=", + "hohUpT/ADqk=", + "bvjRdiFGfTM=", + "fj7GNFRwzCo=", + "nXkts5zEKgU=", + "ZiQQXHUPS5U=", + "TC5BKqdd+B4=", + "WXaQReLYO5k=", + "LPFg+MEaSQg=", + "Asgvsb6w7Q0=", + "wBJrQJNAkmE=", + "wrjwucM/Zow=", + "e4novqniZ+g=", + "Ibm21qxpI7s=", + "UextlVZsjnM=", + "0n7dMegdZ3A=", + "11XOGuZ3Xug=", + "WV1Ho30vGvc=", + "SnZLQYvZxfA=", + "M5BZn04JJsE=", + "uMeTcfXMq5A=", + "ziO+HFD3yYY=", + "kGjdPxb4AWs=", + "lpn9djEUn2M=", + "baWzcJ+nmdg=", + "UF9UKXHjJDc=", + "y+4FEa0UPZo=", + "uqFChe3y8rQ=", + "WZZHQawuopM=", + "76Tytga3SWQ=", + "oBXvRd8lj7E=", + "2UIvJMrAYkA=", + "Lu4HE/7VmrU=", + "jax2QwuMLvA=", + "icIkd6qRbPg=", + "KtZfjBYf/V8=", + "huqkxOC2LoA=", + "Jt3tgC+PClI=", + "+UcB9Bo0/hg=", + "zJRsdNMwMAc=", + "MLrMqsvOziE=", + "6vK4A0eFd9c=", + "/dBnJEv5mug=", + "T1D/fXHUY8k=", + "wbI5JOgzavw=", + "saayh6lFoy8=", + "1zCrRETesEE=", + "4aeZ3nDW2H8=", + "2EVgJD6hMxE=", + "0HYOxKmFbXc=", + "2GH3CCviOcs=", + "9kcvbtcyje8=", + "tPZwrweWQ8c=", + "bujRoPDgmrM=", + "1VayL7z/03M=", + "WzdN95wnWC4=", + "cOk9uPy8kSQ=", + "0yMEY5GgOiw=", + "80ul4iYlg1A=", + "Kvi1c6UeFIs=", + "M+BKIBuPOS4=", + "Edzb65HK+ao=", + "xNFD7cNA9PE=", + "+3bDFSiTswE=", + "x43oIP5QKZ0=", + "T1z4oGc8vqc=", + "mlW6FqrcYSI=", + "hQEjDayPI9E=", + "C1hgh4vaYvk=", + "rm96fjiAVTE=", + "C5N7gq8301Q=", + "v4o9l1ZMoD0=", + "gdJY9xf3I/I=", + "0FvLTFyx56c=", + "RihwpLL/n/E=", + "Oo2rLuCHGr4=", + "ldJr6nmpvUU=", + "4EErS4HHw7k=", + "oPYYvxcsZY4=", + "Jzmx0gWrqGQ=", + "siQ/3Hh+++U=", + "Q2Q6XWtaQCQ=", + "Ot03scyos0k=", + "rOcJh/4Ez/Y=", + "dKxQ91Mrvn8=", + "LBpIyth3wYk=", + "wmV5IzZY1pM=", + "qIRuVOi6tQM=", + "k2ZdBvYWWi8=", + "wNwIp33jJMI=", + "Ma1rDLoDtvA=", + "XpM0H5kfBLs=", + "xlufJjbEuYg=", + "TFeOtOGRmt8=", + "Eclf3i5w7gg=", + "5aKvPQAX7tE=", + "GLxznahSj+E=", + "4L5PdJ7n/qo=", + "i23Oql77Y6U=", + "cOJDuiZkHlg=" + ], + "Soroban operation source created and removed in classic phase|protocol version 24" : + [ + "+cR3oq2qY0I=", + "nz9v1PzpgfI=", + "hohUpT/ADqk=", + "bvjRdiFGfTM=", + "fj7GNFRwzCo=", + "nXkts5zEKgU=", + "ZiQQXHUPS5U=", + "TC5BKqdd+B4=", + "WXaQReLYO5k=", + "LPFg+MEaSQg=", + "Asgvsb6w7Q0=", + "wBJrQJNAkmE=", + "wrjwucM/Zow=", + "e4novqniZ+g=", + "Ibm21qxpI7s=", + "UextlVZsjnM=", + "0n7dMegdZ3A=", + "11XOGuZ3Xug=", + "WV1Ho30vGvc=", + "SnZLQYvZxfA=", + "M5BZn04JJsE=", + "uMeTcfXMq5A=", + "ziO+HFD3yYY=", + "kGjdPxb4AWs=", + "lpn9djEUn2M=", + "baWzcJ+nmdg=", + "UF9UKXHjJDc=", + "y+4FEa0UPZo=", + "uqFChe3y8rQ=", + "WZZHQawuopM=", + "76Tytga3SWQ=", + "oBXvRd8lj7E=", + "2UIvJMrAYkA=", + "Lu4HE/7VmrU=", + "jax2QwuMLvA=", + "icIkd6qRbPg=", + "KtZfjBYf/V8=", + "huqkxOC2LoA=", + "Jt3tgC+PClI=", + "+UcB9Bo0/hg=", + "zJRsdNMwMAc=", + "MLrMqsvOziE=", + "6vK4A0eFd9c=", + "/dBnJEv5mug=", + "T1D/fXHUY8k=", + "wbI5JOgzavw=", + "saayh6lFoy8=", + "1zCrRETesEE=", + "4aeZ3nDW2H8=", + "2EVgJD6hMxE=", + "0HYOxKmFbXc=", + "2GH3CCviOcs=", + "9kcvbtcyje8=", + "tPZwrweWQ8c=", + "bujRoPDgmrM=", + "1VayL7z/03M=", + "WzdN95wnWC4=", + "cOk9uPy8kSQ=", + "0yMEY5GgOiw=", + "80ul4iYlg1A=", + "Kvi1c6UeFIs=", + "M+BKIBuPOS4=", + "Edzb65HK+ao=", + "xNFD7cNA9PE=", + "+3bDFSiTswE=", + "x43oIP5QKZ0=", + "T1z4oGc8vqc=", + "mlW6FqrcYSI=", + "hQEjDayPI9E=", + "C1hgh4vaYvk=", + "rm96fjiAVTE=", + "C5N7gq8301Q=", + "v4o9l1ZMoD0=", + "gdJY9xf3I/I=", + "0FvLTFyx56c=", + "RihwpLL/n/E=", + "Oo2rLuCHGr4=", + "ldJr6nmpvUU=", + "4EErS4HHw7k=", + "oPYYvxcsZY4=", + "Jzmx0gWrqGQ=", + "siQ/3Hh+++U=", + "Q2Q6XWtaQCQ=", + "Ot03scyos0k=", + "rOcJh/4Ez/Y=", + "dKxQ91Mrvn8=", + "LBpIyth3wYk=", + "wmV5IzZY1pM=", + "qIRuVOi6tQM=", + "k2ZdBvYWWi8=", + "wNwIp33jJMI=", + "Ma1rDLoDtvA=", + "XpM0H5kfBLs=", + "xlufJjbEuYg=", + "TFeOtOGRmt8=", + "Eclf3i5w7gg=", + "5aKvPQAX7tE=", + "GLxznahSj+E=", + "4L5PdJ7n/qo=", + "i23Oql77Y6U=", + "cOJDuiZkHlg=" + ], + "Soroban operation source created and removed in classic phase|protocol version 25" : + [ + "+cR3oq2qY0I=", + "nz9v1PzpgfI=", + "hohUpT/ADqk=", + "bvjRdiFGfTM=", + "fj7GNFRwzCo=", + "nXkts5zEKgU=", + "ZiQQXHUPS5U=", + "TC5BKqdd+B4=", + "WXaQReLYO5k=", + "LPFg+MEaSQg=", + "Asgvsb6w7Q0=", + "wBJrQJNAkmE=", + "wrjwucM/Zow=", + "e4novqniZ+g=", + "Ibm21qxpI7s=", + "UextlVZsjnM=", + "0n7dMegdZ3A=", + "11XOGuZ3Xug=", + "WV1Ho30vGvc=", + "SnZLQYvZxfA=", + "M5BZn04JJsE=", + "uMeTcfXMq5A=", + "ziO+HFD3yYY=", + "kGjdPxb4AWs=", + "lpn9djEUn2M=", + "baWzcJ+nmdg=", + "UF9UKXHjJDc=", + "y+4FEa0UPZo=", + "uqFChe3y8rQ=", + "WZZHQawuopM=", + "76Tytga3SWQ=", + "oBXvRd8lj7E=", + "2UIvJMrAYkA=", + "Lu4HE/7VmrU=", + "jax2QwuMLvA=", + "icIkd6qRbPg=", + "KtZfjBYf/V8=", + "huqkxOC2LoA=", + "Jt3tgC+PClI=", + "+UcB9Bo0/hg=", + "zJRsdNMwMAc=", + "MLrMqsvOziE=", + "6vK4A0eFd9c=", + "/dBnJEv5mug=", + "T1D/fXHUY8k=", + "wbI5JOgzavw=", + "saayh6lFoy8=", + "1zCrRETesEE=", + "4aeZ3nDW2H8=", + "2EVgJD6hMxE=", + "0HYOxKmFbXc=", + "2GH3CCviOcs=", + "9kcvbtcyje8=", + "tPZwrweWQ8c=", + "bujRoPDgmrM=", + "1VayL7z/03M=", + "WzdN95wnWC4=", + "cOk9uPy8kSQ=", + "0yMEY5GgOiw=", + "80ul4iYlg1A=", + "Kvi1c6UeFIs=", + "M+BKIBuPOS4=", + "Edzb65HK+ao=", + "xNFD7cNA9PE=", + "+3bDFSiTswE=", + "x43oIP5QKZ0=", + "T1z4oGc8vqc=", + "mlW6FqrcYSI=", + "hQEjDayPI9E=", + "C1hgh4vaYvk=", + "rm96fjiAVTE=", + "C5N7gq8301Q=", + "v4o9l1ZMoD0=", + "gdJY9xf3I/I=", + "0FvLTFyx56c=", + "RihwpLL/n/E=", + "Oo2rLuCHGr4=", + "ldJr6nmpvUU=", + "4EErS4HHw7k=", + "oPYYvxcsZY4=", + "Jzmx0gWrqGQ=", + "siQ/3Hh+++U=", + "Q2Q6XWtaQCQ=", + "Ot03scyos0k=", + "rOcJh/4Ez/Y=", + "dKxQ91Mrvn8=", + "LBpIyth3wYk=", + "wmV5IzZY1pM=", + "qIRuVOi6tQM=", + "k2ZdBvYWWi8=", + "wNwIp33jJMI=", + "Ma1rDLoDtvA=", + "XpM0H5kfBLs=", + "xlufJjbEuYg=", + "TFeOtOGRmt8=", + "Eclf3i5w7gg=", + "5aKvPQAX7tE=", + "GLxznahSj+E=", + "4L5PdJ7n/qo=", + "i23Oql77Y6U=", + "cOJDuiZkHlg=" + ], + "Soroban operation source created and removed in classic phase|protocol version 26" : + [ + "+cR3oq2qY0I=", + "crbAf4Ea6kg=", + "iqTvm0y1Iow=", + "6pO37nDn84A=", + "wRmit6MBjrQ=", + "oVpHg7RRBlk=", + "Aid/7xaOTR0=", + "bbMlrMP+qlI=", + "fdD5DjwwiOQ=", + "dLar7fQTVJQ=", + "rqoO87FasNU=", + "7aj+KvKQXwY=", + "mruvECGnEyU=", + "5/S0IcIxBNI=", + "Ze0l8W+lT7M=", + "UXimakEXASc=", + "ay1R0+swdfw=", + "VEV8xNmTE1Q=", + "ohDWJIUApRk=", + "LeIaM+VHBWo=", + "XXB+LJhuCO0=", + "Z1SOcuDsn3U=", + "1j7QdwSbjPY=", + "OSoLJeVtHOg=", + "AoGTPhaZJy4=", + "DWRtqn3dZGo=", + "tuOemI/6/Vw=", + "rpw06OPQ4YQ=", + "yq78L4dQbBA=", + "pS8F28DUKnk=", + "R7aUoGb7MyA=", + "CzSTC9mDsx0=", + "9pl1fjzk41A=", + "czFb8xk728c=", + "67+TTjrwYLE=", + "yRwsYheUXUo=", + "E8kX1Yrj71M=", + "8fn8vzThmDI=", + "WBTucsatdSY=", + "cLZ5fozibbY=", + "sVBaA3CDFfU=", + "zLz7Tfj0ef4=", + "kbGW6RV1SW4=", + "NStKgU3nvsw=", + "jyRmbaA22SU=", + "5niYf+Sj4qo=", + "IYqTe+q5124=", + "b/asdCehmbs=", + "gGW69QBxFJA=", + "sjeXqxl36Eg=", + "ga0wPKIyWeU=", + "o8iLtP9jdUY=", + "3C5DxayMoqQ=", + "oyRATLVeDdA=", + "cBSvG7Y4Lp4=", + "qeYaEXAXYc0=", + "jxNlURg20AM=", + "lwAZn9dNSjg=", + "+ijl0DL07ME=", + "RyPBfGhCxpY=", + "3M0rqTnOqhE=", + "inHVXSTA278=", + "qz9ZiOB41eY=", + "3G2x7xoeHOU=", + "IkoYpaPqPrw=", + "j0WdJcdb+BE=", + "CeAemj67nZM=", + "kVpPjbTS/jo=", + "XEF5tUmLfjU=", + "0Bz/VETaobk=", + "7l4k+XN3Xno=", + "4imVULlTzUw=", + "Aea+sYr85WQ=", + "8PaqAkHF0e0=", + "QKC5AS+jhCM=", + "pEWBhRBgCz8=", + "fMxyYZquhAA=", + "8xxIbp8nLLY=", + "AYZkATYEJOk=", + "XKrbs5lBIcw=", + "6KVpqakZPiw=", + "OeAMSZ0Gc/U=", + "MhgNQLzMD4Q=", + "NK6v8hyAKGY=", + "QuMvE+2NFIU=", + "1G81YWIYlgo=", + "bKdk2OVRSDo=", + "maokePOWsQI=", + "CdgngSmnixI=", + "IcLAQnataC4=", + "vr2lY+iMklA=", + "CYO7w60bDzE=", + "rl+0lPOoBHk=", + "qHcIz27YW90=", + "yvViF0w9JZs=", + "dNuw4Jq10aY=", + "Bk//FNsJ//8=", + "8JWNtTVb5mE=", + "v64n9RwfQRM=", + "q1W0Fxx9Nxw=", + "0u+ay1fNNYE=" + ], + "Soroban operation source created and removed in classic phase|protocol version 27" : + [ + "+cR3oq2qY0I=", + "crbAf4Ea6kg=", + "iqTvm0y1Iow=", + "6pO37nDn84A=", + "wRmit6MBjrQ=", + "oVpHg7RRBlk=", + "Aid/7xaOTR0=", + "bbMlrMP+qlI=", + "fdD5DjwwiOQ=", + "dLar7fQTVJQ=", + "rqoO87FasNU=", + "7aj+KvKQXwY=", + "mruvECGnEyU=", + "5/S0IcIxBNI=", + "Ze0l8W+lT7M=", + "UXimakEXASc=", + "ay1R0+swdfw=", + "VEV8xNmTE1Q=", + "ohDWJIUApRk=", + "LeIaM+VHBWo=", + "XXB+LJhuCO0=", + "Z1SOcuDsn3U=", + "1j7QdwSbjPY=", + "OSoLJeVtHOg=", + "AoGTPhaZJy4=", + "DWRtqn3dZGo=", + "tuOemI/6/Vw=", + "rpw06OPQ4YQ=", + "yq78L4dQbBA=", + "pS8F28DUKnk=", + "R7aUoGb7MyA=", + "CzSTC9mDsx0=", + "9pl1fjzk41A=", + "czFb8xk728c=", + "67+TTjrwYLE=", + "yRwsYheUXUo=", + "E8kX1Yrj71M=", + "8fn8vzThmDI=", + "WBTucsatdSY=", + "cLZ5fozibbY=", + "sVBaA3CDFfU=", + "zLz7Tfj0ef4=", + "kbGW6RV1SW4=", + "NStKgU3nvsw=", + "jyRmbaA22SU=", + "5niYf+Sj4qo=", + "IYqTe+q5124=", + "b/asdCehmbs=", + "gGW69QBxFJA=", + "sjeXqxl36Eg=", + "ga0wPKIyWeU=", + "o8iLtP9jdUY=", + "3C5DxayMoqQ=", + "oyRATLVeDdA=", + "cBSvG7Y4Lp4=", + "qeYaEXAXYc0=", + "jxNlURg20AM=", + "lwAZn9dNSjg=", + "+ijl0DL07ME=", + "RyPBfGhCxpY=", + "3M0rqTnOqhE=", + "inHVXSTA278=", + "qz9ZiOB41eY=", + "3G2x7xoeHOU=", + "IkoYpaPqPrw=", + "j0WdJcdb+BE=", + "CeAemj67nZM=", + "kVpPjbTS/jo=", + "XEF5tUmLfjU=", + "0Bz/VETaobk=", + "7l4k+XN3Xno=", + "4imVULlTzUw=", + "Aea+sYr85WQ=", + "8PaqAkHF0e0=", + "QKC5AS+jhCM=", + "pEWBhRBgCz8=", + "fMxyYZquhAA=", + "8xxIbp8nLLY=", + "AYZkATYEJOk=", + "XKrbs5lBIcw=", + "6KVpqakZPiw=", + "OeAMSZ0Gc/U=", + "MhgNQLzMD4Q=", + "NK6v8hyAKGY=", + "QuMvE+2NFIU=", + "1G81YWIYlgo=", + "bKdk2OVRSDo=", + "maokePOWsQI=", + "CdgngSmnixI=", + "IcLAQnataC4=", + "vr2lY+iMklA=", + "CYO7w60bDzE=", + "rl+0lPOoBHk=", + "qHcIz27YW90=", + "yvViF0w9JZs=", + "dNuw4Jq10aY=", + "Bk//FNsJ//8=", + "8JWNtTVb5mE=", + "v64n9RwfQRM=", + "q1W0Fxx9Nxw=", + "0u+ay1fNNYE=" + ], + "Soroban operation source created and removed in classic phase|protocol version 28" : + [ + "+cR3oq2qY0I=", + "crbAf4Ea6kg=", + "iqTvm0y1Iow=", + "6pO37nDn84A=", + "wRmit6MBjrQ=", + "oVpHg7RRBlk=", + "Aid/7xaOTR0=", + "bbMlrMP+qlI=", + "fdD5DjwwiOQ=", + "dLar7fQTVJQ=", + "rqoO87FasNU=", + "7aj+KvKQXwY=", + "mruvECGnEyU=", + "5/S0IcIxBNI=", + "Ze0l8W+lT7M=", + "UXimakEXASc=", + "ay1R0+swdfw=", + "VEV8xNmTE1Q=", + "ohDWJIUApRk=", + "LeIaM+VHBWo=", + "XXB+LJhuCO0=", + "Z1SOcuDsn3U=", + "1j7QdwSbjPY=", + "OSoLJeVtHOg=", + "AoGTPhaZJy4=", + "DWRtqn3dZGo=", + "tuOemI/6/Vw=", + "rpw06OPQ4YQ=", + "yq78L4dQbBA=", + "pS8F28DUKnk=", + "R7aUoGb7MyA=", + "CzSTC9mDsx0=", + "9pl1fjzk41A=", + "czFb8xk728c=", + "67+TTjrwYLE=", + "yRwsYheUXUo=", + "E8kX1Yrj71M=", + "8fn8vzThmDI=", + "WBTucsatdSY=", + "cLZ5fozibbY=", + "sVBaA3CDFfU=", + "zLz7Tfj0ef4=", + "kbGW6RV1SW4=", + "NStKgU3nvsw=", + "jyRmbaA22SU=", + "5niYf+Sj4qo=", + "IYqTe+q5124=", + "b/asdCehmbs=", + "gGW69QBxFJA=", + "sjeXqxl36Eg=", + "ga0wPKIyWeU=", + "o8iLtP9jdUY=", + "3C5DxayMoqQ=", + "oyRATLVeDdA=", + "cBSvG7Y4Lp4=", + "qeYaEXAXYc0=", + "jxNlURg20AM=", + "lwAZn9dNSjg=", + "+ijl0DL07ME=", + "RyPBfGhCxpY=", + "3M0rqTnOqhE=", + "inHVXSTA278=", + "qz9ZiOB41eY=", + "3G2x7xoeHOU=", + "IkoYpaPqPrw=", + "j0WdJcdb+BE=", + "CeAemj67nZM=", + "kVpPjbTS/jo=", + "XEF5tUmLfjU=", + "0Bz/VETaobk=", + "7l4k+XN3Xno=", + "4imVULlTzUw=", + "Aea+sYr85WQ=", + "8PaqAkHF0e0=", + "QKC5AS+jhCM=", + "pEWBhRBgCz8=", + "fMxyYZquhAA=", + "8xxIbp8nLLY=", + "AYZkATYEJOk=", + "XKrbs5lBIcw=", + "6KVpqakZPiw=", + "OeAMSZ0Gc/U=", + "MhgNQLzMD4Q=", + "NK6v8hyAKGY=", + "QuMvE+2NFIU=", + "1G81YWIYlgo=", + "bKdk2OVRSDo=", + "maokePOWsQI=", + "CdgngSmnixI=", + "IcLAQnataC4=", + "vr2lY+iMklA=", + "CYO7w60bDzE=", + "rl+0lPOoBHk=", + "qHcIz27YW90=", + "yvViF0w9JZs=", + "dNuw4Jq10aY=", + "Bk//FNsJ//8=", + "8JWNtTVb5mE=", + "v64n9RwfQRM=", + "q1W0Fxx9Nxw=", + "0u+ay1fNNYE=" + ], + "Soroban pre-apply removes pre-auth tx signers|protocol version 20" : + [ + "bKDF6V5IzTo=", + "1YoRQpENTWE=", + "dYRpGqAlBDs=", + "pBZtiu3OJ7M=", + "IBgPoEf6H8s=", + "oUoLY4Gok3Q=", + "V6Ly+If+Kv8=", + "Cm3CkzL2syQ=", + "PhFE8ATy/4g=", + "FUBVEeXefug=", + "+hKh+5+Thsw=", + "BlhftaBlm9g=", + "ZS/mQlezUuE=", + "IbUGZ7407UU=", + "L1cDvhFQE80=", + "D+6b+5+H1ss=", + "j7SY2fAK6fU=", + "/r+khMp1/8Y=", + "7VBKTQNpMeI=", + "6jTXkdr5rPc=", + "6T+YCvYp8H0=", + "z0+71j6HB28=", + "Z8Ghz71bsAw=", + "U5VtQDYCcYU=", + "zvN90WCRkO0=", + "paei0bhsSsw=", + "S28j8kgREkE=", + "fvzFffOIdO4=", + "j+BP1ibsAFw=", + "7HmBrxtECbA=", + "rV7Gs+BCjiE=", + "JePxrvqNzs4=", + "p9TVAXdLwmc=", + "RxrYYjg9xds=", + "O8s2BCTv5DM=", + "+4b9MzEXa20=", + "SZI1PxuLtVE=", + "SvbVLfk38Jk=", + "5/k+BSJIhF4=", + "ybLgnPo+8nk=", + "9VpMR7mf4Po=", + "0oYz7Ehlm6A=", + "ZFeZS74f/4w=", + "u78kYC7n3/Q=" + ], + "Soroban pre-apply removes pre-auth tx signers|protocol version 21" : + [ + "bKDF6V5IzTo=", + "1YoRQpENTWE=", + "dYRpGqAlBDs=", + "pBZtiu3OJ7M=", + "IBgPoEf6H8s=", + "oUoLY4Gok3Q=", + "V6Ly+If+Kv8=", + "Cm3CkzL2syQ=", + "PhFE8ATy/4g=", + "FUBVEeXefug=", + "+hKh+5+Thsw=", + "BlhftaBlm9g=", + "ZS/mQlezUuE=", + "IbUGZ7407UU=", + "L1cDvhFQE80=", + "D+6b+5+H1ss=", + "j7SY2fAK6fU=", + "/r+khMp1/8Y=", + "7VBKTQNpMeI=", + "6jTXkdr5rPc=", + "6T+YCvYp8H0=", + "z0+71j6HB28=", + "Z8Ghz71bsAw=", + "U5VtQDYCcYU=", + "zvN90WCRkO0=", + "paei0bhsSsw=", + "S28j8kgREkE=", + "fvzFffOIdO4=", + "j+BP1ibsAFw=", + "7HmBrxtECbA=", + "rV7Gs+BCjiE=", + "JePxrvqNzs4=", + "p9TVAXdLwmc=", + "RxrYYjg9xds=", + "UIcS2n63svo=", + "5AKz5MKz3EY=", + "mQLIi4/Xnpo=", + "VtBxD9nW620=", + "ceOLSgirQIY=", + "rHzGA3aTfkw=", + "RmAa3HcLZTc=", + "NJfu6kEGL/4=", + "BSMhaTpJB4A=", + "HgIVMbnJQB4=" + ], + "Soroban pre-apply removes pre-auth tx signers|protocol version 22" : + [ + "bKDF6V5IzTo=", + "rR4bN6XbNfA=", + "JS6E860wJVs=", + "IDGsWUzKLBg=", + "nBz425qu8TA=", + "+5Dk5uxx0rE=", + "AKTpav8hbPk=", + "f46MIJNsJ7g=", + "C3nn7+10DbI=", + "uBGgXv+i2FQ=", + "MXG5KnbZNmI=", + "vAOX3fQHjXI=", + "j33yW667WIE=", + "Iy+GcIxksMk=", + "5ssn3tY2daI=", + "jPxDcic4tWY=", + "4ApAZLoEjvA=", + "9beQ0wLBIgo=", + "sDlVuzU/IZY=", + "LK7dyMvS/M4=", + "+fP84720bhs=", + "+6EZ1HEZ7rU=", + "L5nSFfKp2HI=", + "oqlN+7Cc354=", + "dBVQweMckj4=", + "kVTA3XuOok8=", + "ySbms3WjD1k=", + "mxfbAlSuU2A=", + "oIRc9owv+Ng=", + "5dfzdW9W/DU=", + "kh7qum+vOdQ=", + "0/2/o/Y5wvs=", + "jNP2r8hb+wM=", + "+pSYIj4EKGE=", + "NpRJ93HgrYI=", + "irA2wilgrnA=", + "IfCYdVEHN6A=", + "eSJ9dhI6SO8=", + "4+qhdqGn68g=", + "evhjr9oq5cY=", + "bSmOo50CUSQ=", + "IFQf8MX9jWM=", + "hORHjI6CsJs=", + "Is/kbc6knec=" + ], + "Soroban pre-apply removes pre-auth tx signers|protocol version 23" : + [ + "+cR3oq2qY0I=", + "2lu0XZUNpXE=", + "0YkYsN9TXcU=", + "Vudi8oDzW4I=", + "2v7LYECgY2E=", + "4eNmAHnACvY=", + "nsbN8jGC9+c=", + "3kOlGG6S6/Y=", + "BUWt8cXkE5A=", + "4A6ZHTxj8tg=", + "809kpAhSloE=", + "WJKVjCcl7yo=", + "a8oSOyFmNT0=", + "WyI59xx+zTc=", + "YOQgHndQKjA=", + "MsAl1s+BNFQ=", + "nF+LnYavpr0=", + "y9MpYmiN300=", + "nSNpeleCNfI=", + "PSTit18ljrQ=", + "3JCL3JAVKOQ=", + "LPGjbKnCvzE=", + "S8gNPX/DWB0=", + "cIXhb1wrROk=", + "fPIsm2zwFXg=", + "pnW9jsSv/og=", + "Jk4IV8yurg8=", + "dUsPMBubifg=", + "4NKto3sosLY=", + "50doy92gxJs=", + "1Rkqm63AlHw=", + "+LN3x+p008A=", + "+v4Ai/yLXQo=", + "uj+OehxLu2o=", + "XBUW/5qoKUo=", + "zFo4Vc6YmG8=", + "6k8nkchciDY=", + "SFVh7gD5zB8=", + "YsNY30clXRo=", + "aKiRBvxFWOs=", + "yegKP5/2ctg=", + "4WaR3adrld4=", + "b/u7eXDr9mc=", + "5t5NSWs4F3s=" + ], + "Soroban pre-apply removes pre-auth tx signers|protocol version 24" : + [ + "+cR3oq2qY0I=", + "2lu0XZUNpXE=", + "0YkYsN9TXcU=", + "Vudi8oDzW4I=", + "2v7LYECgY2E=", + "4eNmAHnACvY=", + "nsbN8jGC9+c=", + "3kOlGG6S6/Y=", + "BUWt8cXkE5A=", + "4A6ZHTxj8tg=", + "809kpAhSloE=", + "WJKVjCcl7yo=", + "a8oSOyFmNT0=", + "WyI59xx+zTc=", + "YOQgHndQKjA=", + "MsAl1s+BNFQ=", + "nF+LnYavpr0=", + "y9MpYmiN300=", + "nSNpeleCNfI=", + "PSTit18ljrQ=", + "3JCL3JAVKOQ=", + "LPGjbKnCvzE=", + "S8gNPX/DWB0=", + "cIXhb1wrROk=", + "fPIsm2zwFXg=", + "pnW9jsSv/og=", + "Jk4IV8yurg8=", + "dUsPMBubifg=", + "4NKto3sosLY=", + "50doy92gxJs=", + "1Rkqm63AlHw=", + "+LN3x+p008A=", + "+v4Ai/yLXQo=", + "uj+OehxLu2o=", + "XBUW/5qoKUo=", + "zFo4Vc6YmG8=", + "6k8nkchciDY=", + "SFVh7gD5zB8=", + "YsNY30clXRo=", + "aKiRBvxFWOs=", + "yegKP5/2ctg=", + "4WaR3adrld4=", + "b/u7eXDr9mc=", + "5t5NSWs4F3s=" + ], + "Soroban pre-apply removes pre-auth tx signers|protocol version 25" : + [ + "+cR3oq2qY0I=", + "2lu0XZUNpXE=", + "0YkYsN9TXcU=", + "Vudi8oDzW4I=", + "2v7LYECgY2E=", + "4eNmAHnACvY=", + "nsbN8jGC9+c=", + "3kOlGG6S6/Y=", + "BUWt8cXkE5A=", + "4A6ZHTxj8tg=", + "809kpAhSloE=", + "WJKVjCcl7yo=", + "a8oSOyFmNT0=", + "WyI59xx+zTc=", + "YOQgHndQKjA=", + "MsAl1s+BNFQ=", + "nF+LnYavpr0=", + "y9MpYmiN300=", + "nSNpeleCNfI=", + "PSTit18ljrQ=", + "3JCL3JAVKOQ=", + "LPGjbKnCvzE=", + "S8gNPX/DWB0=", + "cIXhb1wrROk=", + "fPIsm2zwFXg=", + "pnW9jsSv/og=", + "Jk4IV8yurg8=", + "dUsPMBubifg=", + "4NKto3sosLY=", + "50doy92gxJs=", + "1Rkqm63AlHw=", + "+LN3x+p008A=", + "+v4Ai/yLXQo=", + "uj+OehxLu2o=", + "XBUW/5qoKUo=", + "zFo4Vc6YmG8=", + "6k8nkchciDY=", + "SFVh7gD5zB8=", + "YsNY30clXRo=", + "aKiRBvxFWOs=", + "yegKP5/2ctg=", + "4WaR3adrld4=", + "b/u7eXDr9mc=", + "5t5NSWs4F3s=" + ], + "Soroban pre-apply removes pre-auth tx signers|protocol version 26" : + [ + "+cR3oq2qY0I=", + "YS3CtbB4LCY=", + "yuxnivFgzdw=", + "NmWV7/t534g=", + "/Kjv95RCny8=", + "pO1qODwjXZY=", + "468KGJPAA6M=", + "zEMED1Ra2FE=", + "6KQ1GRPt6bI=", + "MLNDTE6pvY4=", + "M26lOjlaQIY=", + "mp+KNeldXrU=", + "lUxktDmMJ54=", + "z+4rbTbSb84=", + "CvbvbVNMx4k=", + "5MTOb0MPD+w=", + "MIvv9ify+gw=", + "dSs8vpDFl3c=", + "iw4hEELDw5Q=", + "hajZcnBWStU=", + "Orkjm9fvOX0=", + "YiZVW2/DJTA=", + "MHNjEmcU/yc=", + "N9FNXxDcVDs=", + "IpqtiLtCShI=", + "IWJJU/JPink=", + "hNhPmyg88jY=", + "1pct7OGGkkI=", + "l9wje+2H0Jg=", + "fkW5vRedzh4=", + "Bt4XkzF4Smc=", + "jsZPxY0xD3I=", + "fH3XJwGQAME=", + "MBYfICUqUII=", + "UB3keZnV78E=", + "yTp/L/LfDZ0=", + "fYKKnLHVgDY=", + "/k+LTrpQXdw=", + "hlZh01w9cdU=", + "CPKD3YpWfmI=", + "00Ek9r6xKCU=", + "CYn7uvpg2AM=", + "WgyGQCjDM68=", + "YJso3URJjlo=" + ], + "Soroban pre-apply removes pre-auth tx signers|protocol version 27" : + [ + "+cR3oq2qY0I=", + "YS3CtbB4LCY=", + "yuxnivFgzdw=", + "NmWV7/t534g=", + "/Kjv95RCny8=", + "pO1qODwjXZY=", + "468KGJPAA6M=", + "zEMED1Ra2FE=", + "6KQ1GRPt6bI=", + "MLNDTE6pvY4=", + "M26lOjlaQIY=", + "mp+KNeldXrU=", + "lUxktDmMJ54=", + "z+4rbTbSb84=", + "CvbvbVNMx4k=", + "5MTOb0MPD+w=", + "MIvv9ify+gw=", + "dSs8vpDFl3c=", + "iw4hEELDw5Q=", + "hajZcnBWStU=", + "Orkjm9fvOX0=", + "YiZVW2/DJTA=", + "MHNjEmcU/yc=", + "N9FNXxDcVDs=", + "IpqtiLtCShI=", + "IWJJU/JPink=", + "hNhPmyg88jY=", + "1pct7OGGkkI=", + "l9wje+2H0Jg=", + "fkW5vRedzh4=", + "Bt4XkzF4Smc=", + "jsZPxY0xD3I=", + "fH3XJwGQAME=", + "MBYfICUqUII=", + "UB3keZnV78E=", + "yTp/L/LfDZ0=", + "fYKKnLHVgDY=", + "/k+LTrpQXdw=", + "hlZh01w9cdU=", + "CPKD3YpWfmI=", + "00Ek9r6xKCU=", + "CYn7uvpg2AM=", + "WgyGQCjDM68=", + "YJso3URJjlo=" + ], + "Soroban pre-apply removes pre-auth tx signers|protocol version 28" : + [ + "+cR3oq2qY0I=", + "YS3CtbB4LCY=", + "yuxnivFgzdw=", + "NmWV7/t534g=", + "/Kjv95RCny8=", + "pO1qODwjXZY=", + "468KGJPAA6M=", + "zEMED1Ra2FE=", + "6KQ1GRPt6bI=", + "MLNDTE6pvY4=", + "M26lOjlaQIY=", + "mp+KNeldXrU=", + "lUxktDmMJ54=", + "z+4rbTbSb84=", + "CvbvbVNMx4k=", + "5MTOb0MPD+w=", + "MIvv9ify+gw=", + "dSs8vpDFl3c=", + "iw4hEELDw5Q=", + "hajZcnBWStU=", + "Orkjm9fvOX0=", + "YiZVW2/DJTA=", + "MHNjEmcU/yc=", + "N9FNXxDcVDs=", + "IpqtiLtCShI=", + "IWJJU/JPink=", + "hNhPmyg88jY=", + "1pct7OGGkkI=", + "l9wje+2H0Jg=", + "fkW5vRedzh4=", + "Bt4XkzF4Smc=", + "jsZPxY0xD3I=", + "fH3XJwGQAME=", + "MBYfICUqUII=", + "UB3keZnV78E=", + "yTp/L/LfDZ0=", + "fYKKnLHVgDY=", + "/k+LTrpQXdw=", + "hlZh01w9cdU=", + "CPKD3YpWfmI=", + "00Ek9r6xKCU=", + "CYn7uvpg2AM=", + "WgyGQCjDM68=", + "YJso3URJjlo=" + ], "Stellar asset contract transfer with CAP-67 address types" : [ "+cR3oq2qY0I=", @@ -2063,6 +3422,492 @@ "classic payment to soroban fee bump account|protocol version 26" : [ "NTGGdj1offM=", "aHlgICbAAQs=", "1S4Ni3f8c/0=", "wiDaeJY86QQ=" ], "classic payment to soroban fee bump account|protocol version 27" : [ "NTGGdj1offM=", "aHlgICbAAQs=", "1S4Ni3f8c/0=", "wiDaeJY86QQ=" ], "classic payment to soroban fee bump account|protocol version 28" : [ "NTGGdj1offM=", "aHlgICbAAQs=", "1S4Ni3f8c/0=", "wiDaeJY86QQ=" ], + "classic phase bumps sequence of Soroban tx source account|protocol version 20" : + [ + "bKDF6V5IzTo=", + "rykv5BBzl1k=", + "F6F6vr15q1A=", + "4tvlkZFFRX4=", + "HgDUNiXg80U=", + "18FzAAQdQ70=", + "IHdIHFYrMgg=", + "t89GweXHC1I=", + "apCV+hCbZl8=", + "S29r/lxDh70=", + "UDozttyLWyI=", + "QILccI+f94s=", + "4O7/p0uxawc=", + "XwTloGeBtpc=", + "umjGbr+xtOs=", + "BcAvVWg4Jmw=", + "iHA3lSwayZI=", + "MlkJ5OnVDJQ=", + "2duFeYTKLWw=", + "eOmcEBMQdlE=", + "+eyKPmg9JrE=", + "rCZG4VEVwT0=", + "SGL4HT2uYfA=", + "RjhND1kQks0=", + "SMsTDaoGh5s=", + "L0c/XCA0c6E=", + "1YZC+osAer4=", + "vkAUGnoDIWU=", + "YWbsWj8wD90=", + "E99WgsmZhpk=", + "POLMhqkKPgM=", + "ZXG+1zagaV8=", + "Tifr/sJJRZY=", + "jgRoiQihkdw=", + "lFTHEDTm6z4=", + "8PWngVNc9DA=", + "Vm4VrTdRe6M=", + "eZc8pI4CZao=", + "ByEpJiDhfdA=", + "RYE+nBNdNk4=", + "ljCgxBXavyk=", + "KZdTXZqOc9I=", + "ApLOH3i1dlk=", + "wT3WFcxv7ME=", + "9dt+A5yy5C4=", + "v142Cwj8ods=", + "UZD6laODN4k=", + "bcwT4P0WJEM=", + "CNB2siN27BQ=", + "80Aedxva4PY=", + "b6+dMGM8F6I=" + ], + "classic phase bumps sequence of Soroban tx source account|protocol version 21" : + [ + "bKDF6V5IzTo=", + "rykv5BBzl1k=", + "F6F6vr15q1A=", + "4tvlkZFFRX4=", + "HgDUNiXg80U=", + "18FzAAQdQ70=", + "IHdIHFYrMgg=", + "t89GweXHC1I=", + "apCV+hCbZl8=", + "S29r/lxDh70=", + "UDozttyLWyI=", + "QILccI+f94s=", + "4O7/p0uxawc=", + "XwTloGeBtpc=", + "umjGbr+xtOs=", + "BcAvVWg4Jmw=", + "iHA3lSwayZI=", + "MlkJ5OnVDJQ=", + "2duFeYTKLWw=", + "eOmcEBMQdlE=", + "+eyKPmg9JrE=", + "rCZG4VEVwT0=", + "SGL4HT2uYfA=", + "RjhND1kQks0=", + "SMsTDaoGh5s=", + "L0c/XCA0c6E=", + "1YZC+osAer4=", + "vkAUGnoDIWU=", + "YWbsWj8wD90=", + "E99WgsmZhpk=", + "POLMhqkKPgM=", + "ZXG+1zagaV8=", + "Tifr/sJJRZY=", + "jgRoiQihkdw=", + "lFTHEDTm6z4=", + "8PWngVNc9DA=", + "Vm4VrTdRe6M=", + "eZc8pI4CZao=", + "ByEpJiDhfdA=", + "RYE+nBNdNk4=", + "ljCgxBXavyk=", + "KZdTXZqOc9I=", + "ApLOH3i1dlk=", + "wT3WFcxv7ME=", + "9dt+A5yy5C4=", + "v142Cwj8ods=", + "UZD6laODN4k=", + "bcwT4P0WJEM=", + "CNB2siN27BQ=", + "80Aedxva4PY=", + "b6+dMGM8F6I=" + ], + "classic phase bumps sequence of Soroban tx source account|protocol version 22" : + [ + "bKDF6V5IzTo=", + "KauhmrCg434=", + "i2MP9cdPoQA=", + "6eUlCy7NfA8=", + "dv4BVugo9Pg=", + "GlNEXON53WM=", + "5ji87LopYYw=", + "xR65gTZpjQg=", + "ggE2L5NcQm0=", + "bYzJySm4pJw=", + "+J+y9jDJ/Os=", + "BJdhJgqrUvM=", + "Trqgj75G5qQ=", + "ROga9zur2h4=", + "W1o7OisVZ6M=", + "B2myGpMGbks=", + "PUkvsmqFwW0=", + "LgtPg7GlRuk=", + "DOdX56mpKUA=", + "CTbGbLivHyQ=", + "8gJgm7hCCDk=", + "i3ieaS2MRTQ=", + "S4k1pw8IB0o=", + "ZWY8Qtj2yvo=", + "sjKfSsXN0TY=", + "8LdOzje9Jvk=", + "rbMHcrEsWQI=", + "uAQLz05EF7c=", + "DV+jLYgMzyM=", + "sah3Y6KWgwM=", + "kTdgHIVAjbU=", + "d1nR73Vx9+w=", + "mcfXbs12gZY=", + "vicbAJ0rNlE=", + "F4rgNeQ8IRs=", + "E2Z/buvl4So=", + "VMLALQO+jFw=", + "LTR8SHIw5wQ=", + "ak0x6FJy7NE=", + "carMrGgUz0k=", + "Ae7xAPoccL0=", + "SN1v0r7HvJc=", + "Y8XHtnfJ+3I=", + "dau45GFv2C4=", + "03G7p4D2srI=", + "MaHEhKl/EG8=", + "KdQvPSPGfCM=", + "pIZUsigzx1A=", + "1ytaxBFTWaQ=", + "iIt0HAhbgR8=", + "744WeiXt6Lo=" + ], + "classic phase bumps sequence of Soroban tx source account|protocol version 23" : + [ + "+cR3oq2qY0I=", + "xkbxRHKhRz4=", + "0aeGvP7QhNY=", + "qEJFG6MlRu8=", + "XsgH1c4Y8jE=", + "wncOj5kDqQk=", + "mCjhH83YP1E=", + "wo9cdhwcJxQ=", + "8mY+Hh264uc=", + "XsZ8hZZjnZc=", + "m2hJ7TZM74k=", + "dwFqcSZBTmA=", + "gIWzcqTqr6I=", + "2OqlMksDyek=", + "vTCH8GUcwxk=", + "UoLY7lwUbng=", + "brZEf0Qd6Uk=", + "HsXv1mQ86v0=", + "1CJ5MhbEknI=", + "ONTM2yLjg8c=", + "doJkTlm07Q0=", + "KPHoMvuWIlY=", + "/ti+01USiNc=", + "3cI3odnQfmk=", + "iaHUOftB+Uw=", + "OR7cDRnC/F4=", + "PRT9Xl9zkSw=", + "4DPNrmvKB2M=", + "2UTxLEm41VY=", + "FpZZf7OgA1Y=", + "jThf3ILflPk=", + "AHkcqIASpfE=", + "SHVlFzsVemA=", + "i52vHXLCMI8=", + "G/XCJvs11Qs=", + "K0jLgQjq/dM=", + "sCRVpE+JPrg=", + "/og8kIzS74Y=", + "KtLG25hvCe8=", + "y82NDXppduU=", + "NL7L5pMxgVA=", + "ARKssIQ+5d4=", + "NI9WByR7em8=", + "M87swFclhmk=", + "XgoXLGJ95dk=", + "eFL6eJUV78Q=", + "0ycIKH0BnjM=", + "QUBP3y7EVgY=", + "gtKDQKoYDvc=", + "F976AZv+d4c=", + "6ECCc6Afj/Q=" + ], + "classic phase bumps sequence of Soroban tx source account|protocol version 24" : + [ + "+cR3oq2qY0I=", + "xkbxRHKhRz4=", + "0aeGvP7QhNY=", + "qEJFG6MlRu8=", + "XsgH1c4Y8jE=", + "wncOj5kDqQk=", + "mCjhH83YP1E=", + "wo9cdhwcJxQ=", + "8mY+Hh264uc=", + "XsZ8hZZjnZc=", + "m2hJ7TZM74k=", + "dwFqcSZBTmA=", + "gIWzcqTqr6I=", + "2OqlMksDyek=", + "vTCH8GUcwxk=", + "UoLY7lwUbng=", + "brZEf0Qd6Uk=", + "HsXv1mQ86v0=", + "1CJ5MhbEknI=", + "ONTM2yLjg8c=", + "doJkTlm07Q0=", + "KPHoMvuWIlY=", + "/ti+01USiNc=", + "3cI3odnQfmk=", + "iaHUOftB+Uw=", + "OR7cDRnC/F4=", + "PRT9Xl9zkSw=", + "4DPNrmvKB2M=", + "2UTxLEm41VY=", + "FpZZf7OgA1Y=", + "jThf3ILflPk=", + "AHkcqIASpfE=", + "SHVlFzsVemA=", + "i52vHXLCMI8=", + "G/XCJvs11Qs=", + "K0jLgQjq/dM=", + "sCRVpE+JPrg=", + "/og8kIzS74Y=", + "KtLG25hvCe8=", + "y82NDXppduU=", + "NL7L5pMxgVA=", + "ARKssIQ+5d4=", + "NI9WByR7em8=", + "M87swFclhmk=", + "XgoXLGJ95dk=", + "eFL6eJUV78Q=", + "0ycIKH0BnjM=", + "QUBP3y7EVgY=", + "gtKDQKoYDvc=", + "F976AZv+d4c=", + "6ECCc6Afj/Q=" + ], + "classic phase bumps sequence of Soroban tx source account|protocol version 25" : + [ + "+cR3oq2qY0I=", + "xkbxRHKhRz4=", + "0aeGvP7QhNY=", + "qEJFG6MlRu8=", + "XsgH1c4Y8jE=", + "wncOj5kDqQk=", + "mCjhH83YP1E=", + "wo9cdhwcJxQ=", + "8mY+Hh264uc=", + "XsZ8hZZjnZc=", + "m2hJ7TZM74k=", + "dwFqcSZBTmA=", + "gIWzcqTqr6I=", + "2OqlMksDyek=", + "vTCH8GUcwxk=", + "UoLY7lwUbng=", + "brZEf0Qd6Uk=", + "HsXv1mQ86v0=", + "1CJ5MhbEknI=", + "ONTM2yLjg8c=", + "doJkTlm07Q0=", + "KPHoMvuWIlY=", + "/ti+01USiNc=", + "3cI3odnQfmk=", + "iaHUOftB+Uw=", + "OR7cDRnC/F4=", + "PRT9Xl9zkSw=", + "4DPNrmvKB2M=", + "2UTxLEm41VY=", + "FpZZf7OgA1Y=", + "jThf3ILflPk=", + "AHkcqIASpfE=", + "SHVlFzsVemA=", + "i52vHXLCMI8=", + "G/XCJvs11Qs=", + "K0jLgQjq/dM=", + "sCRVpE+JPrg=", + "/og8kIzS74Y=", + "KtLG25hvCe8=", + "y82NDXppduU=", + "NL7L5pMxgVA=", + "ARKssIQ+5d4=", + "NI9WByR7em8=", + "M87swFclhmk=", + "XgoXLGJ95dk=", + "eFL6eJUV78Q=", + "0ycIKH0BnjM=", + "QUBP3y7EVgY=", + "gtKDQKoYDvc=", + "F976AZv+d4c=", + "6ECCc6Afj/Q=" + ], + "classic phase bumps sequence of Soroban tx source account|protocol version 26" : + [ + "+cR3oq2qY0I=", + "9G86+JBHgeg=", + "O9xQyz4xR6g=", + "u0McD0c7NfI=", + "KLJ0pWMwK30=", + "wpwSFCn5q6g=", + "4r+bbTlvQKM=", + "hDtFvwMk1/Q=", + "LgGCds/Fc5Q=", + "7e0mbuTWiVk=", + "V4njnChVt/w=", + "dDEX/hJzsdc=", + "vp8A6fnZmGU=", + "Kye95ZZ5ASA=", + "tjYacA0ikf0=", + "5HuWsvaD6R0=", + "7vAx5m28tlg=", + "xefwYThnA/c=", + "1OAH/Yjpzhw=", + "OnxYYYZ1auA=", + "xyDBkmJ29YM=", + "gJObg3YiHKo=", + "Fasoypcxhw8=", + "Op58Pa68puU=", + "QZzhPrNXWOk=", + "uQCN2iKRws4=", + "4qXGhGs0MEY=", + "BhD8QBYtJck=", + "LMq6nGWKQQs=", + "qUCY5Yht2nE=", + "+yQ+NHtpKwc=", + "odMr5HEuZ1c=", + "YfdsmGxer0Y=", + "6nHthXP12Is=", + "cBu0hV6kpKM=", + "JxBRDvkPK+c=", + "ndLjEW0PIeY=", + "5m3OBkeUZEI=", + "JE4YlOvb+as=", + "5tlNHzS4jsY=", + "UJ8X82k1Rwc=", + "q5MMlD4ueJk=", + "wuk/cKq2M60=", + "zzCTevwh32E=", + "um3lT2lgzlk=", + "7TXOxf6SKR8=", + "WXrk89lXDpE=", + "PT44jKZRsK0=", + "N8EpdAB4Iy0=", + "Bk0zP1Njm5M=", + "RG5lu2TSBAE=" + ], + "classic phase bumps sequence of Soroban tx source account|protocol version 27" : + [ + "+cR3oq2qY0I=", + "9G86+JBHgeg=", + "O9xQyz4xR6g=", + "u0McD0c7NfI=", + "KLJ0pWMwK30=", + "wpwSFCn5q6g=", + "4r+bbTlvQKM=", + "hDtFvwMk1/Q=", + "LgGCds/Fc5Q=", + "7e0mbuTWiVk=", + "V4njnChVt/w=", + "dDEX/hJzsdc=", + "vp8A6fnZmGU=", + "Kye95ZZ5ASA=", + "tjYacA0ikf0=", + "5HuWsvaD6R0=", + "7vAx5m28tlg=", + "xefwYThnA/c=", + "1OAH/Yjpzhw=", + "OnxYYYZ1auA=", + "xyDBkmJ29YM=", + "gJObg3YiHKo=", + "Fasoypcxhw8=", + "Op58Pa68puU=", + "QZzhPrNXWOk=", + "uQCN2iKRws4=", + "4qXGhGs0MEY=", + "BhD8QBYtJck=", + "LMq6nGWKQQs=", + "qUCY5Yht2nE=", + "+yQ+NHtpKwc=", + "odMr5HEuZ1c=", + "YfdsmGxer0Y=", + "6nHthXP12Is=", + "cBu0hV6kpKM=", + "JxBRDvkPK+c=", + "ndLjEW0PIeY=", + "5m3OBkeUZEI=", + "JE4YlOvb+as=", + "5tlNHzS4jsY=", + "UJ8X82k1Rwc=", + "q5MMlD4ueJk=", + "wuk/cKq2M60=", + "zzCTevwh32E=", + "um3lT2lgzlk=", + "7TXOxf6SKR8=", + "WXrk89lXDpE=", + "PT44jKZRsK0=", + "N8EpdAB4Iy0=", + "Bk0zP1Njm5M=", + "RG5lu2TSBAE=" + ], + "classic phase bumps sequence of Soroban tx source account|protocol version 28" : + [ + "+cR3oq2qY0I=", + "9G86+JBHgeg=", + "O9xQyz4xR6g=", + "u0McD0c7NfI=", + "KLJ0pWMwK30=", + "wpwSFCn5q6g=", + "4r+bbTlvQKM=", + "hDtFvwMk1/Q=", + "LgGCds/Fc5Q=", + "7e0mbuTWiVk=", + "V4njnChVt/w=", + "dDEX/hJzsdc=", + "vp8A6fnZmGU=", + "Kye95ZZ5ASA=", + "tjYacA0ikf0=", + "5HuWsvaD6R0=", + "7vAx5m28tlg=", + "xefwYThnA/c=", + "1OAH/Yjpzhw=", + "OnxYYYZ1auA=", + "xyDBkmJ29YM=", + "gJObg3YiHKo=", + "Fasoypcxhw8=", + "Op58Pa68puU=", + "QZzhPrNXWOk=", + "uQCN2iKRws4=", + "4qXGhGs0MEY=", + "BhD8QBYtJck=", + "LMq6nGWKQQs=", + "qUCY5Yht2nE=", + "+yQ+NHtpKwc=", + "odMr5HEuZ1c=", + "YfdsmGxer0Y=", + "6nHthXP12Is=", + "cBu0hV6kpKM=", + "JxBRDvkPK+c=", + "ndLjEW0PIeY=", + "5m3OBkeUZEI=", + "JE4YlOvb+as=", + "5tlNHzS4jsY=", + "UJ8X82k1Rwc=", + "q5MMlD4ueJk=", + "wuk/cKq2M60=", + "zzCTevwh32E=", + "um3lT2lgzlk=", + "7TXOxf6SKR8=", + "WXrk89lXDpE=", + "PT44jKZRsK0=", + "N8EpdAB4Iy0=", + "Bk0zP1Njm5M=", + "RG5lu2TSBAE=" + ], "classic phase bumps sequence of soroban source account|protocol version 20" : [ "NTGGdj1offM=", "FVbub/Ss/Xg=", "9Rpfyyb4ewI=" ], "classic phase bumps sequence of soroban source account|protocol version 21" : [ "NTGGdj1offM=", "FVbub/Ss/Xg=", "9Rpfyyb4ewI=" ], "classic phase bumps sequence of soroban source account|protocol version 22" : [ "NTGGdj1offM=", "CTLGLR5LLHY=", "xXpwdFz6+Uc=" ], diff --git a/test-tx-meta-baseline-next/InvokeHostFunctionTests.json b/test-tx-meta-baseline-next/InvokeHostFunctionTests.json index 6dc67843fa..5c7752e4c9 100644 --- a/test-tx-meta-baseline-next/InvokeHostFunctionTests.json +++ b/test-tx-meta-baseline-next/InvokeHostFunctionTests.json @@ -1341,6 +1341,1516 @@ "Soroban non-refundable resource fees are stable|protocol version 27" : [ "+cR3oq2qY0I=" ], "Soroban non-refundable resource fees are stable|protocol version 28" : [ "+cR3oq2qY0I=" ], "Soroban non-refundable resource fees are stable|protocol version 29" : [ "+cR3oq2qY0I=" ], + "Soroban operation source created and removed in classic phase|protocol version 20" : + [ + "bKDF6V5IzTo=", + "ocDSzYTKRxc=", + "x7pY9qZs+ww=", + "pl7yH4GxRFQ=", + "x0yt/kMxerA=", + "2J2lcePHCl0=", + "y+l/7b5BtHw=", + "PJoWBQjHG+8=", + "fFul/ank5Ug=", + "Y2wC5nPoMrw=", + "npK31zqyR9k=", + "2bEpfIKOW8M=", + "2fKoTmSijT0=", + "bLLYYTXiK5M=", + "zd2B7j7hYm8=", + "bQBW+8LXBpU=", + "DaCAag7HYyA=", + "Ls987ixcjsk=", + "RVwvcamX2zQ=", + "pK6dZrXn7tc=", + "KwX2ieEOIM4=", + "KssrhNmE6gQ=", + "ECMbl1rfvkI=", + "mWm3w129yYA=", + "T5TGEqorPA0=", + "C5NyNd3cL+M=", + "IN9qoUwnjl4=", + "qWHwM+Cwsnw=", + "MjVTRJBTTnE=", + "elVHZwI2XCU=", + "+9FSiKk7fXI=", + "Ci0AdmLRgx4=", + "0OSPUrD+VPE=", + "zmqEUhSPbw0=", + "1M+NpduwYMw=", + "iTxGjPm3S4c=", + "zDjM6F9PYIA=", + "BIGclaLIwGk=", + "NGduAG2a87k=", + "fpIm6VOnXec=", + "PXWzTCHXINU=", + "CF0dvEe/zEU=", + "EDPq9VN9FBs=", + "tAjCk6bSL5M=", + "ip/VIOG9QqY=", + "TbWAkcq8y0M=", + "0RXV1B5La98=", + "leaGw26oZuc=", + "VclaMVMhpFo=", + "qQtstBDf9Jc=", + "KONtDnAcOy0=", + "Wl4HOBTvK/Q=", + "+7pNvIZg3Fc=", + "TBn/l3NsyPg=", + "HLL52HhsHGg=", + "RmAB3vLLhWo=", + "N9x9vEZ2fPU=", + "qDuKIhch5f0=", + "2ORdTBzlKco=", + "QIlfJESQTCQ=", + "CrkmaOSPafE=", + "Ytgd0ScwB00=", + "MQFZ4YH3pw8=", + "OUxmL9js4DU=", + "+K4P8h5TqAg=", + "hGNfri2SzJE=", + "B9iRqTncc5U=", + "nEKmWZ75XcQ=", + "XPV5D6izRXk=", + "qlRRw8u4FPc=", + "60y/MYC7xzo=", + "2sxy6PueHq4=", + "XSlCJFxSxIQ=", + "KZcoFOZsArA=", + "y9+jCTKPB8U=", + "gZ8axkqf1gs=", + "jFtqLKJ1B8g=", + "KmD8ngIQNVE=", + "Sz8WZKdGgZ4=", + "Kk/VI45ftPA=", + "SE+6yhTVW88=", + "A/K6mCMMTEM=", + "k1CKfI5Z6Ts=", + "xN144MqXL1o=", + "J3wueY7hFUA=", + "JqRgmBk8WVk=", + "bGk7w5/T5ow=", + "3lJGKmoswBI=", + "g1/d5UgqoGE=", + "Jc0cnsXz3fo=", + "vGxWtpicBnw=", + "XobVeJMz94g=", + "TuCK/UVOHOc=", + "GwZy2YmjNdc=", + "zxgOFDpX5PM=", + "wMUL1rXGiFU=", + "aokk0zAKxqw=", + "pub5+5x7WKs=", + "0pzgjTY0KOA=", + "pSX8z9KMKy8=", + "GSx7GUS4Kpo=" + ], + "Soroban operation source created and removed in classic phase|protocol version 21" : + [ + "bKDF6V5IzTo=", + "ocDSzYTKRxc=", + "x7pY9qZs+ww=", + "pl7yH4GxRFQ=", + "x0yt/kMxerA=", + "2J2lcePHCl0=", + "y+l/7b5BtHw=", + "PJoWBQjHG+8=", + "fFul/ank5Ug=", + "Y2wC5nPoMrw=", + "npK31zqyR9k=", + "2bEpfIKOW8M=", + "2fKoTmSijT0=", + "bLLYYTXiK5M=", + "zd2B7j7hYm8=", + "bQBW+8LXBpU=", + "DaCAag7HYyA=", + "Ls987ixcjsk=", + "RVwvcamX2zQ=", + "pK6dZrXn7tc=", + "KwX2ieEOIM4=", + "KssrhNmE6gQ=", + "ECMbl1rfvkI=", + "mWm3w129yYA=", + "T5TGEqorPA0=", + "C5NyNd3cL+M=", + "IN9qoUwnjl4=", + "qWHwM+Cwsnw=", + "MjVTRJBTTnE=", + "elVHZwI2XCU=", + "+9FSiKk7fXI=", + "Ci0AdmLRgx4=", + "0OSPUrD+VPE=", + "zmqEUhSPbw0=", + "1M+NpduwYMw=", + "iTxGjPm3S4c=", + "zDjM6F9PYIA=", + "BIGclaLIwGk=", + "NGduAG2a87k=", + "fpIm6VOnXec=", + "PXWzTCHXINU=", + "CF0dvEe/zEU=", + "EDPq9VN9FBs=", + "tAjCk6bSL5M=", + "ip/VIOG9QqY=", + "TbWAkcq8y0M=", + "0RXV1B5La98=", + "leaGw26oZuc=", + "VclaMVMhpFo=", + "qQtstBDf9Jc=", + "KONtDnAcOy0=", + "Wl4HOBTvK/Q=", + "+7pNvIZg3Fc=", + "TBn/l3NsyPg=", + "HLL52HhsHGg=", + "RmAB3vLLhWo=", + "N9x9vEZ2fPU=", + "qDuKIhch5f0=", + "2ORdTBzlKco=", + "QIlfJESQTCQ=", + "CrkmaOSPafE=", + "Ytgd0ScwB00=", + "MQFZ4YH3pw8=", + "OUxmL9js4DU=", + "+K4P8h5TqAg=", + "hGNfri2SzJE=", + "B9iRqTncc5U=", + "nEKmWZ75XcQ=", + "XPV5D6izRXk=", + "qlRRw8u4FPc=", + "60y/MYC7xzo=", + "2sxy6PueHq4=", + "XSlCJFxSxIQ=", + "KZcoFOZsArA=", + "y9+jCTKPB8U=", + "gZ8axkqf1gs=", + "jFtqLKJ1B8g=", + "KmD8ngIQNVE=", + "Sz8WZKdGgZ4=", + "Kk/VI45ftPA=", + "SE+6yhTVW88=", + "A/K6mCMMTEM=", + "k1CKfI5Z6Ts=", + "xN144MqXL1o=", + "J3wueY7hFUA=", + "JqRgmBk8WVk=", + "bGk7w5/T5ow=", + "3lJGKmoswBI=", + "g1/d5UgqoGE=", + "Jc0cnsXz3fo=", + "vGxWtpicBnw=", + "XobVeJMz94g=", + "TuCK/UVOHOc=", + "GwZy2YmjNdc=", + "zxgOFDpX5PM=", + "wMUL1rXGiFU=", + "aokk0zAKxqw=", + "pub5+5x7WKs=", + "0pzgjTY0KOA=", + "pSX8z9KMKy8=", + "GSx7GUS4Kpo=" + ], + "Soroban operation source created and removed in classic phase|protocol version 22" : + [ + "bKDF6V5IzTo=", + "5ayMpvekkrI=", + "KpwqxixB1QU=", + "ninzTyfpD8c=", + "0xhX4klidW0=", + "DriP1FYyxvU=", + "syrbRtKc6Og=", + "s7cRiScIDRs=", + "ieMtr3xVwzs=", + "84z/mwe7bLw=", + "bolWl1e2MiI=", + "gSe0PUmaBeE=", + "XWGUN5oRCLQ=", + "giqBXEVckLs=", + "7tc351zel1s=", + "5q0dPSZe6xs=", + "108jh0FFUwQ=", + "YWPZ3Z0AALE=", + "NhbQs97HRHg=", + "4dsyL5+6R3c=", + "/i24ku1BXIk=", + "Dx/7Y1hH1JM=", + "2TqSGPO22xI=", + "5/Z6E0KBrqg=", + "ViUuIKaofeI=", + "leJeeGnjaUY=", + "3CphD5FSwJM=", + "+iy3NPt3ytA=", + "R/VTFpMVli4=", + "y+x7zHxNBmA=", + "4Yi2qwwZIa8=", + "/czKal/hKLc=", + "wCzZUJdypmU=", + "OUQvX9unks8=", + "blcnSidcFAA=", + "iKzCzPo0d6E=", + "wlGGD9kFHsU=", + "usj4INAIQLE=", + "KYhzuloANJg=", + "yBH0FljHOAE=", + "2pEDdVBCPUg=", + "RLQSuE0VRKQ=", + "ltRb1/FUQ9I=", + "eCwc/WSUNCw=", + "l2jIldD5lKI=", + "3GhYy8sX54Q=", + "LcaC2CLknZo=", + "RSb9KWtnQm8=", + "pmfIWlBW6iA=", + "/x4WW5L+tCQ=", + "5fkBtnXRe/0=", + "z62MBXihCyY=", + "CNZp6sFSoXM=", + "ZGfMkEu8Ulw=", + "gxsZCR7REC4=", + "FzKKFViimiQ=", + "2fjvelCaJrE=", + "D6jj6tAnifM=", + "y6I8zVgn67I=", + "fFxwT4onCuo=", + "zmTIbczXn0k=", + "MiTslt8iyzM=", + "jfpyVY+vrMU=", + "azA+s6oxbOg=", + "ZmUPbn7/xc0=", + "ZucccJTapDk=", + "ieWc2QQjYwg=", + "iFmCgHZLhRI=", + "4kpdCiyd6+U=", + "zkWAzUNq6g0=", + "KlkrtrwMZSA=", + "GSXS/naFC7o=", + "UwQSKeqWeuE=", + "FBOP79OXINA=", + "EcchsVBcwjA=", + "2C41h+WmaFc=", + "GstNub02AMU=", + "YNufYkDHgA8=", + "JpAFUvNARFM=", + "jPlMpkSbnnk=", + "lV1IdI9sdOI=", + "GftzwB+pCTQ=", + "hR5d6pVHc40=", + "qLHKxSvGU3E=", + "t721A004I+I=", + "HGtmt02Q6JU=", + "iaQzSi66wcs=", + "rbowyW4jCBs=", + "WJs4iuxCdaU=", + "CGmvdBp/5zs=", + "AMzWei96FSc=", + "G5oFpONpPZ0=", + "mnUDr5XOkiQ=", + "UBrHhLuwCS4=", + "tXDs520pIXI=", + "0gIZ9k20dos=", + "sb6iULDIZIk=", + "/9TZzHvXMJE=", + "U6/5/AUYgvg=", + "xIdb9LXLdE0=", + "LXkHFxv8xCI=" + ], + "Soroban operation source created and removed in classic phase|protocol version 23" : + [ + "+cR3oq2qY0I=", + "nz9v1PzpgfI=", + "hohUpT/ADqk=", + "bvjRdiFGfTM=", + "fj7GNFRwzCo=", + "nXkts5zEKgU=", + "ZiQQXHUPS5U=", + "TC5BKqdd+B4=", + "WXaQReLYO5k=", + "LPFg+MEaSQg=", + "Asgvsb6w7Q0=", + "wBJrQJNAkmE=", + "wrjwucM/Zow=", + "e4novqniZ+g=", + "Ibm21qxpI7s=", + "UextlVZsjnM=", + "0n7dMegdZ3A=", + "11XOGuZ3Xug=", + "WV1Ho30vGvc=", + "SnZLQYvZxfA=", + "M5BZn04JJsE=", + "uMeTcfXMq5A=", + "ziO+HFD3yYY=", + "kGjdPxb4AWs=", + "lpn9djEUn2M=", + "baWzcJ+nmdg=", + "UF9UKXHjJDc=", + "y+4FEa0UPZo=", + "uqFChe3y8rQ=", + "WZZHQawuopM=", + "76Tytga3SWQ=", + "oBXvRd8lj7E=", + "2UIvJMrAYkA=", + "Lu4HE/7VmrU=", + "jax2QwuMLvA=", + "icIkd6qRbPg=", + "KtZfjBYf/V8=", + "huqkxOC2LoA=", + "Jt3tgC+PClI=", + "+UcB9Bo0/hg=", + "zJRsdNMwMAc=", + "MLrMqsvOziE=", + "6vK4A0eFd9c=", + "/dBnJEv5mug=", + "T1D/fXHUY8k=", + "wbI5JOgzavw=", + "saayh6lFoy8=", + "1zCrRETesEE=", + "4aeZ3nDW2H8=", + "2EVgJD6hMxE=", + "0HYOxKmFbXc=", + "2GH3CCviOcs=", + "9kcvbtcyje8=", + "tPZwrweWQ8c=", + "bujRoPDgmrM=", + "1VayL7z/03M=", + "WzdN95wnWC4=", + "cOk9uPy8kSQ=", + "0yMEY5GgOiw=", + "80ul4iYlg1A=", + "Kvi1c6UeFIs=", + "M+BKIBuPOS4=", + "Edzb65HK+ao=", + "xNFD7cNA9PE=", + "+3bDFSiTswE=", + "x43oIP5QKZ0=", + "T1z4oGc8vqc=", + "mlW6FqrcYSI=", + "hQEjDayPI9E=", + "C1hgh4vaYvk=", + "rm96fjiAVTE=", + "C5N7gq8301Q=", + "v4o9l1ZMoD0=", + "gdJY9xf3I/I=", + "0FvLTFyx56c=", + "RihwpLL/n/E=", + "Oo2rLuCHGr4=", + "ldJr6nmpvUU=", + "4EErS4HHw7k=", + "oPYYvxcsZY4=", + "Jzmx0gWrqGQ=", + "siQ/3Hh+++U=", + "Q2Q6XWtaQCQ=", + "Ot03scyos0k=", + "rOcJh/4Ez/Y=", + "dKxQ91Mrvn8=", + "LBpIyth3wYk=", + "wmV5IzZY1pM=", + "qIRuVOi6tQM=", + "k2ZdBvYWWi8=", + "wNwIp33jJMI=", + "Ma1rDLoDtvA=", + "XpM0H5kfBLs=", + "xlufJjbEuYg=", + "TFeOtOGRmt8=", + "Eclf3i5w7gg=", + "5aKvPQAX7tE=", + "GLxznahSj+E=", + "4L5PdJ7n/qo=", + "i23Oql77Y6U=", + "cOJDuiZkHlg=" + ], + "Soroban operation source created and removed in classic phase|protocol version 24" : + [ + "+cR3oq2qY0I=", + "nz9v1PzpgfI=", + "hohUpT/ADqk=", + "bvjRdiFGfTM=", + "fj7GNFRwzCo=", + "nXkts5zEKgU=", + "ZiQQXHUPS5U=", + "TC5BKqdd+B4=", + "WXaQReLYO5k=", + "LPFg+MEaSQg=", + "Asgvsb6w7Q0=", + "wBJrQJNAkmE=", + "wrjwucM/Zow=", + "e4novqniZ+g=", + "Ibm21qxpI7s=", + "UextlVZsjnM=", + "0n7dMegdZ3A=", + "11XOGuZ3Xug=", + "WV1Ho30vGvc=", + "SnZLQYvZxfA=", + "M5BZn04JJsE=", + "uMeTcfXMq5A=", + "ziO+HFD3yYY=", + "kGjdPxb4AWs=", + "lpn9djEUn2M=", + "baWzcJ+nmdg=", + "UF9UKXHjJDc=", + "y+4FEa0UPZo=", + "uqFChe3y8rQ=", + "WZZHQawuopM=", + "76Tytga3SWQ=", + "oBXvRd8lj7E=", + "2UIvJMrAYkA=", + "Lu4HE/7VmrU=", + "jax2QwuMLvA=", + "icIkd6qRbPg=", + "KtZfjBYf/V8=", + "huqkxOC2LoA=", + "Jt3tgC+PClI=", + "+UcB9Bo0/hg=", + "zJRsdNMwMAc=", + "MLrMqsvOziE=", + "6vK4A0eFd9c=", + "/dBnJEv5mug=", + "T1D/fXHUY8k=", + "wbI5JOgzavw=", + "saayh6lFoy8=", + "1zCrRETesEE=", + "4aeZ3nDW2H8=", + "2EVgJD6hMxE=", + "0HYOxKmFbXc=", + "2GH3CCviOcs=", + "9kcvbtcyje8=", + "tPZwrweWQ8c=", + "bujRoPDgmrM=", + "1VayL7z/03M=", + "WzdN95wnWC4=", + "cOk9uPy8kSQ=", + "0yMEY5GgOiw=", + "80ul4iYlg1A=", + "Kvi1c6UeFIs=", + "M+BKIBuPOS4=", + "Edzb65HK+ao=", + "xNFD7cNA9PE=", + "+3bDFSiTswE=", + "x43oIP5QKZ0=", + "T1z4oGc8vqc=", + "mlW6FqrcYSI=", + "hQEjDayPI9E=", + "C1hgh4vaYvk=", + "rm96fjiAVTE=", + "C5N7gq8301Q=", + "v4o9l1ZMoD0=", + "gdJY9xf3I/I=", + "0FvLTFyx56c=", + "RihwpLL/n/E=", + "Oo2rLuCHGr4=", + "ldJr6nmpvUU=", + "4EErS4HHw7k=", + "oPYYvxcsZY4=", + "Jzmx0gWrqGQ=", + "siQ/3Hh+++U=", + "Q2Q6XWtaQCQ=", + "Ot03scyos0k=", + "rOcJh/4Ez/Y=", + "dKxQ91Mrvn8=", + "LBpIyth3wYk=", + "wmV5IzZY1pM=", + "qIRuVOi6tQM=", + "k2ZdBvYWWi8=", + "wNwIp33jJMI=", + "Ma1rDLoDtvA=", + "XpM0H5kfBLs=", + "xlufJjbEuYg=", + "TFeOtOGRmt8=", + "Eclf3i5w7gg=", + "5aKvPQAX7tE=", + "GLxznahSj+E=", + "4L5PdJ7n/qo=", + "i23Oql77Y6U=", + "cOJDuiZkHlg=" + ], + "Soroban operation source created and removed in classic phase|protocol version 25" : + [ + "+cR3oq2qY0I=", + "nz9v1PzpgfI=", + "hohUpT/ADqk=", + "bvjRdiFGfTM=", + "fj7GNFRwzCo=", + "nXkts5zEKgU=", + "ZiQQXHUPS5U=", + "TC5BKqdd+B4=", + "WXaQReLYO5k=", + "LPFg+MEaSQg=", + "Asgvsb6w7Q0=", + "wBJrQJNAkmE=", + "wrjwucM/Zow=", + "e4novqniZ+g=", + "Ibm21qxpI7s=", + "UextlVZsjnM=", + "0n7dMegdZ3A=", + "11XOGuZ3Xug=", + "WV1Ho30vGvc=", + "SnZLQYvZxfA=", + "M5BZn04JJsE=", + "uMeTcfXMq5A=", + "ziO+HFD3yYY=", + "kGjdPxb4AWs=", + "lpn9djEUn2M=", + "baWzcJ+nmdg=", + "UF9UKXHjJDc=", + "y+4FEa0UPZo=", + "uqFChe3y8rQ=", + "WZZHQawuopM=", + "76Tytga3SWQ=", + "oBXvRd8lj7E=", + "2UIvJMrAYkA=", + "Lu4HE/7VmrU=", + "jax2QwuMLvA=", + "icIkd6qRbPg=", + "KtZfjBYf/V8=", + "huqkxOC2LoA=", + "Jt3tgC+PClI=", + "+UcB9Bo0/hg=", + "zJRsdNMwMAc=", + "MLrMqsvOziE=", + "6vK4A0eFd9c=", + "/dBnJEv5mug=", + "T1D/fXHUY8k=", + "wbI5JOgzavw=", + "saayh6lFoy8=", + "1zCrRETesEE=", + "4aeZ3nDW2H8=", + "2EVgJD6hMxE=", + "0HYOxKmFbXc=", + "2GH3CCviOcs=", + "9kcvbtcyje8=", + "tPZwrweWQ8c=", + "bujRoPDgmrM=", + "1VayL7z/03M=", + "WzdN95wnWC4=", + "cOk9uPy8kSQ=", + "0yMEY5GgOiw=", + "80ul4iYlg1A=", + "Kvi1c6UeFIs=", + "M+BKIBuPOS4=", + "Edzb65HK+ao=", + "xNFD7cNA9PE=", + "+3bDFSiTswE=", + "x43oIP5QKZ0=", + "T1z4oGc8vqc=", + "mlW6FqrcYSI=", + "hQEjDayPI9E=", + "C1hgh4vaYvk=", + "rm96fjiAVTE=", + "C5N7gq8301Q=", + "v4o9l1ZMoD0=", + "gdJY9xf3I/I=", + "0FvLTFyx56c=", + "RihwpLL/n/E=", + "Oo2rLuCHGr4=", + "ldJr6nmpvUU=", + "4EErS4HHw7k=", + "oPYYvxcsZY4=", + "Jzmx0gWrqGQ=", + "siQ/3Hh+++U=", + "Q2Q6XWtaQCQ=", + "Ot03scyos0k=", + "rOcJh/4Ez/Y=", + "dKxQ91Mrvn8=", + "LBpIyth3wYk=", + "wmV5IzZY1pM=", + "qIRuVOi6tQM=", + "k2ZdBvYWWi8=", + "wNwIp33jJMI=", + "Ma1rDLoDtvA=", + "XpM0H5kfBLs=", + "xlufJjbEuYg=", + "TFeOtOGRmt8=", + "Eclf3i5w7gg=", + "5aKvPQAX7tE=", + "GLxznahSj+E=", + "4L5PdJ7n/qo=", + "i23Oql77Y6U=", + "cOJDuiZkHlg=" + ], + "Soroban operation source created and removed in classic phase|protocol version 26" : + [ + "+cR3oq2qY0I=", + "crbAf4Ea6kg=", + "iqTvm0y1Iow=", + "6pO37nDn84A=", + "wRmit6MBjrQ=", + "oVpHg7RRBlk=", + "Aid/7xaOTR0=", + "bbMlrMP+qlI=", + "fdD5DjwwiOQ=", + "dLar7fQTVJQ=", + "rqoO87FasNU=", + "7aj+KvKQXwY=", + "mruvECGnEyU=", + "5/S0IcIxBNI=", + "Ze0l8W+lT7M=", + "UXimakEXASc=", + "ay1R0+swdfw=", + "VEV8xNmTE1Q=", + "ohDWJIUApRk=", + "LeIaM+VHBWo=", + "XXB+LJhuCO0=", + "Z1SOcuDsn3U=", + "1j7QdwSbjPY=", + "OSoLJeVtHOg=", + "AoGTPhaZJy4=", + "DWRtqn3dZGo=", + "tuOemI/6/Vw=", + "rpw06OPQ4YQ=", + "yq78L4dQbBA=", + "pS8F28DUKnk=", + "R7aUoGb7MyA=", + "CzSTC9mDsx0=", + "9pl1fjzk41A=", + "czFb8xk728c=", + "67+TTjrwYLE=", + "yRwsYheUXUo=", + "E8kX1Yrj71M=", + "8fn8vzThmDI=", + "WBTucsatdSY=", + "cLZ5fozibbY=", + "sVBaA3CDFfU=", + "zLz7Tfj0ef4=", + "kbGW6RV1SW4=", + "NStKgU3nvsw=", + "jyRmbaA22SU=", + "5niYf+Sj4qo=", + "IYqTe+q5124=", + "b/asdCehmbs=", + "gGW69QBxFJA=", + "sjeXqxl36Eg=", + "ga0wPKIyWeU=", + "o8iLtP9jdUY=", + "3C5DxayMoqQ=", + "oyRATLVeDdA=", + "cBSvG7Y4Lp4=", + "qeYaEXAXYc0=", + "jxNlURg20AM=", + "lwAZn9dNSjg=", + "+ijl0DL07ME=", + "RyPBfGhCxpY=", + "3M0rqTnOqhE=", + "inHVXSTA278=", + "qz9ZiOB41eY=", + "3G2x7xoeHOU=", + "IkoYpaPqPrw=", + "j0WdJcdb+BE=", + "CeAemj67nZM=", + "kVpPjbTS/jo=", + "XEF5tUmLfjU=", + "0Bz/VETaobk=", + "7l4k+XN3Xno=", + "4imVULlTzUw=", + "Aea+sYr85WQ=", + "8PaqAkHF0e0=", + "QKC5AS+jhCM=", + "pEWBhRBgCz8=", + "fMxyYZquhAA=", + "8xxIbp8nLLY=", + "AYZkATYEJOk=", + "XKrbs5lBIcw=", + "6KVpqakZPiw=", + "OeAMSZ0Gc/U=", + "MhgNQLzMD4Q=", + "NK6v8hyAKGY=", + "QuMvE+2NFIU=", + "1G81YWIYlgo=", + "bKdk2OVRSDo=", + "maokePOWsQI=", + "CdgngSmnixI=", + "IcLAQnataC4=", + "vr2lY+iMklA=", + "CYO7w60bDzE=", + "rl+0lPOoBHk=", + "qHcIz27YW90=", + "yvViF0w9JZs=", + "dNuw4Jq10aY=", + "Bk//FNsJ//8=", + "8JWNtTVb5mE=", + "v64n9RwfQRM=", + "q1W0Fxx9Nxw=", + "0u+ay1fNNYE=" + ], + "Soroban operation source created and removed in classic phase|protocol version 27" : + [ + "+cR3oq2qY0I=", + "crbAf4Ea6kg=", + "iqTvm0y1Iow=", + "6pO37nDn84A=", + "wRmit6MBjrQ=", + "oVpHg7RRBlk=", + "Aid/7xaOTR0=", + "bbMlrMP+qlI=", + "fdD5DjwwiOQ=", + "dLar7fQTVJQ=", + "rqoO87FasNU=", + "7aj+KvKQXwY=", + "mruvECGnEyU=", + "5/S0IcIxBNI=", + "Ze0l8W+lT7M=", + "UXimakEXASc=", + "ay1R0+swdfw=", + "VEV8xNmTE1Q=", + "ohDWJIUApRk=", + "LeIaM+VHBWo=", + "XXB+LJhuCO0=", + "Z1SOcuDsn3U=", + "1j7QdwSbjPY=", + "OSoLJeVtHOg=", + "AoGTPhaZJy4=", + "DWRtqn3dZGo=", + "tuOemI/6/Vw=", + "rpw06OPQ4YQ=", + "yq78L4dQbBA=", + "pS8F28DUKnk=", + "R7aUoGb7MyA=", + "CzSTC9mDsx0=", + "9pl1fjzk41A=", + "czFb8xk728c=", + "67+TTjrwYLE=", + "yRwsYheUXUo=", + "E8kX1Yrj71M=", + "8fn8vzThmDI=", + "WBTucsatdSY=", + "cLZ5fozibbY=", + "sVBaA3CDFfU=", + "zLz7Tfj0ef4=", + "kbGW6RV1SW4=", + "NStKgU3nvsw=", + "jyRmbaA22SU=", + "5niYf+Sj4qo=", + "IYqTe+q5124=", + "b/asdCehmbs=", + "gGW69QBxFJA=", + "sjeXqxl36Eg=", + "ga0wPKIyWeU=", + "o8iLtP9jdUY=", + "3C5DxayMoqQ=", + "oyRATLVeDdA=", + "cBSvG7Y4Lp4=", + "qeYaEXAXYc0=", + "jxNlURg20AM=", + "lwAZn9dNSjg=", + "+ijl0DL07ME=", + "RyPBfGhCxpY=", + "3M0rqTnOqhE=", + "inHVXSTA278=", + "qz9ZiOB41eY=", + "3G2x7xoeHOU=", + "IkoYpaPqPrw=", + "j0WdJcdb+BE=", + "CeAemj67nZM=", + "kVpPjbTS/jo=", + "XEF5tUmLfjU=", + "0Bz/VETaobk=", + "7l4k+XN3Xno=", + "4imVULlTzUw=", + "Aea+sYr85WQ=", + "8PaqAkHF0e0=", + "QKC5AS+jhCM=", + "pEWBhRBgCz8=", + "fMxyYZquhAA=", + "8xxIbp8nLLY=", + "AYZkATYEJOk=", + "XKrbs5lBIcw=", + "6KVpqakZPiw=", + "OeAMSZ0Gc/U=", + "MhgNQLzMD4Q=", + "NK6v8hyAKGY=", + "QuMvE+2NFIU=", + "1G81YWIYlgo=", + "bKdk2OVRSDo=", + "maokePOWsQI=", + "CdgngSmnixI=", + "IcLAQnataC4=", + "vr2lY+iMklA=", + "CYO7w60bDzE=", + "rl+0lPOoBHk=", + "qHcIz27YW90=", + "yvViF0w9JZs=", + "dNuw4Jq10aY=", + "Bk//FNsJ//8=", + "8JWNtTVb5mE=", + "v64n9RwfQRM=", + "q1W0Fxx9Nxw=", + "0u+ay1fNNYE=" + ], + "Soroban operation source created and removed in classic phase|protocol version 28" : + [ + "+cR3oq2qY0I=", + "crbAf4Ea6kg=", + "iqTvm0y1Iow=", + "6pO37nDn84A=", + "wRmit6MBjrQ=", + "oVpHg7RRBlk=", + "Aid/7xaOTR0=", + "bbMlrMP+qlI=", + "fdD5DjwwiOQ=", + "dLar7fQTVJQ=", + "rqoO87FasNU=", + "7aj+KvKQXwY=", + "mruvECGnEyU=", + "5/S0IcIxBNI=", + "Ze0l8W+lT7M=", + "UXimakEXASc=", + "ay1R0+swdfw=", + "VEV8xNmTE1Q=", + "ohDWJIUApRk=", + "LeIaM+VHBWo=", + "XXB+LJhuCO0=", + "Z1SOcuDsn3U=", + "1j7QdwSbjPY=", + "OSoLJeVtHOg=", + "AoGTPhaZJy4=", + "DWRtqn3dZGo=", + "tuOemI/6/Vw=", + "rpw06OPQ4YQ=", + "yq78L4dQbBA=", + "pS8F28DUKnk=", + "R7aUoGb7MyA=", + "CzSTC9mDsx0=", + "9pl1fjzk41A=", + "czFb8xk728c=", + "67+TTjrwYLE=", + "yRwsYheUXUo=", + "E8kX1Yrj71M=", + "8fn8vzThmDI=", + "WBTucsatdSY=", + "cLZ5fozibbY=", + "sVBaA3CDFfU=", + "zLz7Tfj0ef4=", + "kbGW6RV1SW4=", + "NStKgU3nvsw=", + "jyRmbaA22SU=", + "5niYf+Sj4qo=", + "IYqTe+q5124=", + "b/asdCehmbs=", + "gGW69QBxFJA=", + "sjeXqxl36Eg=", + "ga0wPKIyWeU=", + "o8iLtP9jdUY=", + "3C5DxayMoqQ=", + "oyRATLVeDdA=", + "cBSvG7Y4Lp4=", + "qeYaEXAXYc0=", + "jxNlURg20AM=", + "lwAZn9dNSjg=", + "+ijl0DL07ME=", + "RyPBfGhCxpY=", + "3M0rqTnOqhE=", + "inHVXSTA278=", + "qz9ZiOB41eY=", + "3G2x7xoeHOU=", + "IkoYpaPqPrw=", + "j0WdJcdb+BE=", + "CeAemj67nZM=", + "kVpPjbTS/jo=", + "XEF5tUmLfjU=", + "0Bz/VETaobk=", + "7l4k+XN3Xno=", + "4imVULlTzUw=", + "Aea+sYr85WQ=", + "8PaqAkHF0e0=", + "QKC5AS+jhCM=", + "pEWBhRBgCz8=", + "fMxyYZquhAA=", + "8xxIbp8nLLY=", + "AYZkATYEJOk=", + "XKrbs5lBIcw=", + "6KVpqakZPiw=", + "OeAMSZ0Gc/U=", + "MhgNQLzMD4Q=", + "NK6v8hyAKGY=", + "QuMvE+2NFIU=", + "1G81YWIYlgo=", + "bKdk2OVRSDo=", + "maokePOWsQI=", + "CdgngSmnixI=", + "IcLAQnataC4=", + "vr2lY+iMklA=", + "CYO7w60bDzE=", + "rl+0lPOoBHk=", + "qHcIz27YW90=", + "yvViF0w9JZs=", + "dNuw4Jq10aY=", + "Bk//FNsJ//8=", + "8JWNtTVb5mE=", + "v64n9RwfQRM=", + "q1W0Fxx9Nxw=", + "0u+ay1fNNYE=" + ], + "Soroban operation source created and removed in classic phase|protocol version 29" : + [ + "+cR3oq2qY0I=", + "crbAf4Ea6kg=", + "iqTvm0y1Iow=", + "6pO37nDn84A=", + "wRmit6MBjrQ=", + "oVpHg7RRBlk=", + "Aid/7xaOTR0=", + "bbMlrMP+qlI=", + "fdD5DjwwiOQ=", + "dLar7fQTVJQ=", + "rqoO87FasNU=", + "7aj+KvKQXwY=", + "mruvECGnEyU=", + "5/S0IcIxBNI=", + "Ze0l8W+lT7M=", + "UXimakEXASc=", + "ay1R0+swdfw=", + "VEV8xNmTE1Q=", + "ohDWJIUApRk=", + "LeIaM+VHBWo=", + "XXB+LJhuCO0=", + "Z1SOcuDsn3U=", + "1j7QdwSbjPY=", + "OSoLJeVtHOg=", + "AoGTPhaZJy4=", + "DWRtqn3dZGo=", + "tuOemI/6/Vw=", + "rpw06OPQ4YQ=", + "yq78L4dQbBA=", + "pS8F28DUKnk=", + "R7aUoGb7MyA=", + "CzSTC9mDsx0=", + "9pl1fjzk41A=", + "czFb8xk728c=", + "67+TTjrwYLE=", + "yRwsYheUXUo=", + "E8kX1Yrj71M=", + "8fn8vzThmDI=", + "WBTucsatdSY=", + "cLZ5fozibbY=", + "sVBaA3CDFfU=", + "zLz7Tfj0ef4=", + "kbGW6RV1SW4=", + "NStKgU3nvsw=", + "jyRmbaA22SU=", + "5niYf+Sj4qo=", + "IYqTe+q5124=", + "b/asdCehmbs=", + "gGW69QBxFJA=", + "sjeXqxl36Eg=", + "ga0wPKIyWeU=", + "o8iLtP9jdUY=", + "3C5DxayMoqQ=", + "oyRATLVeDdA=", + "cBSvG7Y4Lp4=", + "qeYaEXAXYc0=", + "jxNlURg20AM=", + "lwAZn9dNSjg=", + "+ijl0DL07ME=", + "RyPBfGhCxpY=", + "3M0rqTnOqhE=", + "inHVXSTA278=", + "qz9ZiOB41eY=", + "3G2x7xoeHOU=", + "IkoYpaPqPrw=", + "j0WdJcdb+BE=", + "CeAemj67nZM=", + "kVpPjbTS/jo=", + "XEF5tUmLfjU=", + "0Bz/VETaobk=", + "7l4k+XN3Xno=", + "4imVULlTzUw=", + "Aea+sYr85WQ=", + "8PaqAkHF0e0=", + "QKC5AS+jhCM=", + "pEWBhRBgCz8=", + "fMxyYZquhAA=", + "8xxIbp8nLLY=", + "AYZkATYEJOk=", + "XKrbs5lBIcw=", + "6KVpqakZPiw=", + "OeAMSZ0Gc/U=", + "MhgNQLzMD4Q=", + "NK6v8hyAKGY=", + "QuMvE+2NFIU=", + "1G81YWIYlgo=", + "bKdk2OVRSDo=", + "maokePOWsQI=", + "CdgngSmnixI=", + "IcLAQnataC4=", + "vr2lY+iMklA=", + "CYO7w60bDzE=", + "rl+0lPOoBHk=", + "qHcIz27YW90=", + "yvViF0w9JZs=", + "dNuw4Jq10aY=", + "Bk//FNsJ//8=", + "8JWNtTVb5mE=", + "v64n9RwfQRM=", + "q1W0Fxx9Nxw=", + "0u+ay1fNNYE=" + ], + "Soroban pre-apply removes pre-auth tx signers|protocol version 20" : + [ + "bKDF6V5IzTo=", + "1YoRQpENTWE=", + "dYRpGqAlBDs=", + "pBZtiu3OJ7M=", + "IBgPoEf6H8s=", + "oUoLY4Gok3Q=", + "V6Ly+If+Kv8=", + "Cm3CkzL2syQ=", + "PhFE8ATy/4g=", + "FUBVEeXefug=", + "+hKh+5+Thsw=", + "BlhftaBlm9g=", + "ZS/mQlezUuE=", + "IbUGZ7407UU=", + "L1cDvhFQE80=", + "D+6b+5+H1ss=", + "j7SY2fAK6fU=", + "/r+khMp1/8Y=", + "7VBKTQNpMeI=", + "6jTXkdr5rPc=", + "6T+YCvYp8H0=", + "z0+71j6HB28=", + "Z8Ghz71bsAw=", + "U5VtQDYCcYU=", + "zvN90WCRkO0=", + "paei0bhsSsw=", + "S28j8kgREkE=", + "fvzFffOIdO4=", + "j+BP1ibsAFw=", + "7HmBrxtECbA=", + "rV7Gs+BCjiE=", + "JePxrvqNzs4=", + "p9TVAXdLwmc=", + "RxrYYjg9xds=", + "O8s2BCTv5DM=", + "+4b9MzEXa20=", + "SZI1PxuLtVE=", + "SvbVLfk38Jk=", + "5/k+BSJIhF4=", + "ybLgnPo+8nk=", + "9VpMR7mf4Po=", + "0oYz7Ehlm6A=", + "ZFeZS74f/4w=", + "u78kYC7n3/Q=" + ], + "Soroban pre-apply removes pre-auth tx signers|protocol version 21" : + [ + "bKDF6V5IzTo=", + "1YoRQpENTWE=", + "dYRpGqAlBDs=", + "pBZtiu3OJ7M=", + "IBgPoEf6H8s=", + "oUoLY4Gok3Q=", + "V6Ly+If+Kv8=", + "Cm3CkzL2syQ=", + "PhFE8ATy/4g=", + "FUBVEeXefug=", + "+hKh+5+Thsw=", + "BlhftaBlm9g=", + "ZS/mQlezUuE=", + "IbUGZ7407UU=", + "L1cDvhFQE80=", + "D+6b+5+H1ss=", + "j7SY2fAK6fU=", + "/r+khMp1/8Y=", + "7VBKTQNpMeI=", + "6jTXkdr5rPc=", + "6T+YCvYp8H0=", + "z0+71j6HB28=", + "Z8Ghz71bsAw=", + "U5VtQDYCcYU=", + "zvN90WCRkO0=", + "paei0bhsSsw=", + "S28j8kgREkE=", + "fvzFffOIdO4=", + "j+BP1ibsAFw=", + "7HmBrxtECbA=", + "rV7Gs+BCjiE=", + "JePxrvqNzs4=", + "p9TVAXdLwmc=", + "RxrYYjg9xds=", + "UIcS2n63svo=", + "5AKz5MKz3EY=", + "mQLIi4/Xnpo=", + "VtBxD9nW620=", + "ceOLSgirQIY=", + "rHzGA3aTfkw=", + "RmAa3HcLZTc=", + "NJfu6kEGL/4=", + "BSMhaTpJB4A=", + "HgIVMbnJQB4=" + ], + "Soroban pre-apply removes pre-auth tx signers|protocol version 22" : + [ + "bKDF6V5IzTo=", + "rR4bN6XbNfA=", + "JS6E860wJVs=", + "IDGsWUzKLBg=", + "nBz425qu8TA=", + "+5Dk5uxx0rE=", + "AKTpav8hbPk=", + "f46MIJNsJ7g=", + "C3nn7+10DbI=", + "uBGgXv+i2FQ=", + "MXG5KnbZNmI=", + "vAOX3fQHjXI=", + "j33yW667WIE=", + "Iy+GcIxksMk=", + "5ssn3tY2daI=", + "jPxDcic4tWY=", + "4ApAZLoEjvA=", + "9beQ0wLBIgo=", + "sDlVuzU/IZY=", + "LK7dyMvS/M4=", + "+fP84720bhs=", + "+6EZ1HEZ7rU=", + "L5nSFfKp2HI=", + "oqlN+7Cc354=", + "dBVQweMckj4=", + "kVTA3XuOok8=", + "ySbms3WjD1k=", + "mxfbAlSuU2A=", + "oIRc9owv+Ng=", + "5dfzdW9W/DU=", + "kh7qum+vOdQ=", + "0/2/o/Y5wvs=", + "jNP2r8hb+wM=", + "+pSYIj4EKGE=", + "NpRJ93HgrYI=", + "irA2wilgrnA=", + "IfCYdVEHN6A=", + "eSJ9dhI6SO8=", + "4+qhdqGn68g=", + "evhjr9oq5cY=", + "bSmOo50CUSQ=", + "IFQf8MX9jWM=", + "hORHjI6CsJs=", + "Is/kbc6knec=" + ], + "Soroban pre-apply removes pre-auth tx signers|protocol version 23" : + [ + "+cR3oq2qY0I=", + "2lu0XZUNpXE=", + "0YkYsN9TXcU=", + "Vudi8oDzW4I=", + "2v7LYECgY2E=", + "4eNmAHnACvY=", + "nsbN8jGC9+c=", + "3kOlGG6S6/Y=", + "BUWt8cXkE5A=", + "4A6ZHTxj8tg=", + "809kpAhSloE=", + "WJKVjCcl7yo=", + "a8oSOyFmNT0=", + "WyI59xx+zTc=", + "YOQgHndQKjA=", + "MsAl1s+BNFQ=", + "nF+LnYavpr0=", + "y9MpYmiN300=", + "nSNpeleCNfI=", + "PSTit18ljrQ=", + "3JCL3JAVKOQ=", + "LPGjbKnCvzE=", + "S8gNPX/DWB0=", + "cIXhb1wrROk=", + "fPIsm2zwFXg=", + "pnW9jsSv/og=", + "Jk4IV8yurg8=", + "dUsPMBubifg=", + "4NKto3sosLY=", + "50doy92gxJs=", + "1Rkqm63AlHw=", + "+LN3x+p008A=", + "+v4Ai/yLXQo=", + "uj+OehxLu2o=", + "XBUW/5qoKUo=", + "zFo4Vc6YmG8=", + "6k8nkchciDY=", + "SFVh7gD5zB8=", + "YsNY30clXRo=", + "aKiRBvxFWOs=", + "yegKP5/2ctg=", + "4WaR3adrld4=", + "b/u7eXDr9mc=", + "5t5NSWs4F3s=" + ], + "Soroban pre-apply removes pre-auth tx signers|protocol version 24" : + [ + "+cR3oq2qY0I=", + "2lu0XZUNpXE=", + "0YkYsN9TXcU=", + "Vudi8oDzW4I=", + "2v7LYECgY2E=", + "4eNmAHnACvY=", + "nsbN8jGC9+c=", + "3kOlGG6S6/Y=", + "BUWt8cXkE5A=", + "4A6ZHTxj8tg=", + "809kpAhSloE=", + "WJKVjCcl7yo=", + "a8oSOyFmNT0=", + "WyI59xx+zTc=", + "YOQgHndQKjA=", + "MsAl1s+BNFQ=", + "nF+LnYavpr0=", + "y9MpYmiN300=", + "nSNpeleCNfI=", + "PSTit18ljrQ=", + "3JCL3JAVKOQ=", + "LPGjbKnCvzE=", + "S8gNPX/DWB0=", + "cIXhb1wrROk=", + "fPIsm2zwFXg=", + "pnW9jsSv/og=", + "Jk4IV8yurg8=", + "dUsPMBubifg=", + "4NKto3sosLY=", + "50doy92gxJs=", + "1Rkqm63AlHw=", + "+LN3x+p008A=", + "+v4Ai/yLXQo=", + "uj+OehxLu2o=", + "XBUW/5qoKUo=", + "zFo4Vc6YmG8=", + "6k8nkchciDY=", + "SFVh7gD5zB8=", + "YsNY30clXRo=", + "aKiRBvxFWOs=", + "yegKP5/2ctg=", + "4WaR3adrld4=", + "b/u7eXDr9mc=", + "5t5NSWs4F3s=" + ], + "Soroban pre-apply removes pre-auth tx signers|protocol version 25" : + [ + "+cR3oq2qY0I=", + "2lu0XZUNpXE=", + "0YkYsN9TXcU=", + "Vudi8oDzW4I=", + "2v7LYECgY2E=", + "4eNmAHnACvY=", + "nsbN8jGC9+c=", + "3kOlGG6S6/Y=", + "BUWt8cXkE5A=", + "4A6ZHTxj8tg=", + "809kpAhSloE=", + "WJKVjCcl7yo=", + "a8oSOyFmNT0=", + "WyI59xx+zTc=", + "YOQgHndQKjA=", + "MsAl1s+BNFQ=", + "nF+LnYavpr0=", + "y9MpYmiN300=", + "nSNpeleCNfI=", + "PSTit18ljrQ=", + "3JCL3JAVKOQ=", + "LPGjbKnCvzE=", + "S8gNPX/DWB0=", + "cIXhb1wrROk=", + "fPIsm2zwFXg=", + "pnW9jsSv/og=", + "Jk4IV8yurg8=", + "dUsPMBubifg=", + "4NKto3sosLY=", + "50doy92gxJs=", + "1Rkqm63AlHw=", + "+LN3x+p008A=", + "+v4Ai/yLXQo=", + "uj+OehxLu2o=", + "XBUW/5qoKUo=", + "zFo4Vc6YmG8=", + "6k8nkchciDY=", + "SFVh7gD5zB8=", + "YsNY30clXRo=", + "aKiRBvxFWOs=", + "yegKP5/2ctg=", + "4WaR3adrld4=", + "b/u7eXDr9mc=", + "5t5NSWs4F3s=" + ], + "Soroban pre-apply removes pre-auth tx signers|protocol version 26" : + [ + "+cR3oq2qY0I=", + "YS3CtbB4LCY=", + "yuxnivFgzdw=", + "NmWV7/t534g=", + "/Kjv95RCny8=", + "pO1qODwjXZY=", + "468KGJPAA6M=", + "zEMED1Ra2FE=", + "6KQ1GRPt6bI=", + "MLNDTE6pvY4=", + "M26lOjlaQIY=", + "mp+KNeldXrU=", + "lUxktDmMJ54=", + "z+4rbTbSb84=", + "CvbvbVNMx4k=", + "5MTOb0MPD+w=", + "MIvv9ify+gw=", + "dSs8vpDFl3c=", + "iw4hEELDw5Q=", + "hajZcnBWStU=", + "Orkjm9fvOX0=", + "YiZVW2/DJTA=", + "MHNjEmcU/yc=", + "N9FNXxDcVDs=", + "IpqtiLtCShI=", + "IWJJU/JPink=", + "hNhPmyg88jY=", + "1pct7OGGkkI=", + "l9wje+2H0Jg=", + "fkW5vRedzh4=", + "Bt4XkzF4Smc=", + "jsZPxY0xD3I=", + "fH3XJwGQAME=", + "MBYfICUqUII=", + "UB3keZnV78E=", + "yTp/L/LfDZ0=", + "fYKKnLHVgDY=", + "/k+LTrpQXdw=", + "hlZh01w9cdU=", + "CPKD3YpWfmI=", + "00Ek9r6xKCU=", + "CYn7uvpg2AM=", + "WgyGQCjDM68=", + "YJso3URJjlo=" + ], + "Soroban pre-apply removes pre-auth tx signers|protocol version 27" : + [ + "+cR3oq2qY0I=", + "YS3CtbB4LCY=", + "yuxnivFgzdw=", + "NmWV7/t534g=", + "/Kjv95RCny8=", + "pO1qODwjXZY=", + "468KGJPAA6M=", + "zEMED1Ra2FE=", + "6KQ1GRPt6bI=", + "MLNDTE6pvY4=", + "M26lOjlaQIY=", + "mp+KNeldXrU=", + "lUxktDmMJ54=", + "z+4rbTbSb84=", + "CvbvbVNMx4k=", + "5MTOb0MPD+w=", + "MIvv9ify+gw=", + "dSs8vpDFl3c=", + "iw4hEELDw5Q=", + "hajZcnBWStU=", + "Orkjm9fvOX0=", + "YiZVW2/DJTA=", + "MHNjEmcU/yc=", + "N9FNXxDcVDs=", + "IpqtiLtCShI=", + "IWJJU/JPink=", + "hNhPmyg88jY=", + "1pct7OGGkkI=", + "l9wje+2H0Jg=", + "fkW5vRedzh4=", + "Bt4XkzF4Smc=", + "jsZPxY0xD3I=", + "fH3XJwGQAME=", + "MBYfICUqUII=", + "UB3keZnV78E=", + "yTp/L/LfDZ0=", + "fYKKnLHVgDY=", + "/k+LTrpQXdw=", + "hlZh01w9cdU=", + "CPKD3YpWfmI=", + "00Ek9r6xKCU=", + "CYn7uvpg2AM=", + "WgyGQCjDM68=", + "YJso3URJjlo=" + ], + "Soroban pre-apply removes pre-auth tx signers|protocol version 28" : + [ + "+cR3oq2qY0I=", + "YS3CtbB4LCY=", + "yuxnivFgzdw=", + "NmWV7/t534g=", + "/Kjv95RCny8=", + "pO1qODwjXZY=", + "468KGJPAA6M=", + "zEMED1Ra2FE=", + "6KQ1GRPt6bI=", + "MLNDTE6pvY4=", + "M26lOjlaQIY=", + "mp+KNeldXrU=", + "lUxktDmMJ54=", + "z+4rbTbSb84=", + "CvbvbVNMx4k=", + "5MTOb0MPD+w=", + "MIvv9ify+gw=", + "dSs8vpDFl3c=", + "iw4hEELDw5Q=", + "hajZcnBWStU=", + "Orkjm9fvOX0=", + "YiZVW2/DJTA=", + "MHNjEmcU/yc=", + "N9FNXxDcVDs=", + "IpqtiLtCShI=", + "IWJJU/JPink=", + "hNhPmyg88jY=", + "1pct7OGGkkI=", + "l9wje+2H0Jg=", + "fkW5vRedzh4=", + "Bt4XkzF4Smc=", + "jsZPxY0xD3I=", + "fH3XJwGQAME=", + "MBYfICUqUII=", + "UB3keZnV78E=", + "yTp/L/LfDZ0=", + "fYKKnLHVgDY=", + "/k+LTrpQXdw=", + "hlZh01w9cdU=", + "CPKD3YpWfmI=", + "00Ek9r6xKCU=", + "CYn7uvpg2AM=", + "WgyGQCjDM68=", + "YJso3URJjlo=" + ], + "Soroban pre-apply removes pre-auth tx signers|protocol version 29" : + [ + "+cR3oq2qY0I=", + "YS3CtbB4LCY=", + "yuxnivFgzdw=", + "NmWV7/t534g=", + "/Kjv95RCny8=", + "pO1qODwjXZY=", + "468KGJPAA6M=", + "zEMED1Ra2FE=", + "6KQ1GRPt6bI=", + "MLNDTE6pvY4=", + "M26lOjlaQIY=", + "mp+KNeldXrU=", + "lUxktDmMJ54=", + "z+4rbTbSb84=", + "CvbvbVNMx4k=", + "5MTOb0MPD+w=", + "MIvv9ify+gw=", + "dSs8vpDFl3c=", + "iw4hEELDw5Q=", + "hajZcnBWStU=", + "Orkjm9fvOX0=", + "YiZVW2/DJTA=", + "MHNjEmcU/yc=", + "N9FNXxDcVDs=", + "IpqtiLtCShI=", + "IWJJU/JPink=", + "hNhPmyg88jY=", + "1pct7OGGkkI=", + "l9wje+2H0Jg=", + "fkW5vRedzh4=", + "Bt4XkzF4Smc=", + "jsZPxY0xD3I=", + "fH3XJwGQAME=", + "MBYfICUqUII=", + "UB3keZnV78E=", + "yTp/L/LfDZ0=", + "fYKKnLHVgDY=", + "/k+LTrpQXdw=", + "hlZh01w9cdU=", + "CPKD3YpWfmI=", + "00Ek9r6xKCU=", + "CYn7uvpg2AM=", + "WgyGQCjDM68=", + "YJso3URJjlo=" + ], "Stellar asset contract transfer with CAP-67 address types" : [ "+cR3oq2qY0I=", @@ -2151,6 +3661,546 @@ "classic payment to soroban fee bump account|protocol version 27" : [ "NTGGdj1offM=", "aHlgICbAAQs=", "1S4Ni3f8c/0=", "wiDaeJY86QQ=" ], "classic payment to soroban fee bump account|protocol version 28" : [ "NTGGdj1offM=", "aHlgICbAAQs=", "1S4Ni3f8c/0=", "wiDaeJY86QQ=" ], "classic payment to soroban fee bump account|protocol version 29" : [ "NTGGdj1offM=", "aHlgICbAAQs=", "1S4Ni3f8c/0=", "wiDaeJY86QQ=" ], + "classic phase bumps sequence of Soroban tx source account|protocol version 20" : + [ + "bKDF6V5IzTo=", + "rykv5BBzl1k=", + "F6F6vr15q1A=", + "4tvlkZFFRX4=", + "HgDUNiXg80U=", + "18FzAAQdQ70=", + "IHdIHFYrMgg=", + "t89GweXHC1I=", + "apCV+hCbZl8=", + "S29r/lxDh70=", + "UDozttyLWyI=", + "QILccI+f94s=", + "4O7/p0uxawc=", + "XwTloGeBtpc=", + "umjGbr+xtOs=", + "BcAvVWg4Jmw=", + "iHA3lSwayZI=", + "MlkJ5OnVDJQ=", + "2duFeYTKLWw=", + "eOmcEBMQdlE=", + "+eyKPmg9JrE=", + "rCZG4VEVwT0=", + "SGL4HT2uYfA=", + "RjhND1kQks0=", + "SMsTDaoGh5s=", + "L0c/XCA0c6E=", + "1YZC+osAer4=", + "vkAUGnoDIWU=", + "YWbsWj8wD90=", + "E99WgsmZhpk=", + "POLMhqkKPgM=", + "ZXG+1zagaV8=", + "Tifr/sJJRZY=", + "jgRoiQihkdw=", + "lFTHEDTm6z4=", + "8PWngVNc9DA=", + "Vm4VrTdRe6M=", + "eZc8pI4CZao=", + "ByEpJiDhfdA=", + "RYE+nBNdNk4=", + "ljCgxBXavyk=", + "KZdTXZqOc9I=", + "ApLOH3i1dlk=", + "wT3WFcxv7ME=", + "9dt+A5yy5C4=", + "v142Cwj8ods=", + "UZD6laODN4k=", + "bcwT4P0WJEM=", + "CNB2siN27BQ=", + "80Aedxva4PY=", + "b6+dMGM8F6I=" + ], + "classic phase bumps sequence of Soroban tx source account|protocol version 21" : + [ + "bKDF6V5IzTo=", + "rykv5BBzl1k=", + "F6F6vr15q1A=", + "4tvlkZFFRX4=", + "HgDUNiXg80U=", + "18FzAAQdQ70=", + "IHdIHFYrMgg=", + "t89GweXHC1I=", + "apCV+hCbZl8=", + "S29r/lxDh70=", + "UDozttyLWyI=", + "QILccI+f94s=", + "4O7/p0uxawc=", + "XwTloGeBtpc=", + "umjGbr+xtOs=", + "BcAvVWg4Jmw=", + "iHA3lSwayZI=", + "MlkJ5OnVDJQ=", + "2duFeYTKLWw=", + "eOmcEBMQdlE=", + "+eyKPmg9JrE=", + "rCZG4VEVwT0=", + "SGL4HT2uYfA=", + "RjhND1kQks0=", + "SMsTDaoGh5s=", + "L0c/XCA0c6E=", + "1YZC+osAer4=", + "vkAUGnoDIWU=", + "YWbsWj8wD90=", + "E99WgsmZhpk=", + "POLMhqkKPgM=", + "ZXG+1zagaV8=", + "Tifr/sJJRZY=", + "jgRoiQihkdw=", + "lFTHEDTm6z4=", + "8PWngVNc9DA=", + "Vm4VrTdRe6M=", + "eZc8pI4CZao=", + "ByEpJiDhfdA=", + "RYE+nBNdNk4=", + "ljCgxBXavyk=", + "KZdTXZqOc9I=", + "ApLOH3i1dlk=", + "wT3WFcxv7ME=", + "9dt+A5yy5C4=", + "v142Cwj8ods=", + "UZD6laODN4k=", + "bcwT4P0WJEM=", + "CNB2siN27BQ=", + "80Aedxva4PY=", + "b6+dMGM8F6I=" + ], + "classic phase bumps sequence of Soroban tx source account|protocol version 22" : + [ + "bKDF6V5IzTo=", + "KauhmrCg434=", + "i2MP9cdPoQA=", + "6eUlCy7NfA8=", + "dv4BVugo9Pg=", + "GlNEXON53WM=", + "5ji87LopYYw=", + "xR65gTZpjQg=", + "ggE2L5NcQm0=", + "bYzJySm4pJw=", + "+J+y9jDJ/Os=", + "BJdhJgqrUvM=", + "Trqgj75G5qQ=", + "ROga9zur2h4=", + "W1o7OisVZ6M=", + "B2myGpMGbks=", + "PUkvsmqFwW0=", + "LgtPg7GlRuk=", + "DOdX56mpKUA=", + "CTbGbLivHyQ=", + "8gJgm7hCCDk=", + "i3ieaS2MRTQ=", + "S4k1pw8IB0o=", + "ZWY8Qtj2yvo=", + "sjKfSsXN0TY=", + "8LdOzje9Jvk=", + "rbMHcrEsWQI=", + "uAQLz05EF7c=", + "DV+jLYgMzyM=", + "sah3Y6KWgwM=", + "kTdgHIVAjbU=", + "d1nR73Vx9+w=", + "mcfXbs12gZY=", + "vicbAJ0rNlE=", + "F4rgNeQ8IRs=", + "E2Z/buvl4So=", + "VMLALQO+jFw=", + "LTR8SHIw5wQ=", + "ak0x6FJy7NE=", + "carMrGgUz0k=", + "Ae7xAPoccL0=", + "SN1v0r7HvJc=", + "Y8XHtnfJ+3I=", + "dau45GFv2C4=", + "03G7p4D2srI=", + "MaHEhKl/EG8=", + "KdQvPSPGfCM=", + "pIZUsigzx1A=", + "1ytaxBFTWaQ=", + "iIt0HAhbgR8=", + "744WeiXt6Lo=" + ], + "classic phase bumps sequence of Soroban tx source account|protocol version 23" : + [ + "+cR3oq2qY0I=", + "xkbxRHKhRz4=", + "0aeGvP7QhNY=", + "qEJFG6MlRu8=", + "XsgH1c4Y8jE=", + "wncOj5kDqQk=", + "mCjhH83YP1E=", + "wo9cdhwcJxQ=", + "8mY+Hh264uc=", + "XsZ8hZZjnZc=", + "m2hJ7TZM74k=", + "dwFqcSZBTmA=", + "gIWzcqTqr6I=", + "2OqlMksDyek=", + "vTCH8GUcwxk=", + "UoLY7lwUbng=", + "brZEf0Qd6Uk=", + "HsXv1mQ86v0=", + "1CJ5MhbEknI=", + "ONTM2yLjg8c=", + "doJkTlm07Q0=", + "KPHoMvuWIlY=", + "/ti+01USiNc=", + "3cI3odnQfmk=", + "iaHUOftB+Uw=", + "OR7cDRnC/F4=", + "PRT9Xl9zkSw=", + "4DPNrmvKB2M=", + "2UTxLEm41VY=", + "FpZZf7OgA1Y=", + "jThf3ILflPk=", + "AHkcqIASpfE=", + "SHVlFzsVemA=", + "i52vHXLCMI8=", + "G/XCJvs11Qs=", + "K0jLgQjq/dM=", + "sCRVpE+JPrg=", + "/og8kIzS74Y=", + "KtLG25hvCe8=", + "y82NDXppduU=", + "NL7L5pMxgVA=", + "ARKssIQ+5d4=", + "NI9WByR7em8=", + "M87swFclhmk=", + "XgoXLGJ95dk=", + "eFL6eJUV78Q=", + "0ycIKH0BnjM=", + "QUBP3y7EVgY=", + "gtKDQKoYDvc=", + "F976AZv+d4c=", + "6ECCc6Afj/Q=" + ], + "classic phase bumps sequence of Soroban tx source account|protocol version 24" : + [ + "+cR3oq2qY0I=", + "xkbxRHKhRz4=", + "0aeGvP7QhNY=", + "qEJFG6MlRu8=", + "XsgH1c4Y8jE=", + "wncOj5kDqQk=", + "mCjhH83YP1E=", + "wo9cdhwcJxQ=", + "8mY+Hh264uc=", + "XsZ8hZZjnZc=", + "m2hJ7TZM74k=", + "dwFqcSZBTmA=", + "gIWzcqTqr6I=", + "2OqlMksDyek=", + "vTCH8GUcwxk=", + "UoLY7lwUbng=", + "brZEf0Qd6Uk=", + "HsXv1mQ86v0=", + "1CJ5MhbEknI=", + "ONTM2yLjg8c=", + "doJkTlm07Q0=", + "KPHoMvuWIlY=", + "/ti+01USiNc=", + "3cI3odnQfmk=", + "iaHUOftB+Uw=", + "OR7cDRnC/F4=", + "PRT9Xl9zkSw=", + "4DPNrmvKB2M=", + "2UTxLEm41VY=", + "FpZZf7OgA1Y=", + "jThf3ILflPk=", + "AHkcqIASpfE=", + "SHVlFzsVemA=", + "i52vHXLCMI8=", + "G/XCJvs11Qs=", + "K0jLgQjq/dM=", + "sCRVpE+JPrg=", + "/og8kIzS74Y=", + "KtLG25hvCe8=", + "y82NDXppduU=", + "NL7L5pMxgVA=", + "ARKssIQ+5d4=", + "NI9WByR7em8=", + "M87swFclhmk=", + "XgoXLGJ95dk=", + "eFL6eJUV78Q=", + "0ycIKH0BnjM=", + "QUBP3y7EVgY=", + "gtKDQKoYDvc=", + "F976AZv+d4c=", + "6ECCc6Afj/Q=" + ], + "classic phase bumps sequence of Soroban tx source account|protocol version 25" : + [ + "+cR3oq2qY0I=", + "xkbxRHKhRz4=", + "0aeGvP7QhNY=", + "qEJFG6MlRu8=", + "XsgH1c4Y8jE=", + "wncOj5kDqQk=", + "mCjhH83YP1E=", + "wo9cdhwcJxQ=", + "8mY+Hh264uc=", + "XsZ8hZZjnZc=", + "m2hJ7TZM74k=", + "dwFqcSZBTmA=", + "gIWzcqTqr6I=", + "2OqlMksDyek=", + "vTCH8GUcwxk=", + "UoLY7lwUbng=", + "brZEf0Qd6Uk=", + "HsXv1mQ86v0=", + "1CJ5MhbEknI=", + "ONTM2yLjg8c=", + "doJkTlm07Q0=", + "KPHoMvuWIlY=", + "/ti+01USiNc=", + "3cI3odnQfmk=", + "iaHUOftB+Uw=", + "OR7cDRnC/F4=", + "PRT9Xl9zkSw=", + "4DPNrmvKB2M=", + "2UTxLEm41VY=", + "FpZZf7OgA1Y=", + "jThf3ILflPk=", + "AHkcqIASpfE=", + "SHVlFzsVemA=", + "i52vHXLCMI8=", + "G/XCJvs11Qs=", + "K0jLgQjq/dM=", + "sCRVpE+JPrg=", + "/og8kIzS74Y=", + "KtLG25hvCe8=", + "y82NDXppduU=", + "NL7L5pMxgVA=", + "ARKssIQ+5d4=", + "NI9WByR7em8=", + "M87swFclhmk=", + "XgoXLGJ95dk=", + "eFL6eJUV78Q=", + "0ycIKH0BnjM=", + "QUBP3y7EVgY=", + "gtKDQKoYDvc=", + "F976AZv+d4c=", + "6ECCc6Afj/Q=" + ], + "classic phase bumps sequence of Soroban tx source account|protocol version 26" : + [ + "+cR3oq2qY0I=", + "9G86+JBHgeg=", + "O9xQyz4xR6g=", + "u0McD0c7NfI=", + "KLJ0pWMwK30=", + "wpwSFCn5q6g=", + "4r+bbTlvQKM=", + "hDtFvwMk1/Q=", + "LgGCds/Fc5Q=", + "7e0mbuTWiVk=", + "V4njnChVt/w=", + "dDEX/hJzsdc=", + "vp8A6fnZmGU=", + "Kye95ZZ5ASA=", + "tjYacA0ikf0=", + "5HuWsvaD6R0=", + "7vAx5m28tlg=", + "xefwYThnA/c=", + "1OAH/Yjpzhw=", + "OnxYYYZ1auA=", + "xyDBkmJ29YM=", + "gJObg3YiHKo=", + "Fasoypcxhw8=", + "Op58Pa68puU=", + "QZzhPrNXWOk=", + "uQCN2iKRws4=", + "4qXGhGs0MEY=", + "BhD8QBYtJck=", + "LMq6nGWKQQs=", + "qUCY5Yht2nE=", + "+yQ+NHtpKwc=", + "odMr5HEuZ1c=", + "YfdsmGxer0Y=", + "6nHthXP12Is=", + "cBu0hV6kpKM=", + "JxBRDvkPK+c=", + "ndLjEW0PIeY=", + "5m3OBkeUZEI=", + "JE4YlOvb+as=", + "5tlNHzS4jsY=", + "UJ8X82k1Rwc=", + "q5MMlD4ueJk=", + "wuk/cKq2M60=", + "zzCTevwh32E=", + "um3lT2lgzlk=", + "7TXOxf6SKR8=", + "WXrk89lXDpE=", + "PT44jKZRsK0=", + "N8EpdAB4Iy0=", + "Bk0zP1Njm5M=", + "RG5lu2TSBAE=" + ], + "classic phase bumps sequence of Soroban tx source account|protocol version 27" : + [ + "+cR3oq2qY0I=", + "9G86+JBHgeg=", + "O9xQyz4xR6g=", + "u0McD0c7NfI=", + "KLJ0pWMwK30=", + "wpwSFCn5q6g=", + "4r+bbTlvQKM=", + "hDtFvwMk1/Q=", + "LgGCds/Fc5Q=", + "7e0mbuTWiVk=", + "V4njnChVt/w=", + "dDEX/hJzsdc=", + "vp8A6fnZmGU=", + "Kye95ZZ5ASA=", + "tjYacA0ikf0=", + "5HuWsvaD6R0=", + "7vAx5m28tlg=", + "xefwYThnA/c=", + "1OAH/Yjpzhw=", + "OnxYYYZ1auA=", + "xyDBkmJ29YM=", + "gJObg3YiHKo=", + "Fasoypcxhw8=", + "Op58Pa68puU=", + "QZzhPrNXWOk=", + "uQCN2iKRws4=", + "4qXGhGs0MEY=", + "BhD8QBYtJck=", + "LMq6nGWKQQs=", + "qUCY5Yht2nE=", + "+yQ+NHtpKwc=", + "odMr5HEuZ1c=", + "YfdsmGxer0Y=", + "6nHthXP12Is=", + "cBu0hV6kpKM=", + "JxBRDvkPK+c=", + "ndLjEW0PIeY=", + "5m3OBkeUZEI=", + "JE4YlOvb+as=", + "5tlNHzS4jsY=", + "UJ8X82k1Rwc=", + "q5MMlD4ueJk=", + "wuk/cKq2M60=", + "zzCTevwh32E=", + "um3lT2lgzlk=", + "7TXOxf6SKR8=", + "WXrk89lXDpE=", + "PT44jKZRsK0=", + "N8EpdAB4Iy0=", + "Bk0zP1Njm5M=", + "RG5lu2TSBAE=" + ], + "classic phase bumps sequence of Soroban tx source account|protocol version 28" : + [ + "+cR3oq2qY0I=", + "9G86+JBHgeg=", + "O9xQyz4xR6g=", + "u0McD0c7NfI=", + "KLJ0pWMwK30=", + "wpwSFCn5q6g=", + "4r+bbTlvQKM=", + "hDtFvwMk1/Q=", + "LgGCds/Fc5Q=", + "7e0mbuTWiVk=", + "V4njnChVt/w=", + "dDEX/hJzsdc=", + "vp8A6fnZmGU=", + "Kye95ZZ5ASA=", + "tjYacA0ikf0=", + "5HuWsvaD6R0=", + "7vAx5m28tlg=", + "xefwYThnA/c=", + "1OAH/Yjpzhw=", + "OnxYYYZ1auA=", + "xyDBkmJ29YM=", + "gJObg3YiHKo=", + "Fasoypcxhw8=", + "Op58Pa68puU=", + "QZzhPrNXWOk=", + "uQCN2iKRws4=", + "4qXGhGs0MEY=", + "BhD8QBYtJck=", + "LMq6nGWKQQs=", + "qUCY5Yht2nE=", + "+yQ+NHtpKwc=", + "odMr5HEuZ1c=", + "YfdsmGxer0Y=", + "6nHthXP12Is=", + "cBu0hV6kpKM=", + "JxBRDvkPK+c=", + "ndLjEW0PIeY=", + "5m3OBkeUZEI=", + "JE4YlOvb+as=", + "5tlNHzS4jsY=", + "UJ8X82k1Rwc=", + "q5MMlD4ueJk=", + "wuk/cKq2M60=", + "zzCTevwh32E=", + "um3lT2lgzlk=", + "7TXOxf6SKR8=", + "WXrk89lXDpE=", + "PT44jKZRsK0=", + "N8EpdAB4Iy0=", + "Bk0zP1Njm5M=", + "RG5lu2TSBAE=" + ], + "classic phase bumps sequence of Soroban tx source account|protocol version 29" : + [ + "+cR3oq2qY0I=", + "9G86+JBHgeg=", + "O9xQyz4xR6g=", + "u0McD0c7NfI=", + "KLJ0pWMwK30=", + "wpwSFCn5q6g=", + "4r+bbTlvQKM=", + "hDtFvwMk1/Q=", + "LgGCds/Fc5Q=", + "7e0mbuTWiVk=", + "V4njnChVt/w=", + "dDEX/hJzsdc=", + "vp8A6fnZmGU=", + "Kye95ZZ5ASA=", + "tjYacA0ikf0=", + "5HuWsvaD6R0=", + "7vAx5m28tlg=", + "xefwYThnA/c=", + "1OAH/Yjpzhw=", + "OnxYYYZ1auA=", + "xyDBkmJ29YM=", + "gJObg3YiHKo=", + "Fasoypcxhw8=", + "Op58Pa68puU=", + "QZzhPrNXWOk=", + "uQCN2iKRws4=", + "4qXGhGs0MEY=", + "BhD8QBYtJck=", + "LMq6nGWKQQs=", + "qUCY5Yht2nE=", + "+yQ+NHtpKwc=", + "odMr5HEuZ1c=", + "YfdsmGxer0Y=", + "6nHthXP12Is=", + "cBu0hV6kpKM=", + "JxBRDvkPK+c=", + "ndLjEW0PIeY=", + "5m3OBkeUZEI=", + "JE4YlOvb+as=", + "5tlNHzS4jsY=", + "UJ8X82k1Rwc=", + "q5MMlD4ueJk=", + "wuk/cKq2M60=", + "zzCTevwh32E=", + "um3lT2lgzlk=", + "7TXOxf6SKR8=", + "WXrk89lXDpE=", + "PT44jKZRsK0=", + "N8EpdAB4Iy0=", + "Bk0zP1Njm5M=", + "RG5lu2TSBAE=" + ], "classic phase bumps sequence of soroban source account|protocol version 20" : [ "NTGGdj1offM=", "FVbub/Ss/Xg=", "9Rpfyyb4ewI=" ], "classic phase bumps sequence of soroban source account|protocol version 21" : [ "NTGGdj1offM=", "FVbub/Ss/Xg=", "9Rpfyyb4ewI=" ], "classic phase bumps sequence of soroban source account|protocol version 22" : [ "NTGGdj1offM=", "CTLGLR5LLHY=", "xXpwdFz6+Uc=" ], From 5be38fb7c7a9b634ccd8dda96cf96007d694e7aa Mon Sep 17 00:00:00 2001 From: dmkozh Date: Mon, 10 Aug 2026 19:09:02 -0400 Subject: [PATCH 2/2] Parallelize merge of per-thread changes into global map. When we commit the changes from Soroban apply threads to the global state map, we can shard the output such that every worker only writes into a single shard (thus avoiding any synchronization). Besides the parallelization, this includes a few micro-optimizations that help us avoid extra hash re-computations and copies. --- src/ledger/LedgerEntryScope.cpp | 57 +++++ src/ledger/LedgerEntryScope.h | 37 +++ src/ledger/LedgerManagerImpl.cpp | 2 +- src/transactions/ParallelApplyTypes.h | 189 +++++++++++++++ src/transactions/ParallelApplyUtils.cpp | 306 ++++++++++++++++-------- src/transactions/ParallelApplyUtils.h | 79 ++++-- src/transactions/TransactionFrameBase.h | 100 +------- 7 files changed, 541 insertions(+), 229 deletions(-) create mode 100644 src/transactions/ParallelApplyTypes.h diff --git a/src/ledger/LedgerEntryScope.cpp b/src/ledger/LedgerEntryScope.cpp index 9d9fde38e0..50deb7f8cc 100644 --- a/src/ledger/LedgerEntryScope.cpp +++ b/src/ledger/LedgerEntryScope.cpp @@ -417,6 +417,14 @@ LedgerEntryScope::scopeAdoptEntryOpt( return ScopedLedgerEntryOpt(mScopeID, entry); } +template +ScopedLedgerEntryOpt +LedgerEntryScope::scopeAdoptEntryOpt( + std::optional&& entry) const +{ + return ScopedLedgerEntryOpt(mScopeID, std::move(entry)); +} + template template ScopedLedgerEntry @@ -456,6 +464,41 @@ LedgerEntryScope::scopeAdoptEntryOptFromImpl( return ScopedLedgerEntryOpt{mScopeID, entry.mEntry}; } +template +template +ScopedLedgerEntry +LedgerEntryScope::scopeAdoptEntryFromImpl( + ScopedLedgerEntry&& entry, + LedgerEntryScope const& scope) const +{ + if (scope.mActive) + { + throw std::runtime_error(fmt::format( + "scopeAdoptEntryFrom: adopting entry with scope ID {} from " + "still-active scope ID '{}'", + entry.mScopeID, scope.mScopeID)); + } + return EntryT{mScopeID, std::move(entry.mEntry)}; +} + +template +template +ScopedLedgerEntryOpt +LedgerEntryScope::scopeAdoptEntryOptFromImpl( + ScopedLedgerEntryOpt&& entry, + LedgerEntryScope const& scope) const +{ + if (scope.mActive) + { + throw std::runtime_error( + fmt::format("scopeAdoptEntryOptFrom: adopting entry with " + "scope ID {} from " + "still-active scope ID '{}'", + entry.mScopeID, scope.mScopeID)); + } + return ScopedLedgerEntryOpt{mScopeID, std::move(entry.mEntry)}; +} + ///////////////////////////////// // DeactivateScopeGuard ///////////////////////////////// @@ -495,6 +538,20 @@ FOREACH_STATIC_LEDGER_ENTRY_SCOPE(INSTANTIATE_SCOPE_CLASSES) scopeAdoptEntryOptFromImpl( \ ScopedLedgerEntryOpt const&, \ LedgerEntryScope const&) \ + const; \ +\ + template ScopedLedgerEntry \ + LedgerEntryScope:: \ + scopeAdoptEntryFromImpl( \ + ScopedLedgerEntry&&, \ + LedgerEntryScope const&) \ + const; \ +\ + template ScopedLedgerEntryOpt \ + LedgerEntryScope:: \ + scopeAdoptEntryOptFromImpl( \ + ScopedLedgerEntryOpt&&, \ + LedgerEntryScope const&) \ const; FOR_EACH_VALID_SCOPE_ADOPTION(INSTANTIATE_ADOPT_METHODS) diff --git a/src/ledger/LedgerEntryScope.h b/src/ledger/LedgerEntryScope.h index 7b5b59b1ac..b04de04468 100644 --- a/src/ledger/LedgerEntryScope.h +++ b/src/ledger/LedgerEntryScope.h @@ -387,6 +387,7 @@ template class LedgerEntryScope EntryT scopeAdoptEntry(LedgerEntry const& entry) const; OptionalEntryT scopeAdoptEntryOpt(std::optional const& entry) const; + OptionalEntryT scopeAdoptEntryOpt(std::optional&& entry) const; template EntryT @@ -414,6 +415,32 @@ template class LedgerEntryScope return scopeAdoptEntryOptFromImpl(entry, scope); } + template + EntryT + scopeAdoptEntryFrom(ScopedLedgerEntry&& entry, + LedgerEntryScope const& scope) const + { + static_assert( + IsValidScopeAdoption::value, + "Invalid scope adoption: this transition is not allowed. " + "Check FOR_EACH_VALID_SCOPE_ADOPTION in LedgerEntryScope.h " + "for the list of valid transitions."); + return scopeAdoptEntryFromImpl(std::move(entry), scope); + } + + template + OptionalEntryT + scopeAdoptEntryOptFrom(ScopedLedgerEntryOpt&& entry, + LedgerEntryScope const& scope) const + { + static_assert( + IsValidScopeAdoption::value, + "Invalid scope adoption: this transition is not allowed. " + "Check FOR_EACH_VALID_SCOPE_ADOPTION in LedgerEntryScope.h " + "for the list of valid transitions."); + return scopeAdoptEntryOptFromImpl(std::move(entry), scope); + } + private: template EntryT @@ -424,6 +451,16 @@ template class LedgerEntryScope OptionalEntryT scopeAdoptEntryOptFromImpl(ScopedLedgerEntryOpt const& entry, LedgerEntryScope const& scope) const; + + template + EntryT + scopeAdoptEntryFromImpl(ScopedLedgerEntry&& entry, + LedgerEntryScope const& scope) const; + + template + OptionalEntryT + scopeAdoptEntryOptFromImpl(ScopedLedgerEntryOpt&& entry, + LedgerEntryScope const& scope) const; }; template class DeactivateScopeGuard diff --git a/src/ledger/LedgerManagerImpl.cpp b/src/ledger/LedgerManagerImpl.cpp index dc703a2e8c..0a92901305 100644 --- a/src/ledger/LedgerManagerImpl.cpp +++ b/src/ledger/LedgerManagerImpl.cpp @@ -2724,7 +2724,7 @@ LedgerManagerImpl::applySorobanStage( txBundle.getResPayload().getRefundableFeeTracker()); } - globalParState.commitChangesFromThreads(app, threadStates, stage); + globalParState.commitChangesFromThreads(app, threadStates); } void diff --git a/src/transactions/ParallelApplyTypes.h b/src/transactions/ParallelApplyTypes.h new file mode 100644 index 0000000000..438ba027af --- /dev/null +++ b/src/transactions/ParallelApplyTypes.h @@ -0,0 +1,189 @@ +// Copyright 2026 Stellar Development Foundation and contributors. Licensed +// under the Apache License, Version 2.0. See the COPYING file at the root +// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0 + +#pragma once + +#include "ledger/LedgerEntryScope.h" +#include "ledger/LedgerHashUtils.h" +#include "ledger/LedgerTxn.h" +#include "util/UnorderedMap.h" +#include "util/UnorderedSet.h" +#include "xdr/Stellar-ledger-entries.h" + +namespace stellar +{ + +// A LedgerKey that caches its own hash. +// +// The parallel apply maps are keyed by LedgerKey and the same key is looked +// up many times as it flows tx map -> thread map -> global map, so caching +// the hash avoids repeated re-hashing of the same key. +class ParallelApplyLedgerKey +{ + public: + explicit ParallelApplyLedgerKey(LedgerKey const& ledgerKey) + : mLedgerKey(ledgerKey), mHash(std::hash{}(mLedgerKey)) + { + } + explicit ParallelApplyLedgerKey(LedgerKey&& ledgerKey) + : mLedgerKey(std::move(ledgerKey)) + , mHash(std::hash{}(mLedgerKey)) + { + } + + LedgerKey const& + ledgerKey() const + { + return mLedgerKey; + } + + operator LedgerKey const&() const + { + return mLedgerKey; + } + + size_t + hash() const + { + return mHash; + } + + private: + LedgerKey mLedgerKey; + size_t mHash; +}; + +inline bool +operator==(ParallelApplyLedgerKey const& lhs, ParallelApplyLedgerKey const& rhs) +{ + return lhs.ledgerKey() == rhs.ledgerKey(); +} + +using ParallelApplyLedgerKeySet = UnorderedSet; +template +using ParallelApplyLedgerKeyMap = UnorderedMap; + +// Tracks entry updates within a transaction during parallel apply phases. If +// the transaction succeeds, the thread's ParallelApplyEntryMap should be +// updated with the entries from the TxModifiedEntryMap. +using TxParApplyLedgerEntry = + ScopedLedgerEntry; +using TxModifiedEntryMap = ParallelApplyLedgerKeyMap; + +// Used to track the current state of an entry during parallel apply phases. Can +// be updated by successful transactions. +template struct ParallelApplyEntry +{ + // Will not be set if the entry doesn't exist, or if no tx was able to load + // it due to hitting read limits. + ScopedLedgerEntryOpt mLedgerEntry; + bool mIsDirty; + static ParallelApplyEntry + clean(ScopedLedgerEntryOpt const& e) + { + return ParallelApplyEntry{e, false}; + } + static ParallelApplyEntry + dirty(ScopedLedgerEntryOpt const& e) + { + return ParallelApplyEntry{e, true}; + } + template + ParallelApplyEntry + rescope(LedgerEntryScope const& s1, + LedgerEntryScope const& s2) const& + { + auto adoptedEntry = s2.scopeAdoptEntryOptFrom(mLedgerEntry, s1); + return ParallelApplyEntry{adoptedEntry, mIsDirty}; + } + // Moves the entry payload into the new scope and thus makes the current + // entry invalid. + template + ParallelApplyEntry + rescope(LedgerEntryScope const& s1, LedgerEntryScope const& s2) && + { + auto adoptedEntry = + s2.scopeAdoptEntryOptFrom(std::move(mLedgerEntry), s1); + return ParallelApplyEntry{std::move(adoptedEntry), mIsDirty}; + } +}; +using GlobalParallelApplyEntry = + ParallelApplyEntry; +using ThreadParallelApplyEntry = + ParallelApplyEntry; +using TxParallelApplyEntry = + ParallelApplyEntry; + +// This is a map of all entries that will be read and/or written during parallel +// apply phases: there is one such "global" map which disjoint per-thread maps +// get split off of, modified during applyThread, and merged back into. Once all +// threads return, the updates from each threads entry map should be committed +// to LedgerTxn. +template +using ParallelApplyEntryMap = ParallelApplyLedgerKeyMap>; +using GlobalParallelApplyEntryMap = + ParallelApplyEntryMap; +using ThreadParallelApplyEntryMap = + ParallelApplyEntryMap; +using TxParallelApplyEntryMap = + ParallelApplyEntryMap; + +// Returned by each parallel transaction on success. It will contain the entries +// modified by the transaction and the keys restored. +class ParallelTxSuccessVal + : public LedgerEntryScope +{ + public: + ParallelTxSuccessVal(TxModifiedEntryMap&& modifiedEntryMap, + ScopeIdT txScopeID) + : LedgerEntryScope(txScopeID) + , mModifiedEntryMap(std::move(modifiedEntryMap)) + { + // The ModifiedEntryMap should not be used for reading entries, only + // to serve as a source for thread state to scopeAdoptEntryFrom. So + // we deactivate ourselves as a LedgerEntryScope on construction, to + // prevent accidental reads. + scopeDeactivate(); + } + ParallelTxSuccessVal(TxModifiedEntryMap&& modifiedEntryMap, + RestoredEntries&& restoredEntries, ScopeIdT txScopeID) + : LedgerEntryScope(txScopeID) + , mModifiedEntryMap(std::move(modifiedEntryMap)) + , mRestoredEntries(std::move(restoredEntries)) + { + scopeDeactivate(); + } + + TxModifiedEntryMap const& + getModifiedEntryMap() const + { + return mModifiedEntryMap; + } + RestoredEntries const& + getRestoredEntries() const + { + return mRestoredEntries; + } + + friend class TxParallelApplyLedgerState; + + private: + // This will contain a key for every entry modified by a transaction + TxModifiedEntryMap mModifiedEntryMap; + RestoredEntries mRestoredEntries; +}; +} // namespace stellar + +namespace std +{ +template <> class hash +{ + public: + size_t + operator()(stellar::ParallelApplyLedgerKey const& key) const + { + return key.hash(); + } +}; +} // namespace std diff --git a/src/transactions/ParallelApplyUtils.cpp b/src/transactions/ParallelApplyUtils.cpp index 701dfa2883..bd42c737cd 100644 --- a/src/transactions/ParallelApplyUtils.cpp +++ b/src/transactions/ParallelApplyUtils.cpp @@ -100,27 +100,6 @@ using namespace stellar; // total order, B could save this fee, but we would lose the ability to run A // and B in parallel in the future. CAP 0063 explicitly chose this tradeoff. -std::unordered_set -getReadWriteKeysForStage(ApplyStage const& stage) -{ - ZoneScoped; - std::unordered_set res; - - for (auto const& txBundle : stage) - { - for (auto const& lk : - txBundle.getTx()->sorobanResources().footprint.readWrite) - { - res.emplace(lk); - if (isSorobanEntry(lk)) - { - res.emplace(getTTLKey(lk)); - } - } - } - return res; -} - inline uint32_t& ttl(LedgerEntry& le) { @@ -149,10 +128,10 @@ ttl(std::optional const& le) // (code-or-data) keys named in the footprint of the `txBundle`. Note // that since RO and RW footprints are disjoint, we only have to look // at the RO set. -UnorderedSet +ParallelApplyLedgerKeySet buildRoTTLSet(TxBundle const& txBundle) { - UnorderedSet isReadOnlyTTLSet; + ParallelApplyLedgerKeySet isReadOnlyTTLSet; for (auto const& ro : txBundle.getTx()->sorobanResources().footprint.readOnly) { @@ -168,8 +147,9 @@ buildRoTTLSet(TxBundle const& txBundle) // Accumulate into the buffer of `roTTLBumps` the max of any existing entry and // the provided `updatedLE`, which must be a non-nullopt TTL LE. void -updateMaxOfRoTTLBump(UnorderedMap& roTTLBumps, - LedgerKey const& lk, LedgerEntry const& updatedLe) +updateMaxOfRoTTLBump(ParallelApplyLedgerKeyMap& roTTLBumps, + ParallelApplyLedgerKey const& lk, + LedgerEntry const& updatedLe) { auto [it, emplaced] = roTTLBumps.emplace(lk, ttl(updatedLe)); if (!emplaced) @@ -320,6 +300,8 @@ GlobalParallelApplyLedgerState::GlobalParallelApplyLedgerState( , mLCLApplyView(std::move(applyView)) , mInMemorySorobanState(inMemoryState) , mSorobanConfig(sorobanConfig) + , mGlobalEntryMap( + std::max(1, app.getBatchExecutor().preferredTaskCount())) { releaseAssertOrThrow(mLCLApplyView.getLedgerSeq() == mInMemorySorobanState.getLedgerSeq()); @@ -364,8 +346,10 @@ GlobalParallelApplyLedgerState::preApplyAndCollectModifiedClassicEntries( ? std::make_optional(entryPair.second->ledgerEntry()) : std::nullopt); - mGlobalEntryMap.emplace(lk, - GlobalParallelApplyEntry{entry, false}); + ParallelApplyLedgerKey pk(lk); + globalMapShardFor(pk).emplace( + std::move(pk), + GlobalParallelApplyEntry{std::move(entry), false}); } }; @@ -470,34 +454,37 @@ GlobalParallelApplyLedgerState::commitChangesToLedgerTxn( { ZoneScoped; LedgerTxn ltxInner(ltx); - for (auto const& [key, entry] : mGlobalEntryMap) + for (auto const& shard : mGlobalEntryMap) { - // Only update if dirty bit is set - if (!entry.mIsDirty) + for (auto const& [key, entry] : shard) { - continue; - } - - std::optional const& updatedLe = - entry.mLedgerEntry.readInScope(*this); - if (updatedLe) - { - auto ltxe = ltxInner.load(key); - if (ltxe) + // Only update if dirty bit is set + if (!entry.mIsDirty) { - ltxe.current() = *updatedLe; + continue; } - else + + std::optional const& updatedLe = + entry.mLedgerEntry.readInScope(*this); + if (updatedLe) { - ltxInner.create(*updatedLe); + auto ltxe = ltxInner.load(key.ledgerKey()); + if (ltxe) + { + ltxe.current() = *updatedLe; + } + else + { + ltxInner.create(*updatedLe); + } } - } - else - { - auto ltxe = ltxInner.load(key); - if (ltxe) + else { - ltxInner.erase(key); + auto ltxe = ltxInner.load(key.ledgerKey()); + if (ltxe) + { + ltxInner.erase(key.ledgerKey()); + } } } } @@ -543,10 +530,29 @@ GlobalParallelApplyLedgerState::getSnapshotLedgerSeq() const return mInMemorySorobanState.getLedgerSeq(); } -GlobalParallelApplyEntryMap const& -GlobalParallelApplyLedgerState::getGlobalEntryMap() const +size_t +GlobalParallelApplyLedgerState::globalMapShardOf( + ParallelApplyLedgerKey const& key) const +{ + // Truncate the low bits of the hash to avoid clumping of keys in per-shard + // maps in case if the shard count divides the map size. + return (key.hash() >> 24) % mGlobalEntryMap.size(); +} + +ParallelApplyEntryMap& +GlobalParallelApplyLedgerState::globalMapShardFor( + ParallelApplyLedgerKey const& key) +{ + return mGlobalEntryMap[globalMapShardOf(key)]; +} + +GlobalParallelApplyEntry const* +GlobalParallelApplyLedgerState::findInGlobalEntryMap( + ParallelApplyLedgerKey const& key) const { - return mGlobalEntryMap; + auto const& shard = mGlobalEntryMap[globalMapShardOf(key)]; + auto it = shard.find(key); + return it == shard.end() ? nullptr : &it->second; } RestoredEntries const& @@ -557,9 +563,9 @@ GlobalParallelApplyLedgerState::getRestoredEntries() const bool GlobalParallelApplyLedgerState::maybeMergeRoTTLBumps( - LedgerKey const& key, GlobalParallelApplyEntry const& newEntry, + ParallelApplyLedgerKey const& key, GlobalParallelApplyEntry const& newEntry, GlobalParallelApplyEntry& oldEntry, - std::unordered_set const& readWriteSet) + ParallelApplyLedgerKeySet const& readWriteSet) { // Read Only bumps will always be updating a pre-existing value. TTL // creation (!oldEntry) or deletion (!newEntry) are write conflicts that @@ -569,7 +575,7 @@ GlobalParallelApplyLedgerState::maybeMergeRoTTLBumps( auto merged = false; oldEntry.mLedgerEntry.modifyInScope( *this, [&](std::optional& oldLe) { - if (newLe && oldLe && key.type() == TTL) + if (newLe && oldLe && key.ledgerKey().type() == TTL) { releaseAssertOrThrow(newLe.value().data.type() == TTL); releaseAssertOrThrow(oldLe.value().data.type() == TTL); @@ -587,54 +593,110 @@ GlobalParallelApplyLedgerState::maybeMergeRoTTLBumps( void GlobalParallelApplyLedgerState::commitChangeFromThread( - ThreadParallelApplyLedgerState const& thread, LedgerKey const& key, - ThreadParallelApplyEntry const& parEntry, - std::unordered_set const& readWriteSet) + ThreadParallelApplyLedgerState const& thread, + ParallelApplyLedgerKey const& key, ThreadParallelApplyEntry&& parEntry, + ParallelApplyLedgerKeySet const& readWriteSet) { if (!parEntry.mIsDirty) { return; } - auto rescopedParEntry = parEntry.rescope(thread, *this); - auto [it, inserted] = mGlobalEntryMap.emplace(key, rescopedParEntry); - if (!inserted) + auto rescopedParEntry = std::move(parEntry).rescope(thread, *this); + auto& shard = globalMapShardFor(key); + auto it = shard.find(key); + if (it == shard.end()) { - if (!maybeMergeRoTTLBumps(key, rescopedParEntry, it->second, - readWriteSet)) - { - it->second = rescopedParEntry; - } + shard.emplace(key, std::move(rescopedParEntry)); + } + else if (!maybeMergeRoTTLBumps(key, rescopedParEntry, it->second, + readWriteSet)) + { + it->second = std::move(rescopedParEntry); } } void -GlobalParallelApplyLedgerState::commitChangesFromThread( - AppConnector& app, ThreadParallelApplyLedgerState const& thread, - std::unordered_set const& readWriteSet) +GlobalParallelApplyLedgerState::commitShardChangesFromThreads( + size_t shardIdx, + std::vector> const& threads, + size_t rwKeyCountHint) { ZoneScoped; - thread.scopeDeactivate(); - for (auto const& [key, entry] : thread.getEntryMap()) + // Subset of the stage's read-write TTL key set belonging to this shard. + ParallelApplyLedgerKeySet readWriteSet; + readWriteSet.reserve(rwKeyCountHint / mGlobalEntryMap.size() + 1); + for (auto const& thread : threads) { - commitChangeFromThread(thread, key, entry, readWriteSet); + for (auto const& key : thread->getRwFootprintTTLKeys()) + { + if (globalMapShardOf(key) == shardIdx) + { + readWriteSet.emplace(key); + } + } + } + + for (auto const& thread : threads) + { + for (auto& [key, entry] : thread->getEntryMapMut()) + { + if (globalMapShardOf(key) != shardIdx) + { + continue; + } + // Move the shard's entries out of the thread's map into the global + // map. This is safe thanks to the sharding invariant: each worker + // thread only modifies the disjoint set of values belonging to its + // shard, and the map structure itself is not modified by any + // thread. + commitChangeFromThread(*thread, key, std::move(entry), + readWriteSet); + } } - mGlobalRestoredEntries.addRestoresFrom(thread.getRestoredEntries()); } void GlobalParallelApplyLedgerState::commitChangesFromThreads( AppConnector& app, - std::vector> const& threads, - ApplyStage const& stage) + std::vector> const& threads) { ZoneScoped; releaseAssert(threadIsMain() || app.threadIsType(Application::ThreadType::APPLY)); - auto readWriteSet = getReadWriteKeysForStage(stage); for (auto const& thread : threads) { - commitChangesFromThread(app, *thread, readWriteSet); + thread->scopeDeactivate(); + } + + // Size hint for estimating per-worker read-write TTL key count. + size_t rwKeyCountHint = 0; + for (auto const& thread : threads) + { + rwKeyCountHint += thread->getRwFootprintTTLKeys().size(); + } + + // Merge the per-thread maps on parallel workers, one per global-map shard. + // Every worker scans every thread map but only merges the entries in its + // own shard, so the workers write to disjoint entries and need no + // synchronization. + size_t shardCount = mGlobalEntryMap.size(); + std::vector> tasks; + tasks.reserve(shardCount); + for (size_t i = 0; i < shardCount; ++i) + { + tasks.emplace_back([this, i, &threads, rwKeyCountHint]() { + commitShardChangesFromThreads(i, threads, rwKeyCountHint); + return 0; + }); + } + app.getBatchExecutor().executeBatch(std::move(tasks)); + + // The restored entry sets are not sharded, so we need to merge them + // sequentially. + for (auto const& thread : threads) + { + mGlobalRestoredEntries.addRestoresFrom(thread->getRestoredEntries()); } } @@ -647,39 +709,53 @@ ThreadParallelApplyLedgerState::collectClusterFootprintEntriesFromGlobal( // collect all the keys that are in the global state map. For any keys // we need not in the global state, we will fetch them from the live // applyView, in memory soroban state, or the hot archive later. - GlobalParallelApplyEntryMap const& globalEntryMap = - global.getGlobalEntryMap(); - - auto fetchFromGlobal = [&](LedgerKey const& key) { + auto fetchFromGlobal = [&](ParallelApplyLedgerKey const& key) { if (mThreadEntryMap.find(key) != mThreadEntryMap.end()) { return; } - auto entryIt = globalEntryMap.find(key); - if (entryIt != globalEntryMap.end()) + auto const* globalEntry = global.findInGlobalEntryMap(key); + if (globalEntry != nullptr) { mThreadEntryMap.emplace( key, ThreadParallelApplyEntry::clean(scopeAdoptEntryOptFrom( - entryIt->second.mLedgerEntry, global))); + globalEntry->mLedgerEntry, global))); } }; - for (auto const& txBundle : cluster) - { - auto const& footprint = txBundle.getTx()->sorobanResources().footprint; - for (auto const& keys : {footprint.readWrite, footprint.readOnly}) + auto collectFootprint = [&](xdr::xvector const& keys, + bool isReadWrite) { + for (auto const& key : keys) { - for (auto const& key : keys) + fetchFromGlobal(ParallelApplyLedgerKey(key)); + if (isSorobanEntry(key)) { - fetchFromGlobal(key); - if (isSorobanEntry(key)) + ParallelApplyLedgerKey ttlKey(getTTLKey(key)); + fetchFromGlobal(ttlKey); + if (isReadWrite) { - auto ttlKey = getTTLKey(key); - fetchFromGlobal(ttlKey); + mRwFootprintTTLKeys.push_back(std::move(ttlKey)); } } } + }; + + // Reserve the max possible size for mRwFootprintTTLKeys once for the whole + // cluster. + size_t rwKeyCount = 0; + for (auto const& txBundle : cluster) + { + rwKeyCount += + txBundle.getTx()->sorobanResources().footprint.readWrite.size(); + } + mRwFootprintTTLKeys.reserve(rwKeyCount); + + for (auto const& txBundle : cluster) + { + auto const& footprint = txBundle.getTx()->sorobanResources().footprint; + collectFootprint(footprint.readWrite, /*isReadWrite=*/true); + collectFootprint(footprint.readOnly, /*isReadWrite=*/false); } } @@ -712,7 +788,7 @@ ThreadParallelApplyLedgerState::flushRoTTLBumpsInTxWriteFootprint( continue; } - auto const& ttlKey = getTTLKey(lk); + ParallelApplyLedgerKey ttlKey(getTTLKey(lk)); auto b = mRoTTLBumps.find(ttlKey); if (b != mRoTTLBumps.end()) { @@ -760,8 +836,14 @@ ThreadParallelApplyLedgerState::flushRemainingRoTTLBumps() } } -ThreadParallelApplyEntryMap const& -ThreadParallelApplyLedgerState::getEntryMap() const +std::vector const& +ThreadParallelApplyLedgerState::getRwFootprintTTLKeys() const +{ + return mRwFootprintTTLKeys; +} + +ThreadParallelApplyEntryMap& +ThreadParallelApplyLedgerState::getEntryMapMut() { return mThreadEntryMap; } @@ -774,6 +856,13 @@ ThreadParallelApplyLedgerState::getRestoredEntries() const ThreadParallelApplyLedgerState::OptionalEntryT ThreadParallelApplyLedgerState::getLiveEntryOpt(LedgerKey const& key) const +{ + return getLiveEntryOpt(ParallelApplyLedgerKey(key)); +} + +ThreadParallelApplyLedgerState::OptionalEntryT +ThreadParallelApplyLedgerState::getLiveEntryOpt( + ParallelApplyLedgerKey const& key) const { auto it0 = mThreadEntryMap.find(key); if (it0 != mThreadEntryMap.end()) @@ -812,7 +901,7 @@ ThreadParallelApplyLedgerState::getLiveEntryOpt(LedgerKey const& key) const void ThreadParallelApplyLedgerState::upsertEntry( - LedgerKey const& key, ThreadParApplyLedgerEntry const& entry, + ParallelApplyLedgerKey const& key, ThreadParApplyLedgerEntry const& entry, uint32_t ledgerSeq) { // Weird syntax avoid extra map lookup @@ -825,7 +914,7 @@ ThreadParallelApplyLedgerState::upsertEntry( mThreadEntryMap.insert_or_assign(key, parAppEntry); } void -ThreadParallelApplyLedgerState::eraseEntry(LedgerKey const& key) +ThreadParallelApplyLedgerState::eraseEntry(ParallelApplyLedgerKey const& key) { auto parAppEntry = ThreadParallelApplyEntry::dirty(scopeAdoptEntryOpt(std::nullopt)); @@ -834,8 +923,9 @@ ThreadParallelApplyLedgerState::eraseEntry(LedgerKey const& key) void ThreadParallelApplyLedgerState::commitChangeFromSuccessfulTx( - LedgerKey const& key, ThreadParApplyLedgerEntryOpt const& newScopedEntryOpt, - UnorderedSet const& roTTLSet) + ParallelApplyLedgerKey const& key, + ThreadParApplyLedgerEntryOpt const& newScopedEntryOpt, + ParallelApplyLedgerKeySet const& roTTLSet) { ThreadParApplyLedgerEntryOpt oldScopedEntryOpt = getLiveEntryOpt(key); std::optional const& oldEntryOpt = @@ -958,6 +1048,13 @@ TxParallelApplyLedgerState::TxParallelApplyLedgerState( TxParallelApplyLedgerState::OptionalEntryT TxParallelApplyLedgerState::getLiveEntryOpt(LedgerKey const& key) const +{ + return getLiveEntryOpt(ParallelApplyLedgerKey(key)); +} + +TxParallelApplyLedgerState::OptionalEntryT +TxParallelApplyLedgerState::getLiveEntryOpt( + ParallelApplyLedgerKey const& key) const { // Note: most of the time we expect to be calling this function on an empty // mTxEntryMap -- during op setup -- and so to find no entries in @@ -1008,15 +1105,16 @@ TxParallelApplyLedgerState::upsertEntry(LedgerKey const& key, // populated mTxEntryMap before invoking the host, and we don't especially // need to check our own TTL-creating work in that case. + ParallelApplyLedgerKey pk(key); bool liveEntryExistedAlready = - getLiveEntryOpt(key).readInScope(*this).has_value(); + getLiveEntryOpt(pk).readInScope(*this).has_value(); CLOG_TRACE(Tx, "parallel apply thread {} upserting {} key {}", std::this_thread::get_id(), liveEntryExistedAlready ? "already-live" : "new", xdr::xdr_to_string(key, "key")); auto [mapEntry, _] = - mTxEntryMap.insert_or_assign(key, scopeAdoptEntryOpt(entry)); + mTxEntryMap.insert_or_assign(std::move(pk), scopeAdoptEntryOpt(entry)); mapEntry->second.modifyInScope(*this, [&](std::optional& le) { releaseAssertOrThrow(le); le.value().lastModifiedLedgerSeq = ledgerSeq; @@ -1027,8 +1125,9 @@ TxParallelApplyLedgerState::upsertEntry(LedgerKey const& key, bool TxParallelApplyLedgerState::eraseEntryIfExists(LedgerKey const& key) { + ParallelApplyLedgerKey pk(key); bool liveEntryExistedAlready = - getLiveEntryOpt(key).readInScope(*this).has_value(); + getLiveEntryOpt(pk).readInScope(*this).has_value(); if (liveEntryExistedAlready) { // NB: we only erase an entry if it doesn't already exist in @@ -1037,7 +1136,8 @@ TxParallelApplyLedgerState::eraseEntryIfExists(LedgerKey const& key) // any pre-state key when calculating the ledger delta. CLOG_TRACE(Tx, "parallel apply thread {} erasing {}", std::this_thread::get_id(), xdr::xdr_to_string(key, "key")); - mTxEntryMap.insert_or_assign(key, scopeAdoptEntryOpt(std::nullopt)); + mTxEntryMap.insert_or_assign(std::move(pk), + scopeAdoptEntryOpt(std::nullopt)); } else { diff --git a/src/transactions/ParallelApplyUtils.h b/src/transactions/ParallelApplyUtils.h index 62497713c1..5f03aab397 100644 --- a/src/transactions/ParallelApplyUtils.h +++ b/src/transactions/ParallelApplyUtils.h @@ -10,8 +10,10 @@ #include "ledger/LedgerTxn.h" #include "ledger/LedgerTypeUtils.h" #include "transactions/ParallelApplyStage.h" +#include "transactions/ParallelApplyTypes.h" #include "transactions/TransactionFrameBase.h" #include "xdr/Stellar-ledger-entries.h" +#include #include namespace stellar @@ -109,20 +111,25 @@ class ThreadParallelApplyLedgerState // Contains a buffered set of RO TTL bumps that should only be observed // when/if the corresponding entry is modified, otherwise they are merged // (by taking maximums) into the global map at the end of the thread's life. - UnorderedMap mRoTTLBumps; + ParallelApplyLedgerKeyMap mRoTTLBumps; + + // TTL keys of all the RW footprint entries in the footprints of this + // thread's transactions. This is populated once on construction and is not + // deduplicated. + std::vector mRwFootprintTTLKeys; void collectClusterFootprintEntriesFromGlobal( AppConnector& app, GlobalParallelApplyLedgerState const& global, Cluster const& cluster); - void upsertEntry(LedgerKey const& key, + void upsertEntry(ParallelApplyLedgerKey const& key, ThreadParApplyLedgerEntry const& entry, uint32_t ledgerSeq); - void eraseEntry(LedgerKey const& key); + void eraseEntry(ParallelApplyLedgerKey const& key); void - commitChangeFromSuccessfulTx(LedgerKey const& key, + commitChangeFromSuccessfulTx(ParallelApplyLedgerKey const& key, ThreadParApplyLedgerEntryOpt const& entryOpt, - UnorderedSet const& roTTLSet); + ParallelApplyLedgerKeySet const& roTTLSet); public: ThreadParallelApplyLedgerState(AppConnector& app, @@ -154,11 +161,20 @@ class ThreadParallelApplyLedgerState // TTL entry is present in the `mThreadEntryMap` and is >= the bump TTL. void flushRemainingRoTTLBumps(); - ParallelApplyEntryMap const& getEntryMap() const; + // TTL keys of all the RW footprint entries in the footprints of this + // thread's transactions. + std::vector const& getRwFootprintTTLKeys() const; + + // Mutable access to the thread's entry map. This should only be used to + // consume the map after the thread has finished processing its cluster, + // otherwise the map should only be mutated through + // `commitChangesFromSuccessfulTx`. + ParallelApplyEntryMap& getEntryMapMut(); RestoredEntries const& getRestoredEntries() const; OptionalEntryT getLiveEntryOpt(LedgerKey const& key) const; + OptionalEntryT getLiveEntryOpt(ParallelApplyLedgerKey const& key) const; bool entryWasRestored(LedgerKey const& key) const; void setDeltaForInvariantsFromSuccessfulTx(ParallelTxSuccessVal const& res, @@ -217,7 +233,14 @@ class GlobalParallelApplyLedgerState // These are propagated from stage to stage of the parallel soroban phase // -- split into disjoint per-thread maps during execution and merged // after -- as well as written back to the ltx at the phase's end. - ParallelApplyEntryMap mGlobalEntryMap; + // + // The map is sharded by key hash, so that it's possible to update it + // concurrently using as many threads as there are shards. + std::vector> mGlobalEntryMap; + + size_t globalMapShardOf(ParallelApplyLedgerKey const& key) const; + ParallelApplyEntryMap& + globalMapShardFor(ParallelApplyLedgerKey const& key); void preApplyAndCollectModifiedClassicEntries( AppConnector& app, AbstractLedgerTxn& ltx, @@ -229,22 +252,25 @@ class GlobalParallelApplyLedgerState std::shared_ptr header, AbstractLedgerTxn& ltx); - bool - maybeMergeRoTTLBumps(LedgerKey const& key, - GlobalParallelApplyEntry const& newEntry, - GlobalParallelApplyEntry& oldEntry, - std::unordered_set const& readWriteSet); - - void - commitChangeFromThread(ThreadParallelApplyLedgerState const& thread, - LedgerKey const& key, - ThreadParallelApplyEntry const& parEntry, - std::unordered_set const& readWriteSet); - - void - commitChangesFromThread(AppConnector& app, - ThreadParallelApplyLedgerState const& thread, - std::unordered_set const& readWriteSet); + bool maybeMergeRoTTLBumps(ParallelApplyLedgerKey const& key, + GlobalParallelApplyEntry const& newEntry, + GlobalParallelApplyEntry& oldEntry, + ParallelApplyLedgerKeySet const& readWriteSet); + + void commitChangeFromThread(ThreadParallelApplyLedgerState const& thread, + ParallelApplyLedgerKey const& key, + ThreadParallelApplyEntry&& parEntry, + ParallelApplyLedgerKeySet const& readWriteSet); + + // Merges the entries of every thread that belong to shard `shardIdx`. + // Used as the per-worker body of the parallelized + // commitChangesFromThreads: every entry belongs to exactly one shard, so + // concurrent calls for different shards need no synchronization. + void commitShardChangesFromThreads( + size_t shardIdx, + std::vector> const& + threads, + size_t rwKeyCountHint); public: GlobalParallelApplyLedgerState(AppConnector& app, ApplyLedgerView applyView, @@ -253,14 +279,14 @@ class GlobalParallelApplyLedgerState InMemorySorobanState const& inMemoryState, SorobanNetworkConfig const& sorobanConfig); - ParallelApplyEntryMap const& getGlobalEntryMap() const; + GlobalParallelApplyEntry const* + findInGlobalEntryMap(ParallelApplyLedgerKey const& key) const; RestoredEntries const& getRestoredEntries() const; void commitChangesFromThreads( AppConnector& app, std::vector> const& - threads, - ApplyStage const& stage); + threads); void commitChangesToLedgerTxn(AbstractLedgerTxn& ltx) const; @@ -309,6 +335,7 @@ class TxParallelApplyLedgerState public: TxParallelApplyLedgerState(ThreadParallelApplyLedgerState const& parent); OptionalEntryT getLiveEntryOpt(LedgerKey const& key) const; + OptionalEntryT getLiveEntryOpt(ParallelApplyLedgerKey const& key) const; // Upsert the entry and sets the lastModifiedLedgerSeq to the given ledger // sequence number. diff --git a/src/transactions/TransactionFrameBase.h b/src/transactions/TransactionFrameBase.h index 3d2a84dc01..f5bc2ed429 100644 --- a/src/transactions/TransactionFrameBase.h +++ b/src/transactions/TransactionFrameBase.h @@ -12,6 +12,7 @@ #include "ledger/NetworkConfig.h" #include "main/Config.h" #include "overlay/StellarXDR.h" +#include "transactions/ParallelApplyTypes.h" #include "util/TxResource.h" #include "util/UnorderedSet.h" #include "util/types.h" @@ -44,105 +45,6 @@ using TransactionFrameBasePtr = std::shared_ptr; using TransactionFrameBaseConstPtr = std::shared_ptr; -// Tracks entry updates within a transaction during parallel apply phases. If -// the transaction succeeds, the thread's ParallelApplyEntryMap should be -// updated with the entries from the TxModifiedEntryMap. -using TxParApplyLedgerEntry = - ScopedLedgerEntry; -using TxModifiedEntryMap = UnorderedMap; - -// Used to track the current state of an entry during parallel apply phases. Can -// be updated by successful transactions. -template struct ParallelApplyEntry -{ - // Will not be set if the entry doesn't exist, or if no tx was able to load - // it due to hitting read limits. - ScopedLedgerEntryOpt mLedgerEntry; - bool mIsDirty; - static ParallelApplyEntry - clean(ScopedLedgerEntryOpt const& e) - { - return ParallelApplyEntry{e, false}; - } - static ParallelApplyEntry - dirty(ScopedLedgerEntryOpt const& e) - { - return ParallelApplyEntry{e, true}; - } - template - ParallelApplyEntry - rescope(LedgerEntryScope const& s1, LedgerEntryScope const& s2) const - { - auto adoptedEntry = s2.scopeAdoptEntryOptFrom(mLedgerEntry, s1); - return ParallelApplyEntry{adoptedEntry, mIsDirty}; - } -}; -using GlobalParallelApplyEntry = - ParallelApplyEntry; -using ThreadParallelApplyEntry = - ParallelApplyEntry; -using TxParallelApplyEntry = - ParallelApplyEntry; - -// This is a map of all entries that will be read and/or written during parallel -// apply phases: there is one such "global" map which disjoint per-thread maps -// get split off of, modified during applyThread, and merged back into. Once all -// threads return, the updates from each threads entry map should be committed -// to LedgerTxn. -template -using ParallelApplyEntryMap = UnorderedMap>; -using GlobalParallelApplyEntryMap = - ParallelApplyEntryMap; -using ThreadParallelApplyEntryMap = - ParallelApplyEntryMap; -using TxParallelApplyEntryMap = - ParallelApplyEntryMap; - -// Returned by each parallel transaction on success. It will contain the entries -// modified by the transaction and the keys restored. -class ParallelTxSuccessVal - : public LedgerEntryScope -{ - public: - ParallelTxSuccessVal(TxModifiedEntryMap&& modifiedEntryMap, - ScopeIdT txScopeID) - : LedgerEntryScope(txScopeID) - , mModifiedEntryMap(std::move(modifiedEntryMap)) - { - // The ModifiedEntryMap should not be used for reading entries, only - // to serve as a source for thread state to scopeAdoptEntryFrom. So - // we deactivate ourselves as a LedgerEntryScope on construction, to - // prevent accidental reads. - scopeDeactivate(); - } - ParallelTxSuccessVal(TxModifiedEntryMap&& modifiedEntryMap, - RestoredEntries&& restoredEntries, ScopeIdT txScopeID) - : LedgerEntryScope(txScopeID) - , mModifiedEntryMap(std::move(modifiedEntryMap)) - , mRestoredEntries(std::move(restoredEntries)) - { - scopeDeactivate(); - } - - TxModifiedEntryMap const& - getModifiedEntryMap() const - { - return mModifiedEntryMap; - } - RestoredEntries const& - getRestoredEntries() const - { - return mRestoredEntries; - } - - friend class TxParallelApplyLedgerState; - - private: - // This will contain a key for every entry modified by a transaction - TxModifiedEntryMap mModifiedEntryMap; - RestoredEntries mRestoredEntries; -}; - class TransactionFrameBase { public: