diff --git a/src/ledger/ImmutableLedgerView.cpp b/src/ledger/ImmutableLedgerView.cpp index 796e51c5d..3bc324762 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 3f6a523ea..52b30f032 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/ledger/LedgerEntryScope.cpp b/src/ledger/LedgerEntryScope.cpp index 9d9fde38e..50deb7f8c 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 7b5b59b1a..b04de0446 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 dc703a2e8..0a9290130 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/FeeBumpTransactionFrame.cpp b/src/transactions/FeeBumpTransactionFrame.cpp index 9d62ddba1..37aa9d302 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 0d1313422..eccce7018 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/ParallelApplyTypes.h b/src/transactions/ParallelApplyTypes.h new file mode 100644 index 000000000..438ba027a --- /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 618901d49..bd42c737c 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 { @@ -96,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) { @@ -145,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) { @@ -164,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) @@ -174,8 +158,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 { @@ -303,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()); @@ -319,14 +318,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) { @@ -348,39 +346,106 @@ GlobalParallelApplyLedgerState:: ? 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}); } }; - // 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 @@ -389,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()); + } } } } @@ -462,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; + return mGlobalEntryMap[globalMapShardOf(key)]; +} + +GlobalParallelApplyEntry const* +GlobalParallelApplyLedgerState::findInGlobalEntryMap( + ParallelApplyLedgerKey const& key) const +{ + auto const& shard = mGlobalEntryMap[globalMapShardOf(key)]; + auto it = shard.find(key); + return it == shard.end() ? nullptr : &it->second; } RestoredEntries const& @@ -476,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 @@ -488,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); @@ -506,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()); } } @@ -566,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); } } @@ -631,7 +788,7 @@ ThreadParallelApplyLedgerState::flushRoTTLBumpsInTxWriteFootprint( continue; } - auto const& ttlKey = getTTLKey(lk); + ParallelApplyLedgerKey ttlKey(getTTLKey(lk)); auto b = mRoTTLBumps.find(ttlKey); if (b != mRoTTLBumps.end()) { @@ -679,8 +836,14 @@ ThreadParallelApplyLedgerState::flushRemainingRoTTLBumps() } } -ThreadParallelApplyEntryMap const& -ThreadParallelApplyLedgerState::getEntryMap() const +std::vector const& +ThreadParallelApplyLedgerState::getRwFootprintTTLKeys() const +{ + return mRwFootprintTTLKeys; +} + +ThreadParallelApplyEntryMap& +ThreadParallelApplyLedgerState::getEntryMapMut() { return mThreadEntryMap; } @@ -693,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()) @@ -731,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 @@ -744,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)); @@ -753,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 = @@ -877,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 @@ -927,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; @@ -946,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 @@ -956,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 7e7fd8b74..5f03aab39 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,28 +233,44 @@ 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; - void preParallelApplyAndCollectModifiedClassicEntries( + size_t globalMapShardOf(ParallelApplyLedgerKey const& key) const; + ParallelApplyEntryMap& + globalMapShardFor(ParallelApplyLedgerKey const& key); + + void preApplyAndCollectModifiedClassicEntries( AppConnector& app, AbstractLedgerTxn& ltx, std::vector const& stages); - 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); + // 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(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, @@ -247,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; @@ -303,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/TransactionFrame.cpp b/src/transactions/TransactionFrame.cpp index 6d3593455..b50f7115d 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 f46716a8d..f0d37a631 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 0e45aa97e..f5bc2ed42 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: @@ -156,11 +58,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 18cfe946f..41205b1e5 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 8c9a212cf..c1350fe7b 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 461559386..7e8b3eace 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 8a94d93d8..41c50f0d0 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 acb3228f8..77b40c81b 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 324efc429..9bc3885d1 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 652dfc357..fc5c01ffe 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 6a2f0d598..a5f5930a5 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 6dc67843f..5c7752e4c 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=" ],