From 3283baa6fd6b00b2bd6f59778e72552bc96d6760 Mon Sep 17 00:00:00 2001 From: Dmytro Kozhevin Date: Fri, 13 Feb 2026 19:36:07 -0500 Subject: [PATCH] Implement CAP-77 - ability to freeze ledger keys via network configuration. --- Builds/VisualStudio/stellar-core.vcxproj | 13 +- .../VisualStudio/stellar-core.vcxproj.filters | 7 +- src/database/test/DatabaseTests.cpp | 9 +- src/herder/Upgrades.cpp | 83 +- src/history/test/HistoryTests.cpp | 1 + src/ledger/NetworkConfig.cpp | 210 +- src/ledger/NetworkConfig.h | 20 + src/protocol-next/xdr | 2 +- src/simulation/LoadGenerator.cpp | 48 + src/simulation/TxGenerator.cpp | 32 + src/simulation/TxGenerator.h | 7 + src/test/TestExceptions.cpp | 16 + src/test/TestExceptions.h | 4 + src/test/TxTests.cpp | 6 +- ...classic-events-v2-protocol-26-soroban.json | 486 ++-- ...-enable-classic-events-v2-protocol-26.json | 12 +- ...ger-close-meta-v2-protocol-26-soroban.json | 446 ++-- .../ledger-close-meta-v2-protocol-26.json | 12 +- src/transactions/AllowTrustOpFrame.cpp | 7 + src/transactions/AllowTrustOpFrame.h | 3 + .../BeginSponsoringFutureReservesOpFrame.cpp | 7 + .../BeginSponsoringFutureReservesOpFrame.h | 3 + src/transactions/BumpSequenceOpFrame.cpp | 7 + src/transactions/BumpSequenceOpFrame.h | 3 + src/transactions/ChangeTrustOpFrame.cpp | 27 + src/transactions/ChangeTrustOpFrame.h | 3 + .../ClaimClaimableBalanceOpFrame.cpp | 60 +- .../ClaimClaimableBalanceOpFrame.h | 11 + .../ClawbackClaimableBalanceOpFrame.cpp | 7 + .../ClawbackClaimableBalanceOpFrame.h | 3 + src/transactions/ClawbackOpFrame.cpp | 8 + src/transactions/ClawbackOpFrame.h | 3 + src/transactions/CreateAccountOpFrame.cpp | 7 + src/transactions/CreateAccountOpFrame.h | 3 + .../CreateClaimableBalanceOpFrame.cpp | 9 + .../CreateClaimableBalanceOpFrame.h | 3 + .../EndSponsoringFutureReservesOpFrame.cpp | 7 + .../EndSponsoringFutureReservesOpFrame.h | 3 + .../ExtendFootprintTTLOpFrame.cpp | 9 + src/transactions/ExtendFootprintTTLOpFrame.h | 3 + src/transactions/FeeBumpTransactionFrame.cpp | 32 +- src/transactions/InflationOpFrame.cpp | 7 + src/transactions/InflationOpFrame.h | 3 + .../InvokeHostFunctionOpFrame.cpp | 9 + src/transactions/InvokeHostFunctionOpFrame.h | 3 + .../LiquidityPoolDepositOpFrame.cpp | 157 +- .../LiquidityPoolDepositOpFrame.h | 11 + .../LiquidityPoolWithdrawOpFrame.cpp | 62 +- .../LiquidityPoolWithdrawOpFrame.h | 11 + src/transactions/ManageDataOpFrame.cpp | 7 + src/transactions/ManageDataOpFrame.h | 3 + src/transactions/ManageOfferOpFrameBase.cpp | 42 +- src/transactions/ManageOfferOpFrameBase.h | 7 + src/transactions/MergeOpFrame.cpp | 8 + src/transactions/MergeOpFrame.h | 3 + src/transactions/OfferExchange.cpp | 14 + src/transactions/OfferExchange.h | 3 +- src/transactions/OperationFrame.cpp | 29 +- src/transactions/OperationFrame.h | 9 + src/transactions/PathPaymentOpFrameBase.cpp | 30 +- src/transactions/PathPaymentOpFrameBase.h | 7 +- .../PathPaymentStrictReceiveOpFrame.cpp | 18 +- .../PathPaymentStrictReceiveOpFrame.h | 4 + .../PathPaymentStrictSendOpFrame.cpp | 17 +- .../PathPaymentStrictSendOpFrame.h | 4 + src/transactions/PaymentOpFrame.cpp | 33 +- src/transactions/PaymentOpFrame.h | 7 + src/transactions/RestoreFootprintOpFrame.cpp | 9 + src/transactions/RestoreFootprintOpFrame.h | 4 + src/transactions/RevokeSponsorshipOpFrame.cpp | 22 + src/transactions/RevokeSponsorshipOpFrame.h | 3 + src/transactions/SetOptionsOpFrame.cpp | 7 + src/transactions/SetOptionsOpFrame.h | 3 + src/transactions/SetTrustLineFlagsOpFrame.cpp | 8 + src/transactions/SetTrustLineFlagsOpFrame.h | 3 + src/transactions/TransactionFrame.cpp | 198 +- src/transactions/TransactionFrame.h | 15 +- src/transactions/TransactionUtils.cpp | 30 + src/transactions/TransactionUtils.h | 2 + .../test/FrozenLedgerKeysTests.cpp | 2005 ++++++++++++++++ .../test/InvokeHostFunctionTests.cpp | 30 + .../FrozenLedgerKeysTests.json | 2066 +++++++++++++++++ 82 files changed, 5915 insertions(+), 640 deletions(-) create mode 100644 src/transactions/test/FrozenLedgerKeysTests.cpp create mode 100644 test-tx-meta-baseline-next/FrozenLedgerKeysTests.json diff --git a/Builds/VisualStudio/stellar-core.vcxproj b/Builds/VisualStudio/stellar-core.vcxproj index fd0f8b738e..7b3dce5696 100644 --- a/Builds/VisualStudio/stellar-core.vcxproj +++ b/Builds/VisualStudio/stellar-core.vcxproj @@ -124,7 +124,7 @@ Level4 Disabled true - CEREAL_THREAD_SAFE;USE_POSTGRES;ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION=1;USE_SPDLOG;FMT_HEADER_ONLY=1;BUILD_TESTS;WIN32_LEAN_AND_MEAN;NOMINMAX;ASIO_STANDALONE;_WINSOCK_DEPRECATED_NO_WARNINGS;SODIUM_STATIC;ASIO_SEPARATE_COMPILATION;ASIO_ERROR_CATEGORY_NOEXCEPT=noexcept;_CRT_SECURE_NO_WARNINGS;_WIN32_WINNT=0x0601;WIN32;_MBCS;_CRT_NONSTDC_NO_DEPRECATE;YY_NO_UNISTD_H;%(PreprocessorDefinitions) + CEREAL_THREAD_SAFE;_SILENCE_CXX20_OLD_SHARED_PTR_ATOMIC_SUPPORT_DEPRECATION_WARNING;USE_POSTGRES;ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION=1;USE_SPDLOG;FMT_HEADER_ONLY=1;BUILD_TESTS;WIN32_LEAN_AND_MEAN;NOMINMAX;ASIO_STANDALONE;_WINSOCK_DEPRECATED_NO_WARNINGS;SODIUM_STATIC;ASIO_SEPARATE_COMPILATION;ASIO_ERROR_CATEGORY_NOEXCEPT=noexcept;_CRT_SECURE_NO_WARNINGS;_WIN32_WINNT=0x0601;WIN32;_MBCS;_CRT_NONSTDC_NO_DEPRECATE;YY_NO_UNISTD_H;%(PreprocessorDefinitions) src;../../src;../../lib;../../lib/tracy/public/tracy;../../lib/spdlog/include;../../lib/libmedida/src;../../lib/soci/src/core;../../lib/autocheck/include;../../lib/cereal/include;../../lib/asio/asio/include;../../lib/xdrpp;../../lib/libsodium/src/libsodium/include;../../lib/fmt/include;../../lib/util;../..;src/$(Configuration)/generated;src/generated;../../lib/sqlite;c:\Program Files\PostgreSQL\15\include;%(AdditionalIncludeDirectories) true false @@ -187,7 +187,7 @@ exit /b 0 Level4 Disabled true - CEREAL_THREAD_SAFE;USE_POSTGRES;USE_SPDLOG;FMT_HEADER_ONLY=1;BUILD_TESTS;WIN32_LEAN_AND_MEAN;NOMINMAX;ASIO_STANDALONE;_WINSOCK_DEPRECATED_NO_WARNINGS;SODIUM_STATIC;ASIO_SEPARATE_COMPILATION;ASIO_ERROR_CATEGORY_NOEXCEPT=noexcept;_CRT_SECURE_NO_WARNINGS;_WIN32_WINNT=0x0601;WIN32;_MBCS;_CRT_NONSTDC_NO_DEPRECATE;YY_NO_UNISTD_H;%(PreprocessorDefinitions) + CEREAL_THREAD_SAFE;_SILENCE_CXX20_OLD_SHARED_PTR_ATOMIC_SUPPORT_DEPRECATION_WARNING;USE_POSTGRES;USE_SPDLOG;FMT_HEADER_ONLY=1;BUILD_TESTS;WIN32_LEAN_AND_MEAN;NOMINMAX;ASIO_STANDALONE;_WINSOCK_DEPRECATED_NO_WARNINGS;SODIUM_STATIC;ASIO_SEPARATE_COMPILATION;ASIO_ERROR_CATEGORY_NOEXCEPT=noexcept;_CRT_SECURE_NO_WARNINGS;_WIN32_WINNT=0x0601;WIN32;_MBCS;_CRT_NONSTDC_NO_DEPRECATE;YY_NO_UNISTD_H;%(PreprocessorDefinitions) src;../../src;../../lib;../../lib/tracy/public/tracy;../../lib/spdlog/include;../../lib/libmedida/src;../../lib/soci/src/core;../../lib/autocheck/include;../../lib/cereal/include;../../lib/asio/asio/include;../../lib/xdrpp;../../lib/libsodium/src/libsodium/include;../../lib/fmt/include;../../lib/util;../..;src/$(Configuration)/generated;src/generated;../../lib/sqlite;c:\Program Files\PostgreSQL\15\include;%(AdditionalIncludeDirectories) true false @@ -251,7 +251,7 @@ exit /b 0 Level4 Disabled true - CEREAL_THREAD_SAFE;ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION=1;USE_SPDLOG;FMT_HEADER_ONLY=1;BUILD_TESTS;WIN32_LEAN_AND_MEAN;NOMINMAX;ASIO_STANDALONE;_WINSOCK_DEPRECATED_NO_WARNINGS;SODIUM_STATIC;ASIO_SEPARATE_COMPILATION;ASIO_ERROR_CATEGORY_NOEXCEPT=noexcept;_CRT_SECURE_NO_WARNINGS;_WIN32_WINNT=0x0601;WIN32;_MBCS;_CRT_NONSTDC_NO_DEPRECATE;YY_NO_UNISTD_H;%(PreprocessorDefinitions) + CEREAL_THREAD_SAFE;_SILENCE_CXX20_OLD_SHARED_PTR_ATOMIC_SUPPORT_DEPRECATION_WARNING;ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION=1;USE_SPDLOG;FMT_HEADER_ONLY=1;BUILD_TESTS;WIN32_LEAN_AND_MEAN;NOMINMAX;ASIO_STANDALONE;_WINSOCK_DEPRECATED_NO_WARNINGS;SODIUM_STATIC;ASIO_SEPARATE_COMPILATION;ASIO_ERROR_CATEGORY_NOEXCEPT=noexcept;_CRT_SECURE_NO_WARNINGS;_WIN32_WINNT=0x0601;WIN32;_MBCS;_CRT_NONSTDC_NO_DEPRECATE;YY_NO_UNISTD_H;%(PreprocessorDefinitions) src;../../src;../../lib;../../lib/tracy/public/tracy;../../lib/spdlog/include;../../lib/libmedida/src;../../lib/soci/src/core;../../lib/autocheck/include;../../lib/cereal/include;../../lib/asio/asio/include;../../lib/xdrpp;../../lib/libsodium/src/libsodium/include;../../lib/fmt/include;../../lib/util;../..;src/$(Configuration)/generated;src/generated;../../lib/sqlite;c:\Program Files\PostgreSQL\15\include;%(AdditionalIncludeDirectories) true false @@ -319,7 +319,7 @@ exit /b 0 true true true - CEREAL_THREAD_SAFE;USE_POSTGRES;ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION=1;USE_SPDLOG;FMT_HEADER_ONLY=1;BUILD_TESTS;WIN32_LEAN_AND_MEAN;NOMINMAX;ASIO_STANDALONE;_WINSOCK_DEPRECATED_NO_WARNINGS;SODIUM_STATIC;ASIO_SEPARATE_COMPILATION;ASIO_ERROR_CATEGORY_NOEXCEPT=noexcept;TRACY_ENABLE;TRACY_ON_DEMAND;TRACY_NO_BROADCAST;TRACY_ONLY_LOCALHOST;TRACY_DELAYED_INIT;USE_TRACY;_CRT_SECURE_NO_WARNINGS;_WIN32_WINNT=0x0601;WIN32;_MBCS;_CRT_NONSTDC_NO_DEPRECATE;YY_NO_UNISTD_H;%(PreprocessorDefinitions) + CEREAL_THREAD_SAFE;_SILENCE_CXX20_OLD_SHARED_PTR_ATOMIC_SUPPORT_DEPRECATION_WARNING;USE_POSTGRES;ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION=1;USE_SPDLOG;FMT_HEADER_ONLY=1;BUILD_TESTS;WIN32_LEAN_AND_MEAN;NOMINMAX;ASIO_STANDALONE;_WINSOCK_DEPRECATED_NO_WARNINGS;SODIUM_STATIC;ASIO_SEPARATE_COMPILATION;ASIO_ERROR_CATEGORY_NOEXCEPT=noexcept;TRACY_ENABLE;TRACY_ON_DEMAND;TRACY_NO_BROADCAST;TRACY_ONLY_LOCALHOST;TRACY_DELAYED_INIT;USE_TRACY;_CRT_SECURE_NO_WARNINGS;_WIN32_WINNT=0x0601;WIN32;_MBCS;_CRT_NONSTDC_NO_DEPRECATE;YY_NO_UNISTD_H;%(PreprocessorDefinitions) src;../../src;../../lib;../../lib/tracy/public/tracy;../../lib/spdlog/include;../../lib/libmedida/src;../../lib/soci/src/core;../../lib/autocheck/include;../../lib/cereal/include;../../lib/asio/asio/include;../../lib/xdrpp;../../lib/libsodium/src/libsodium/include;../../lib/fmt/include;../../lib/util;../..;src/$(Configuration)/generated;src/generated;../../lib/sqlite;c:\Program Files\PostgreSQL\15\include;%(AdditionalIncludeDirectories) false true @@ -382,7 +382,7 @@ exit /b 0 true true true - CEREAL_THREAD_SAFE;USE_POSTGRES;USE_SPDLOG;FMT_HEADER_ONLY=1;BUILD_TESTS;WIN32_LEAN_AND_MEAN;NOMINMAX;ASIO_STANDALONE;_WINSOCK_DEPRECATED_NO_WARNINGS;SODIUM_STATIC;ASIO_SEPARATE_COMPILATION;ASIO_ERROR_CATEGORY_NOEXCEPT=noexcept;TRACY_ENABLE;TRACY_ON_DEMAND;TRACY_NO_BROADCAST;TRACY_ONLY_LOCALHOST;TRACY_DELAYED_INIT;USE_TRACY;_CRT_SECURE_NO_WARNINGS;_WIN32_WINNT=0x0601;WIN32;_MBCS;_CRT_NONSTDC_NO_DEPRECATE;YY_NO_UNISTD_H;%(PreprocessorDefinitions) + CEREAL_THREAD_SAFE;_SILENCE_CXX20_OLD_SHARED_PTR_ATOMIC_SUPPORT_DEPRECATION_WARNING;USE_POSTGRES;USE_SPDLOG;FMT_HEADER_ONLY=1;BUILD_TESTS;WIN32_LEAN_AND_MEAN;NOMINMAX;ASIO_STANDALONE;_WINSOCK_DEPRECATED_NO_WARNINGS;SODIUM_STATIC;ASIO_SEPARATE_COMPILATION;ASIO_ERROR_CATEGORY_NOEXCEPT=noexcept;TRACY_ENABLE;TRACY_ON_DEMAND;TRACY_NO_BROADCAST;TRACY_ONLY_LOCALHOST;TRACY_DELAYED_INIT;USE_TRACY;_CRT_SECURE_NO_WARNINGS;_WIN32_WINNT=0x0601;WIN32;_MBCS;_CRT_NONSTDC_NO_DEPRECATE;YY_NO_UNISTD_H;%(PreprocessorDefinitions) src;../../src;../../lib;../../lib/tracy/public/tracy;../../lib/spdlog/include;../../lib/libmedida/src;../../lib/soci/src/core;../../lib/autocheck/include;../../lib/cereal/include;../../lib/asio/asio/include;../../lib/xdrpp;../../lib/libsodium/src/libsodium/include;../../lib/fmt/include;../../lib/util;../..;src/$(Configuration)/generated;src/generated;../../lib/sqlite;c:\Program Files\PostgreSQL\15\include;%(AdditionalIncludeDirectories) false true @@ -723,6 +723,7 @@ exit /b 0 + @@ -853,7 +854,6 @@ exit /b 0 - @@ -1200,7 +1200,6 @@ exit /b 0 - diff --git a/Builds/VisualStudio/stellar-core.vcxproj.filters b/Builds/VisualStudio/stellar-core.vcxproj.filters index b1a00f3ee0..9ec901a0c9 100644 --- a/Builds/VisualStudio/stellar-core.vcxproj.filters +++ b/Builds/VisualStudio/stellar-core.vcxproj.filters @@ -408,9 +408,6 @@ main - - main - main @@ -1437,6 +1434,7 @@ lib\tracy + @@ -1679,9 +1677,6 @@ main - - main - main diff --git a/src/database/test/DatabaseTests.cpp b/src/database/test/DatabaseTests.cpp index ed400b76bb..bdd5c1b675 100644 --- a/src/database/test/DatabaseTests.cpp +++ b/src/database/test/DatabaseTests.cpp @@ -589,12 +589,13 @@ TEST_CASE("Database splitting migration works correctly", "[db]") TEST_CASE("ledgerheaders migration works correctly", "[db]") { - Config::TestDbMode mode = GENERATE(Config::TESTDB_BUCKET_DB_PERSISTENT #ifdef USE_POSTGRES - , - Config::TESTDB_POSTGRESQL + Config::TestDbMode mode = GENERATE(Config::TESTDB_BUCKET_DB_PERSISTENT, + Config::TESTDB_POSTGRESQL); +#else + Config::TestDbMode mode = GENERATE(Config::TESTDB_BUCKET_DB_PERSISTENT); #endif - ); + #ifdef USE_POSTGRES INFO("Testing mode: " << (mode == Config::TESTDB_POSTGRESQL ? "PostgreSQL" diff --git a/src/herder/Upgrades.cpp b/src/herder/Upgrades.cpp index 8eb55047b4..a3e79d9c24 100644 --- a/src/herder/Upgrades.cpp +++ b/src/herder/Upgrades.cpp @@ -1267,6 +1267,7 @@ Upgrades::applyVersionUpgrade(Application& app, AbstractLedgerTxn& ltx, if (needUpgradeToVersion(ProtocolVersion::V_26, prevVersion, newVersion)) { SorobanNetworkConfig::updateCostTypesForV26(ltx, app); + SorobanNetworkConfig::createLedgerEntriesForV26(ltx, app); } } @@ -1419,6 +1420,18 @@ ConfigUpgradeSetFrame::upgradeNeeded(LedgerSnapshot const& ls) const } for (auto const& updatedEntry : mConfigUpgradeSet.updatedEntry) { + // The delta entry has no stored counterpart — it always needs + // to be applied (it modifies CONFIG_SETTING_FROZEN_LEDGER_KEYS). +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION + if (updatedEntry.configSettingID() == + ConfigSettingID::CONFIG_SETTING_FROZEN_LEDGER_KEYS_DELTA || + updatedEntry.configSettingID() == + ConfigSettingID::CONFIG_SETTING_FREEZE_BYPASS_TXS_DELTA) + { + return true; + } +#endif + LedgerKey key(LedgerEntryType::CONFIG_SETTING); key.configSetting().configSettingID = updatedEntry.configSettingID(); bool isSame = @@ -1438,10 +1451,76 @@ ConfigUpgradeSetFrame::applyTo(AbstractLedgerTxn& ltx, Application& app) const bool hasMemorySettingsUpgrade = false; for (auto const& updatedEntry : mConfigUpgradeSet.updatedEntry) { - LedgerKey key(LedgerEntryType::CONFIG_SETTING); auto const id = updatedEntry.configSettingID(); +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION + // Delta entries are not stored config entries. They modify the + // corresponding base entries instead. + if (id == ConfigSettingID::CONFIG_SETTING_FROZEN_LEDGER_KEYS_DELTA) + { + LedgerKey frozenKeysLk(LedgerEntryType::CONFIG_SETTING); + frozenKeysLk.configSetting().configSettingID = + CONFIG_SETTING_FROZEN_LEDGER_KEYS; + auto frozenKeysLtxe = ltx.load(frozenKeysLk); + auto& frozenKeysVec = frozenKeysLtxe.current() + .data.configSetting() + .frozenLedgerKeys() + .keys; + std::set> existing; + for (auto const& k : frozenKeysVec) + { + existing.insert(k); + } + + auto const& delta = updatedEntry.frozenLedgerKeysDelta(); + for (auto const& k : delta.keysToFreeze) + { + existing.insert(k); + } + for (auto const& k : delta.keysToUnfreeze) + { + existing.erase(k); + } + + frozenKeysVec.assign(existing.begin(), existing.end()); + continue; + } + + if (id == ConfigSettingID::CONFIG_SETTING_FREEZE_BYPASS_TXS_DELTA) + { + LedgerKey bypassTxsLk(LedgerEntryType::CONFIG_SETTING); + bypassTxsLk.configSetting().configSettingID = + CONFIG_SETTING_FREEZE_BYPASS_TXS; + auto bypassTxsLtxe = ltx.load(bypassTxsLk); + auto& bypassTxsVec = bypassTxsLtxe.current() + .data.configSetting() + .freezeBypassTxs() + .txHashes; + + std::set existing; + for (auto const& h : bypassTxsVec) + { + existing.insert(h); + } + + auto const& delta = updatedEntry.freezeBypassTxsDelta(); + for (auto const& h : delta.addTxs) + { + existing.insert(h); + } + for (auto const& h : delta.removeTxs) + { + existing.erase(h); + } + + bypassTxsVec.assign(existing.begin(), existing.end()); + continue; + } +#endif + + LedgerKey key(LedgerEntryType::CONFIG_SETTING); key.configSetting().configSettingID = id; - auto& currentEntry = ltx.load(key).current().data.configSetting(); + auto ltxe = ltx.load(key); + auto& currentEntry = ltxe.current().data.configSetting(); if (id == ConfigSettingID::CONFIG_SETTING_STATE_ARCHIVAL && currentEntry.stateArchivalSettings() .liveSorobanStateSizeWindowSampleSize != diff --git a/src/history/test/HistoryTests.cpp b/src/history/test/HistoryTests.cpp index 0322d585ec..2dd9544278 100644 --- a/src/history/test/HistoryTests.cpp +++ b/src/history/test/HistoryTests.cpp @@ -1582,6 +1582,7 @@ TEST_CASE_VERSIONS( Config cfg(getTestConfig(0)); cfg.MANUAL_CLOSE = false; cfg.MAX_CONCURRENT_SUBPROCESSES = 0; + cfg.PARALLEL_LEDGER_APPLY = false; TmpDirHistoryConfigurator tcfg; cfg = tcfg.configure(cfg, true); VirtualClock clock; diff --git a/src/ledger/NetworkConfig.cpp b/src/ledger/NetworkConfig.cpp index f127ea2dc2..b6f07a8119 100644 --- a/src/ledger/NetworkConfig.cpp +++ b/src/ledger/NetworkConfig.cpp @@ -8,8 +8,10 @@ #include "bucket/test/BucketTestUtils.h" #include "ledger/LedgerStateSnapshot.h" #include "main/Application.h" +#include "util/Logging.h" #include "util/ProtocolVersion.h" #include "util/numeric.h" +#include "util/types.h" #include #ifdef BUILD_TESTS @@ -1485,6 +1487,95 @@ SorobanNetworkConfig::isValidConfigSettingEntry(ConfigSettingEntry const& cfg, MaximumSorobanNetworkConfig:: BALLOT_TIMEOUT_INCREMENT_MILLISECONDS; break; +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION + case ConfigSettingID::CONFIG_SETTING_FROZEN_LEDGER_KEYS: + // The frozen keys entry itself is always valid (it's just a list of + // encoded keys). But it cannot be directly upgraded — only the delta + // mechanism is allowed. + valid = protocolVersionStartsFrom(ledgerVersion, ProtocolVersion::V_26); + break; + case ConfigSettingID::CONFIG_SETTING_FROZEN_LEDGER_KEYS_DELTA: + { + valid = protocolVersionStartsFrom(ledgerVersion, ProtocolVersion::V_26); + if (valid) + { + auto const& delta = cfg.frozenLedgerKeysDelta(); + auto validateEncodedKeys = + [](xdr::xvector const& encodedKeys, + char const* keySetName) -> bool { + for (auto const& encodedKey : encodedKeys) + { + try + { + LedgerKey lk; + xdr::xdr_from_opaque(encodedKey, lk); + // Only ACCOUNT, TRUSTLINE, CONTRACT_DATA, + // CONTRACT_CODE are valid + switch (lk.type()) + { + case ACCOUNT: + case CONTRACT_DATA: + case CONTRACT_CODE: + break; + case TRUSTLINE: + // Trustline keys for liquidity pool shares and + // issuer trustlines are not allowed to be frozen. + if (lk.trustLine().asset.type() == + ASSET_TYPE_POOL_SHARE || + isIssuer(lk.trustLine().accountID, + lk.trustLine().asset)) + { + return false; + } + break; + default: + return false; + } + } + catch (xdr::xdr_runtime_error const& e) + { + CLOG_WARNING( + Herder, + "Got bad upgrade: failed to decode {} in " + "CONFIG_SETTING_FROZEN_LEDGER_KEYS_DELTA: {}", + keySetName, e.what()); + return false; + } + catch (std::exception const& e) + { + CLOG_WARNING( + Herder, + "Got bad upgrade: exception validating {} in " + "CONFIG_SETTING_FROZEN_LEDGER_KEYS_DELTA: {}", + keySetName, e.what()); + return false; + } + catch (...) + { + CLOG_WARNING(Herder, + "Got bad upgrade: unknown exception " + "validating {} in " + "CONFIG_SETTING_FROZEN_LEDGER_KEYS_DELTA", + keySetName); + return false; + } + } + return true; + }; + valid = validateEncodedKeys(delta.keysToFreeze, "keysToFreeze") && + validateEncodedKeys(delta.keysToUnfreeze, "keysToUnfreeze"); + } + break; + } + case ConfigSettingID::CONFIG_SETTING_FREEZE_BYPASS_TXS: + // The bypass tx hashes entry itself is always valid. Similar to frozen + // keys, it cannot be directly upgraded — only via delta. + valid = protocolVersionStartsFrom(ledgerVersion, ProtocolVersion::V_26); + break; + case ConfigSettingID::CONFIG_SETTING_FREEZE_BYPASS_TXS_DELTA: + valid = protocolVersionStartsFrom(ledgerVersion, ProtocolVersion::V_26); + break; +#endif default: break; } @@ -1507,7 +1598,12 @@ SorobanNetworkConfig::isNonUpgradeableConfigSettingEntry( // never be changed via upgrade return cfg == ConfigSettingID::CONFIG_SETTING_LIVE_SOROBAN_STATE_SIZE_WINDOW || - cfg == ConfigSettingID::CONFIG_SETTING_EVICTION_ITERATOR; + cfg == ConfigSettingID::CONFIG_SETTING_EVICTION_ITERATOR +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION + || cfg == ConfigSettingID::CONFIG_SETTING_FROZEN_LEDGER_KEYS || + cfg == ConfigSettingID::CONFIG_SETTING_FREEZE_BYPASS_TXS +#endif + ; } void @@ -1659,6 +1755,7 @@ SorobanNetworkConfig::initializeGenesisLedgerForTesting( if (protocolVersionStartsFrom(genesisLedgerProtocol, ProtocolVersion::V_26)) { SorobanNetworkConfig::updateCostTypesForV26(ltx, app); + SorobanNetworkConfig::createLedgerEntriesForV26(ltx, app); } } @@ -1689,6 +1786,11 @@ SorobanNetworkConfig::loadFromLedger(LedgerSnapshot const& ls) config.loadLedgerCostExtConfig(ls); config.loadSCPTimingConfig(ls); } + if (protocolVersionStartsFrom(protocolVersion, ProtocolVersion::V_26)) + { + config.loadFrozenLedgerKeys(ls); + config.loadFreezeBypassTxs(ls); + } // NB: this should follow loading/updating state size window // size and state archival settings config.computeRentWriteFee(protocolVersion); @@ -2424,6 +2526,96 @@ SorobanNetworkConfig::maxLedgerResources() const return Resource(limits); } +bool +SorobanNetworkConfig::hasFrozenKeys() const +{ + return !mFrozenLedgerKeys.empty(); +} + +bool +SorobanNetworkConfig::isKeyFrozen(LedgerKey const& key) const +{ + return mFrozenLedgerKeys.find(key) != mFrozenLedgerKeys.end(); +} + +bool +SorobanNetworkConfig::isFreezeBypassTx(Hash const& txHash) const +{ + return mFreezeBypassTxs.find(txHash) != mFreezeBypassTxs.end(); +} + +void +SorobanNetworkConfig::loadFrozenLedgerKeys(LedgerSnapshot const& ls) +{ + ZoneScoped; +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION + mFrozenLedgerKeys.clear(); + + LedgerKey key(CONFIG_SETTING); + key.configSetting().configSettingID = CONFIG_SETTING_FROZEN_LEDGER_KEYS; + auto le = ls.load(key); + releaseAssertOrThrow(le); + + auto const& frozenKeys = + le.current().data.configSetting().frozenLedgerKeys().keys; + for (auto const& encodedKey : frozenKeys) + { + LedgerKey lk; + xdr::xdr_from_opaque(encodedKey, lk); + mFrozenLedgerKeys.insert(lk); + } +#endif +} + +void +SorobanNetworkConfig::loadFreezeBypassTxs(LedgerSnapshot const& ls) +{ + ZoneScoped; +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION + mFreezeBypassTxs.clear(); + + LedgerKey key(CONFIG_SETTING); + key.configSetting().configSettingID = CONFIG_SETTING_FREEZE_BYPASS_TXS; + auto le = ls.load(key); + releaseAssertOrThrow(le); + + auto const& txHashes = + le.current().data.configSetting().freezeBypassTxs().txHashes; + for (auto const& txHash : txHashes) + { + mFreezeBypassTxs.insert(txHash); + } +#endif +} + +void +SorobanNetworkConfig::createLedgerEntriesForV26(AbstractLedgerTxn& ltx, + Application& app) +{ + ZoneScoped; +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION + ConfigSettingEntry frozenKeysEntry; + frozenKeysEntry.configSettingID(CONFIG_SETTING_FROZEN_LEDGER_KEYS); + // Start with an empty frozen keys set + frozenKeysEntry.frozenLedgerKeys().keys.clear(); + + ConfigSettingEntry bypassTxsEntry; + bypassTxsEntry.configSettingID(CONFIG_SETTING_FREEZE_BYPASS_TXS); + // Start with an empty bypass tx hash set + bypassTxsEntry.freezeBypassTxs().txHashes.clear(); + + LedgerEntry e; + e.data.type(CONFIG_SETTING); + e.data.configSetting() = frozenKeysEntry; + LedgerTxn inner(ltx); + inner.create(e); + + e.data.configSetting() = bypassTxsEntry; + inner.create(e); + inner.commit(); +#endif +} + #ifdef BUILD_TESTS void SorobanNetworkConfig::updateRecalibratedCostTypesForV20( @@ -2611,7 +2803,21 @@ SorobanNetworkConfig::operator==(SorobanNetworkConfig const& other) const mBallotTimeoutInitialMilliseconds == other.ballotTimeoutInitialMilliseconds() && mBallotTimeoutIncrementMilliseconds == - other.ballotTimeoutIncrementMilliseconds(); + other.ballotTimeoutIncrementMilliseconds() && + mFrozenLedgerKeys == other.frozenLedgerKeys() && + mFreezeBypassTxs == other.freezeBypassTxs(); +} + +UnorderedSet const& +SorobanNetworkConfig::frozenLedgerKeys() const +{ + return mFrozenLedgerKeys; +} + +UnorderedSet const& +SorobanNetworkConfig::freezeBypassTxs() const +{ + return mFreezeBypassTxs; } #endif diff --git a/src/ledger/NetworkConfig.h b/src/ledger/NetworkConfig.h index 0999d57311..ace011c12d 100644 --- a/src/ledger/NetworkConfig.h +++ b/src/ledger/NetworkConfig.h @@ -297,6 +297,12 @@ class SorobanNetworkConfig // upgrade. static void updateCostTypesForV26(AbstractLedgerTxn& ltx, Application& app); + // Creates the new ledger entries introduced in v26. + // This should happen once during the correspondent protocol version + // upgrade. + static void createLedgerEntriesForV26(AbstractLedgerTxn& ltx, + Application& app); + // Creates the new ledger entries introduced in v23 and updates the existing // entries. // This should happen once during the correspondent protocol version @@ -449,6 +455,11 @@ class SorobanNetworkConfig uint32_t ballotTimeoutInitialMilliseconds() const; uint32_t ballotTimeoutIncrementMilliseconds() const; + // Frozen ledger keys + bool hasFrozenKeys() const; + bool isKeyFrozen(LedgerKey const& key) const; + bool isFreezeBypassTx(Hash const& txHash) const; + #ifdef BUILD_TESTS // Update the protocol 20 cost types to match the real network // configuration. @@ -461,6 +472,9 @@ class SorobanNetworkConfig static void updateRecalibratedCostTypesForV20(AbstractLedgerTxn& ltx); bool operator==(SorobanNetworkConfig const& other) const; + + UnorderedSet const& frozenLedgerKeys() const; + UnorderedSet const& freezeBypassTxs() const; #endif private: @@ -483,6 +497,8 @@ class SorobanNetworkConfig void loadParallelComputeConfig(LedgerSnapshot const& ls); void loadLedgerCostExtConfig(LedgerSnapshot const& ls); void loadSCPTimingConfig(LedgerSnapshot const& ls); + void loadFrozenLedgerKeys(LedgerSnapshot const& ls); + void loadFreezeBypassTxs(LedgerSnapshot const& ls); void computeRentWriteFee(uint32_t protocolVersion); #ifdef BUILD_TESTS @@ -561,6 +577,10 @@ class SorobanNetworkConfig uint32_t mNominationTimeoutIncrementMilliseconds{}; uint32_t mBallotTimeoutInitialMilliseconds{}; uint32_t mBallotTimeoutIncrementMilliseconds{}; + + // Frozen ledger keys + UnorderedSet mFrozenLedgerKeys; + UnorderedSet mFreezeBypassTxs; }; #ifdef BUILD_TESTS diff --git a/src/protocol-next/xdr b/src/protocol-next/xdr index 520b487de5..31955e3732 160000 --- a/src/protocol-next/xdr +++ b/src/protocol-next/xdr @@ -1 +1 @@ -Subproject commit 520b487de58fc6c809dd0a959fcc765b3f00b38d +Subproject commit 31955e373224ada03263f0b3c65a12b0608f20da diff --git a/src/simulation/LoadGenerator.cpp b/src/simulation/LoadGenerator.cpp index d288e0c3b5..6137970982 100644 --- a/src/simulation/LoadGenerator.cpp +++ b/src/simulation/LoadGenerator.cpp @@ -1551,6 +1551,54 @@ GeneratedLoadConfig::copySorobanNetworkConfigToUpgradeConfig( updatedConfig.nominationTimeoutInitialMilliseconds(); upgradeCfg.nominationTimeoutIncrementMilliseconds = updatedConfig.nominationTimeoutIncrementMilliseconds(); + +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION + // Compute frozen ledger keys delta + auto const& baseKeys = baseConfig.frozenLedgerKeys(); + auto const& updatedKeys = updatedConfig.frozenLedgerKeys(); + if (baseKeys != updatedKeys) + { + FrozenLedgerKeysDelta delta; + for (auto const& key : updatedKeys) + { + if (baseKeys.find(key) == baseKeys.end()) + { + delta.keysToFreeze.emplace_back(xdr::xdr_to_opaque(key)); + } + } + for (auto const& key : baseKeys) + { + if (updatedKeys.find(key) == updatedKeys.end()) + { + delta.keysToUnfreeze.emplace_back(xdr::xdr_to_opaque(key)); + } + } + upgradeCfg.frozenLedgerKeysDelta = delta; + } + + // Compute freeze bypass tx hashes delta + auto const& baseBypassTxs = baseConfig.freezeBypassTxs(); + auto const& updatedBypassTxs = updatedConfig.freezeBypassTxs(); + if (baseBypassTxs != updatedBypassTxs) + { + FreezeBypassTxsDelta delta; + for (auto const& txHash : updatedBypassTxs) + { + if (baseBypassTxs.find(txHash) == baseBypassTxs.end()) + { + delta.addTxs.emplace_back(txHash); + } + } + for (auto const& txHash : baseBypassTxs) + { + if (updatedBypassTxs.find(txHash) == updatedBypassTxs.end()) + { + delta.removeTxs.emplace_back(txHash); + } + } + upgradeCfg.freezeBypassTxsDelta = delta; + } +#endif } GeneratedLoadConfig diff --git a/src/simulation/TxGenerator.cpp b/src/simulation/TxGenerator.cpp index c909218edf..7bfac2ec5c 100644 --- a/src/simulation/TxGenerator.cpp +++ b/src/simulation/TxGenerator.cpp @@ -880,6 +880,38 @@ TxGenerator::getConfigUpgradeSetFromLoadConfig( continue; } +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION + // The delta has no stored ledger entry — construct it from the + // upgrade config if present, then move on. + if (type == CONFIG_SETTING_FROZEN_LEDGER_KEYS_DELTA) + { + if (upgradeCfg.frozenLedgerKeysDelta.has_value()) + { + ConfigSettingEntry deltaEntry; + deltaEntry.configSettingID( + CONFIG_SETTING_FROZEN_LEDGER_KEYS_DELTA); + deltaEntry.frozenLedgerKeysDelta() = + *upgradeCfg.frozenLedgerKeysDelta; + updatedEntries.emplace_back(deltaEntry); + } + continue; + } + + if (type == CONFIG_SETTING_FREEZE_BYPASS_TXS_DELTA) + { + if (upgradeCfg.freezeBypassTxsDelta.has_value()) + { + ConfigSettingEntry deltaEntry; + deltaEntry.configSettingID( + CONFIG_SETTING_FREEZE_BYPASS_TXS_DELTA); + deltaEntry.freezeBypassTxsDelta() = + *upgradeCfg.freezeBypassTxsDelta; + updatedEntries.emplace_back(deltaEntry); + } + continue; + } +#endif + auto entryPtr = lsg.load(configSettingKey(type)); // This could happen if we have not yet upgraded if ((t == CONFIG_SETTING_CONTRACT_PARALLEL_COMPUTE_V0 || diff --git a/src/simulation/TxGenerator.h b/src/simulation/TxGenerator.h index 9de4887a4e..763a5f947f 100644 --- a/src/simulation/TxGenerator.h +++ b/src/simulation/TxGenerator.h @@ -87,6 +87,13 @@ struct SorobanUpgradeConfig std::optional nominationTimeoutIncrementMilliseconds{}; std::optional ballotTimeoutInitialMilliseconds{}; std::optional ballotTimeoutIncrementMilliseconds{}; + +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION + // Frozen ledger keys delta + std::optional frozenLedgerKeysDelta{}; + // Freeze bypass tx hashes delta + std::optional freezeBypassTxsDelta{}; +#endif }; class TxGenerator diff --git a/src/test/TestExceptions.cpp b/src/test/TestExceptions.cpp index 8d28035158..adea9286a9 100644 --- a/src/test/TestExceptions.cpp +++ b/src/test/TestExceptions.cpp @@ -399,6 +399,10 @@ throwIf(ClaimClaimableBalanceResult const& result) throw ex_CLAIM_CLAIMABLE_BALANCE_NOT_AUTHORIZED{}; case CLAIM_CLAIMABLE_BALANCE_NO_TRUST: throw ex_CLAIM_CLAIMABLE_BALANCE_NO_TRUST{}; +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION + case CLAIM_CLAIMABLE_BALANCE_TRUSTLINE_FROZEN: + throw ex_CLAIM_CLAIMABLE_BALANCE_TRUSTLINE_FROZEN{}; +#endif case CLAIM_CLAIMABLE_BALANCE_SUCCESS: break; default: @@ -483,6 +487,10 @@ throwIf(LiquidityPoolDepositResult const& result) throw ex_LIQUIDITY_POOL_DEPOSIT_BAD_PRICE{}; case LIQUIDITY_POOL_DEPOSIT_POOL_FULL: throw ex_LIQUIDITY_POOL_DEPOSIT_POOL_FULL{}; +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION + case LIQUIDITY_POOL_DEPOSIT_TRUSTLINE_FROZEN: + throw ex_LIQUIDITY_POOL_DEPOSIT_TRUSTLINE_FROZEN{}; +#endif case LIQUIDITY_POOL_DEPOSIT_SUCCESS: break; default: @@ -505,6 +513,10 @@ throwIf(LiquidityPoolWithdrawResult const& result) throw ex_LIQUIDITY_POOL_WITHDRAW_LINE_FULL{}; case LIQUIDITY_POOL_WITHDRAW_UNDER_MINIMUM: throw ex_LIQUIDITY_POOL_WITHDRAW_UNDER_MINIMUM{}; +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION + case LIQUIDITY_POOL_WITHDRAW_TRUSTLINE_FROZEN: + throw ex_LIQUIDITY_POOL_WITHDRAW_TRUSTLINE_FROZEN{}; +#endif case LIQUIDITY_POOL_WITHDRAW_SUCCESS: break; default: @@ -581,6 +593,10 @@ throwIf(TransactionResult const& result) throw ex_txINSUFFICIENT_BALANCE{}; case txBAD_AUTH: throw ex_txBAD_AUTH{}; +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION + case txFROZEN_KEY_ACCESSED: + throw ex_txFROZEN_KEY_ACCESSED{}; +#endif default: throw ex_UNKNOWN{}; } diff --git a/src/test/TestExceptions.h b/src/test/TestExceptions.h index 8b2308a350..8daaa60296 100644 --- a/src/test/TestExceptions.h +++ b/src/test/TestExceptions.h @@ -30,6 +30,7 @@ TEST_EXCEPTION(ex_txNO_ACCOUNT) TEST_EXCEPTION(ex_txINTERNAL_ERROR) TEST_EXCEPTION(ex_txINSUFFICIENT_BALANCE) TEST_EXCEPTION(ex_txBAD_AUTH) +TEST_EXCEPTION(ex_txFROZEN_KEY_ACCESSED) TEST_EXCEPTION(ex_opBAD_AUTH) TEST_EXCEPTION(ex_opNO_ACCOUNT) @@ -161,6 +162,7 @@ TEST_EXCEPTION(ex_CLAIM_CLAIMABLE_BALANCE_CANNOT_CLAIM) TEST_EXCEPTION(ex_CLAIM_CLAIMABLE_BALANCE_LINE_FULL) TEST_EXCEPTION(ex_CLAIM_CLAIMABLE_BALANCE_NOT_AUTHORIZED) TEST_EXCEPTION(ex_CLAIM_CLAIMABLE_BALANCE_NO_TRUST) +TEST_EXCEPTION(ex_CLAIM_CLAIMABLE_BALANCE_TRUSTLINE_FROZEN) TEST_EXCEPTION(ex_CLAWBACK_MALFORMED) TEST_EXCEPTION(ex_CLAWBACK_NO_TRUST) @@ -183,12 +185,14 @@ TEST_EXCEPTION(ex_LIQUIDITY_POOL_DEPOSIT_UNDERFUNDED) TEST_EXCEPTION(ex_LIQUIDITY_POOL_DEPOSIT_LINE_FULL) TEST_EXCEPTION(ex_LIQUIDITY_POOL_DEPOSIT_BAD_PRICE) TEST_EXCEPTION(ex_LIQUIDITY_POOL_DEPOSIT_POOL_FULL) +TEST_EXCEPTION(ex_LIQUIDITY_POOL_DEPOSIT_TRUSTLINE_FROZEN) TEST_EXCEPTION(ex_LIQUIDITY_POOL_WITHDRAW_MALFORMED) TEST_EXCEPTION(ex_LIQUIDITY_POOL_WITHDRAW_NO_TRUST) TEST_EXCEPTION(ex_LIQUIDITY_POOL_WITHDRAW_UNDERFUNDED) TEST_EXCEPTION(ex_LIQUIDITY_POOL_WITHDRAW_LINE_FULL) TEST_EXCEPTION(ex_LIQUIDITY_POOL_WITHDRAW_UNDER_MINIMUM) +TEST_EXCEPTION(ex_LIQUIDITY_POOL_WITHDRAW_TRUSTLINE_FROZEN) TEST_EXCEPTION(ex_INVOKE_HOST_FUNCTION_MALFORMED) TEST_EXCEPTION(ex_INVOKE_HOST_FUNCTION_TRAPPED) diff --git a/src/test/TxTests.cpp b/src/test/TxTests.cpp index f29b9f9f26..e7f385e93c 100644 --- a/src/test/TxTests.cpp +++ b/src/test/TxTests.cpp @@ -291,7 +291,11 @@ applyCheck(TransactionTestFramePtr tx, Application& app, bool checkSeqNum) bool earlyFailure = (code == txMISSING_OPERATION || code == txTOO_EARLY || code == txTOO_LATE || code == txINSUFFICIENT_FEE || - code == txBAD_SEQ || code == txMALFORMED); + code == txBAD_SEQ || code == txMALFORMED +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION + || code == txFROZEN_KEY_ACCESSED +#endif + ); // verify that the sequence number changed (v10+) // do not perform the check if there was a failure before // or during the sequence number processing diff --git a/src/testdata/ledger-close-meta-enable-classic-events-v2-protocol-26-soroban.json b/src/testdata/ledger-close-meta-enable-classic-events-v2-protocol-26-soroban.json index 37c04915b6..690b11c82a 100644 --- a/src/testdata/ledger-close-meta-enable-classic-events-v2-protocol-26-soroban.json +++ b/src/testdata/ledger-close-meta-enable-classic-events-v2-protocol-26-soroban.json @@ -6,24 +6,24 @@ "v": 0 }, "ledgerHeader": { - "hash": "f8e249ac924d72fe4b2a5bd6b28717b1d164fe0b5c7d32d64d7c27b63d7f70d7", + "hash": "059f74d286f715f5cb199115afe231ae62c98a665cbe6223eca865f6e03e4711", "header": { "ledgerVersion": 26, - "previousLedgerHash": "c5be64a7ce74a1a5d2950fb12c1542cb6cc2d69dfa0af500d84751a1812a36e7", + "previousLedgerHash": "7584ec52c748b8c4ad78730fe47b3e2528e47fd95dad346ca3c1eb61248a55ab", "scpValue": { - "txSetHash": "8fc7e93ed2170bf10afa649c59868ab3a4a9c6511b9e21d9a69a3c973d7ba0b5", + "txSetHash": "7eeacd54ebfb80905aab2931876c09b5da9324d7d2d704245c0a90189ef52cfd", "closeTime": 1451692801, "upgrades": [], "ext": { "v": "STELLAR_VALUE_SIGNED", "lcValueSignature": { "nodeID": "GDDOUW25MRFLNXQMN3OODP6JQEXSGLMHAFZV4XPQ2D3GA4QFIDMEJG2O", - "signature": "c0fe7e296a63c47976c65d0443ffe293df794f83243b02954011e2073b20213f5bcff79a1ee67d9a7ae8d18db2dbc7a9059831547fd7c8f1c3a53910f8304f07" + "signature": "43c151074ef7c010bc6ed7b78e93251370144d39c694f4f2b7d8cb35df87729b97cd3957b42bc541cb2f4c0401974bb563f8e1fc189b5057327f5e6e9773cb04" } } }, - "txSetResultHash": "d3bde14b15428d27136e8ce2b0f86cf9c84b9cacfd3258317e382187b5e2f08e", - "bucketListHash": "3e9b4cc079d0fdf987005647ac5b891aa0c313eb95f2d5f3c5ef4c711477fae9", + "txSetResultHash": "7f990d484919d1099f8d79eaf2780b3d19363dc01f7a15bb4f8b0881c5db9bcf", + "bucketListHash": "bafbb8761cb8d2dca18ad3eada232def91a210823c3b10d7ca29773bd3b64297", "ledgerSeq": 31, "totalCoins": 1000000000000000000, "feePool": 9167489, @@ -49,7 +49,7 @@ "txSet": { "v": 1, "v1TxSet": { - "previousLedgerHash": "c5be64a7ce74a1a5d2950fb12c1542cb6cc2d69dfa0af500d84751a1812a36e7", + "previousLedgerHash": "7584ec52c748b8c4ad78730fe47b3e2528e47fd95dad346ca3c1eb61248a55ab", "phases": [ { "v": 0, @@ -505,18 +505,19 @@ "v": 0 }, "result": { - "transactionHash": "89c02a589a36decde3f1e6f9aa8b629fc012daa68eefc76b3763bcc1fdb2609d", + "transactionHash": "d9d747111c25cd7e829c819f13fbfe771849c9a994dcc2dc554ac20841322f01", "result": { - "feeCharged": 54107, + "feeCharged": 94335, "result": { "code": "txSUCCESS", "results": [ { "code": "opINNER", "tr": { - "type": "RESTORE_FOOTPRINT", - "restoreFootprintResult": { - "code": "RESTORE_FOOTPRINT_SUCCESS" + "type": "INVOKE_HOST_FUNCTION", + "invokeHostFunctionResult": { + "code": "INVOKE_HOST_FUNCTION_SUCCESS", + "success": "cbbc48750debb8535093b3deaf88ac7f4cff87425576a58de2bac754acdb4616" } } } @@ -531,13 +532,13 @@ { "type": "LEDGER_ENTRY_STATE", "state": { - "lastModifiedLedgerSeq": 11, + "lastModifiedLedgerSeq": 13, "data": { "type": "ACCOUNT", "account": { - "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", + "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", "balance": 400000000, - "seqNum": 47244640256, + "seqNum": 55834574848, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -561,9 +562,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", - "balance": 398999900, - "seqNum": 47244640256, + "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", + "balance": 399885833, + "seqNum": 55834574848, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -595,9 +596,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", - "balance": 398999900, - "seqNum": 47244640256, + "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", + "balance": 399885833, + "seqNum": 55834574848, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -621,9 +622,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", - "balance": 398999900, - "seqNum": 47244640257, + "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", + "balance": 399885833, + "seqNum": 55834574849, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -672,9 +673,9 @@ }, "changes": [ { - "type": "LEDGER_ENTRY_RESTORED", - "restored": { - "lastModifiedLedgerSeq": 10, + "type": "LEDGER_ENTRY_CREATED", + "created": { + "lastModifiedLedgerSeq": 31, "data": { "type": "CONTRACT_DATA", "contractData": { @@ -684,7 +685,7 @@ "contract": "CAA3QKIP2SNVXUJTB4HKOGF55JTSSMQGED3FZYNHMNSXYV3DRRMAWA3Y", "key": { "type": "SCV_SYMBOL", - "sym": "archived" + "sym": "key" }, "durability": "PERSISTENT", "val": { @@ -699,13 +700,13 @@ } }, { - "type": "LEDGER_ENTRY_RESTORED", - "restored": { + "type": "LEDGER_ENTRY_CREATED", + "created": { "lastModifiedLedgerSeq": 31, "data": { "type": "TTL", "ttl": { - "keyHash": "4791962cd1e2c7b8f8af3f96514f9777f0156a48261fb885a571a7f69b33a058", + "keyHash": "764f4e59e20ac1a357f9f26ab0eaf46d196ab74822db44f039353a6f114864aa", "liveUntilLedgerSeq": 50 } }, @@ -723,7 +724,9 @@ "ext": { "v": 0 }, - "returnValue": null + "returnValue": { + "type": "SCV_VOID" + } }, "events": [ { @@ -744,14 +747,14 @@ }, { "type": "SCV_ADDRESS", - "address": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB" + "address": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD" } ], "data": { "type": "SCV_I128", "i128": { "hi": 0, - "lo": 1000100 + "lo": 114167 } } } @@ -776,14 +779,14 @@ }, { "type": "SCV_ADDRESS", - "address": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB" + "address": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD" } ], "data": { "type": "SCV_I128", "i128": { "hi": -1, - "lo": 18446744073708605623 + "lo": 18446744073709531784 } } } @@ -802,9 +805,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", - "balance": 398999900, - "seqNum": 47244640257, + "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", + "balance": 399885833, + "seqNum": 55834574849, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -852,9 +855,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", - "balance": 399945893, - "seqNum": 47244640257, + "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", + "balance": 399905665, + "seqNum": 55834574849, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -902,18 +905,18 @@ "v": 0 }, "result": { - "transactionHash": "47fd1151d61624bbce0b9a9f7883c60ccd70bcdf6f6c640a53e13e3fa856609f", + "transactionHash": "7c5d889bd9b1b3a9061b13f9a23443a54539751ee31a0ae661d996b45c44675e", "result": { - "feeCharged": 51269, + "feeCharged": 54053, "result": { - "code": "txSUCCESS", + "code": "txFAILED", "results": [ { "code": "opINNER", "tr": { - "type": "EXTEND_FOOTPRINT_TTL", - "extendFootprintTTLResult": { - "code": "EXTEND_FOOTPRINT_TTL_SUCCESS" + "type": "INVOKE_HOST_FUNCTION", + "invokeHostFunctionResult": { + "code": "INVOKE_HOST_FUNCTION_TRAPPED" } } } @@ -928,13 +931,13 @@ { "type": "LEDGER_ENTRY_STATE", "state": { - "lastModifiedLedgerSeq": 12, + "lastModifiedLedgerSeq": 14, "data": { "type": "ACCOUNT", "account": { - "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", + "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", "balance": 400000000, - "seqNum": 51539607552, + "seqNum": 60129542144, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -958,9 +961,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", - "balance": 398999900, - "seqNum": 51539607552, + "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", + "balance": 399905947, + "seqNum": 60129542144, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -992,9 +995,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", - "balance": 398999900, - "seqNum": 51539607552, + "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", + "balance": 399905947, + "seqNum": 60129542144, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1018,9 +1021,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", - "balance": 398999900, - "seqNum": 51539607553, + "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", + "balance": 399905947, + "seqNum": 60129542145, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1062,87 +1065,9 @@ } } ], - "operations": [ - { - "ext": { - "v": 0 - }, - "changes": [ - { - "type": "LEDGER_ENTRY_STATE", - "state": { - "lastModifiedLedgerSeq": 9, - "data": { - "type": "TTL", - "ttl": { - "keyHash": "091ddece931776a53f93869b82c24e132cc12d00d961fac09bc3b9cb9021c62d", - "liveUntilLedgerSeq": 10009 - } - }, - "ext": { - "v": 0 - } - } - }, - { - "type": "LEDGER_ENTRY_UPDATED", - "updated": { - "lastModifiedLedgerSeq": 31, - "data": { - "type": "TTL", - "ttl": { - "keyHash": "091ddece931776a53f93869b82c24e132cc12d00d961fac09bc3b9cb9021c62d", - "liveUntilLedgerSeq": 10031 - } - }, - "ext": { - "v": 0 - } - } - }, - { - "type": "LEDGER_ENTRY_STATE", - "state": { - "lastModifiedLedgerSeq": 9, - "data": { - "type": "TTL", - "ttl": { - "keyHash": "60313f9b273db0b14c3e503cf6cc152dd14a0c57e5e81a23e86b4e27a23a2c06", - "liveUntilLedgerSeq": 10009 - } - }, - "ext": { - "v": 0 - } - } - }, - { - "type": "LEDGER_ENTRY_UPDATED", - "updated": { - "lastModifiedLedgerSeq": 31, - "data": { - "type": "TTL", - "ttl": { - "keyHash": "60313f9b273db0b14c3e503cf6cc152dd14a0c57e5e81a23e86b4e27a23a2c06", - "liveUntilLedgerSeq": 10031 - } - }, - "ext": { - "v": 0 - } - } - } - ], - "events": [] - } - ], + "operations": [], "txChangesAfter": [], - "sorobanMeta": { - "ext": { - "v": 0 - }, - "returnValue": null - }, + "sorobanMeta": null, "events": [ { "stage": "TRANSACTION_EVENT_STAGE_BEFORE_ALL_TXS", @@ -1162,14 +1087,14 @@ }, { "type": "SCV_ADDRESS", - "address": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ" + "address": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR" } ], "data": { "type": "SCV_I128", "i128": { "hi": 0, - "lo": 1000100 + "lo": 94053 } } } @@ -1194,14 +1119,14 @@ }, { "type": "SCV_ADDRESS", - "address": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ" + "address": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR" } ], "data": { "type": "SCV_I128", "i128": { "hi": -1, - "lo": 18446744073708602785 + "lo": 18446744073709511616 } } } @@ -1220,9 +1145,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", - "balance": 398999900, - "seqNum": 51539607553, + "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", + "balance": 399905947, + "seqNum": 60129542145, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1270,9 +1195,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", - "balance": 399948731, - "seqNum": 51539607553, + "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", + "balance": 399945947, + "seqNum": 60129542145, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1320,9 +1245,9 @@ "v": 0 }, "result": { - "transactionHash": "7c5d889bd9b1b3a9061b13f9a23443a54539751ee31a0ae661d996b45c44675e", + "transactionHash": "9f16a9c2ea090a97d6557e89266cad9a19a1a9db6c1d3698e4b189af34e5e585", "result": { - "feeCharged": 54053, + "feeCharged": 45161, "result": { "code": "txFAILED", "results": [ @@ -1331,7 +1256,7 @@ "tr": { "type": "INVOKE_HOST_FUNCTION", "invokeHostFunctionResult": { - "code": "INVOKE_HOST_FUNCTION_TRAPPED" + "code": "INVOKE_HOST_FUNCTION_RESOURCE_LIMIT_EXCEEDED" } } } @@ -1346,13 +1271,13 @@ { "type": "LEDGER_ENTRY_STATE", "state": { - "lastModifiedLedgerSeq": 14, + "lastModifiedLedgerSeq": 15, "data": { "type": "ACCOUNT", "account": { - "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", + "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", "balance": 400000000, - "seqNum": 60129542144, + "seqNum": 64424509440, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1376,9 +1301,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", - "balance": 399905947, - "seqNum": 60129542144, + "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", + "balance": 398953838, + "seqNum": 64424509440, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1410,9 +1335,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", - "balance": 399905947, - "seqNum": 60129542144, + "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", + "balance": 398953838, + "seqNum": 64424509440, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1436,9 +1361,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", - "balance": 399905947, - "seqNum": 60129542145, + "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", + "balance": 398953838, + "seqNum": 64424509441, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1502,14 +1427,14 @@ }, { "type": "SCV_ADDRESS", - "address": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR" + "address": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU" } ], "data": { "type": "SCV_I128", "i128": { "hi": 0, - "lo": 94053 + "lo": 1046162 } } } @@ -1534,14 +1459,14 @@ }, { "type": "SCV_ADDRESS", - "address": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR" + "address": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU" } ], "data": { "type": "SCV_I128", "i128": { "hi": -1, - "lo": 18446744073709511616 + "lo": 18446744073708550615 } } } @@ -1560,9 +1485,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", - "balance": 399905947, - "seqNum": 60129542145, + "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", + "balance": 398953838, + "seqNum": 64424509441, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1610,9 +1535,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", - "balance": 399945947, - "seqNum": 60129542145, + "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", + "balance": 399954839, + "seqNum": 64424509441, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1660,18 +1585,18 @@ "v": 0 }, "result": { - "transactionHash": "9f16a9c2ea090a97d6557e89266cad9a19a1a9db6c1d3698e4b189af34e5e585", + "transactionHash": "47fd1151d61624bbce0b9a9f7883c60ccd70bcdf6f6c640a53e13e3fa856609f", "result": { - "feeCharged": 45161, + "feeCharged": 51269, "result": { - "code": "txFAILED", + "code": "txSUCCESS", "results": [ { "code": "opINNER", "tr": { - "type": "INVOKE_HOST_FUNCTION", - "invokeHostFunctionResult": { - "code": "INVOKE_HOST_FUNCTION_RESOURCE_LIMIT_EXCEEDED" + "type": "EXTEND_FOOTPRINT_TTL", + "extendFootprintTTLResult": { + "code": "EXTEND_FOOTPRINT_TTL_SUCCESS" } } } @@ -1686,13 +1611,13 @@ { "type": "LEDGER_ENTRY_STATE", "state": { - "lastModifiedLedgerSeq": 15, + "lastModifiedLedgerSeq": 12, "data": { "type": "ACCOUNT", "account": { - "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", + "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", "balance": 400000000, - "seqNum": 64424509440, + "seqNum": 51539607552, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1716,9 +1641,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", - "balance": 398953838, - "seqNum": 64424509440, + "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", + "balance": 398999900, + "seqNum": 51539607552, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1750,9 +1675,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", - "balance": 398953838, - "seqNum": 64424509440, + "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", + "balance": 398999900, + "seqNum": 51539607552, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1776,9 +1701,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", - "balance": 398953838, - "seqNum": 64424509441, + "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", + "balance": 398999900, + "seqNum": 51539607553, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1820,9 +1745,87 @@ } } ], - "operations": [], + "operations": [ + { + "ext": { + "v": 0 + }, + "changes": [ + { + "type": "LEDGER_ENTRY_STATE", + "state": { + "lastModifiedLedgerSeq": 9, + "data": { + "type": "TTL", + "ttl": { + "keyHash": "091ddece931776a53f93869b82c24e132cc12d00d961fac09bc3b9cb9021c62d", + "liveUntilLedgerSeq": 10009 + } + }, + "ext": { + "v": 0 + } + } + }, + { + "type": "LEDGER_ENTRY_UPDATED", + "updated": { + "lastModifiedLedgerSeq": 31, + "data": { + "type": "TTL", + "ttl": { + "keyHash": "091ddece931776a53f93869b82c24e132cc12d00d961fac09bc3b9cb9021c62d", + "liveUntilLedgerSeq": 10031 + } + }, + "ext": { + "v": 0 + } + } + }, + { + "type": "LEDGER_ENTRY_STATE", + "state": { + "lastModifiedLedgerSeq": 9, + "data": { + "type": "TTL", + "ttl": { + "keyHash": "60313f9b273db0b14c3e503cf6cc152dd14a0c57e5e81a23e86b4e27a23a2c06", + "liveUntilLedgerSeq": 10009 + } + }, + "ext": { + "v": 0 + } + } + }, + { + "type": "LEDGER_ENTRY_UPDATED", + "updated": { + "lastModifiedLedgerSeq": 31, + "data": { + "type": "TTL", + "ttl": { + "keyHash": "60313f9b273db0b14c3e503cf6cc152dd14a0c57e5e81a23e86b4e27a23a2c06", + "liveUntilLedgerSeq": 10031 + } + }, + "ext": { + "v": 0 + } + } + } + ], + "events": [] + } + ], "txChangesAfter": [], - "sorobanMeta": null, + "sorobanMeta": { + "ext": { + "v": 0 + }, + "returnValue": null + }, "events": [ { "stage": "TRANSACTION_EVENT_STAGE_BEFORE_ALL_TXS", @@ -1842,14 +1845,14 @@ }, { "type": "SCV_ADDRESS", - "address": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU" + "address": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ" } ], "data": { "type": "SCV_I128", "i128": { "hi": 0, - "lo": 1046162 + "lo": 1000100 } } } @@ -1874,14 +1877,14 @@ }, { "type": "SCV_ADDRESS", - "address": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU" + "address": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ" } ], "data": { "type": "SCV_I128", "i128": { "hi": -1, - "lo": 18446744073708550615 + "lo": 18446744073708602785 } } } @@ -1900,9 +1903,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", - "balance": 398953838, - "seqNum": 64424509441, + "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", + "balance": 398999900, + "seqNum": 51539607553, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1950,9 +1953,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", - "balance": 399954839, - "seqNum": 64424509441, + "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", + "balance": 399948731, + "seqNum": 51539607553, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -2000,19 +2003,18 @@ "v": 0 }, "result": { - "transactionHash": "d9d747111c25cd7e829c819f13fbfe771849c9a994dcc2dc554ac20841322f01", + "transactionHash": "89c02a589a36decde3f1e6f9aa8b629fc012daa68eefc76b3763bcc1fdb2609d", "result": { - "feeCharged": 94335, + "feeCharged": 54107, "result": { "code": "txSUCCESS", "results": [ { "code": "opINNER", "tr": { - "type": "INVOKE_HOST_FUNCTION", - "invokeHostFunctionResult": { - "code": "INVOKE_HOST_FUNCTION_SUCCESS", - "success": "cbbc48750debb8535093b3deaf88ac7f4cff87425576a58de2bac754acdb4616" + "type": "RESTORE_FOOTPRINT", + "restoreFootprintResult": { + "code": "RESTORE_FOOTPRINT_SUCCESS" } } } @@ -2027,13 +2029,13 @@ { "type": "LEDGER_ENTRY_STATE", "state": { - "lastModifiedLedgerSeq": 13, + "lastModifiedLedgerSeq": 11, "data": { "type": "ACCOUNT", "account": { - "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", + "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", "balance": 400000000, - "seqNum": 55834574848, + "seqNum": 47244640256, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -2057,9 +2059,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", - "balance": 399885833, - "seqNum": 55834574848, + "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", + "balance": 398999900, + "seqNum": 47244640256, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -2091,9 +2093,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", - "balance": 399885833, - "seqNum": 55834574848, + "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", + "balance": 398999900, + "seqNum": 47244640256, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -2117,9 +2119,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", - "balance": 399885833, - "seqNum": 55834574849, + "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", + "balance": 398999900, + "seqNum": 47244640257, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -2168,9 +2170,9 @@ }, "changes": [ { - "type": "LEDGER_ENTRY_CREATED", - "created": { - "lastModifiedLedgerSeq": 31, + "type": "LEDGER_ENTRY_RESTORED", + "restored": { + "lastModifiedLedgerSeq": 10, "data": { "type": "CONTRACT_DATA", "contractData": { @@ -2180,7 +2182,7 @@ "contract": "CAA3QKIP2SNVXUJTB4HKOGF55JTSSMQGED3FZYNHMNSXYV3DRRMAWA3Y", "key": { "type": "SCV_SYMBOL", - "sym": "key" + "sym": "archived" }, "durability": "PERSISTENT", "val": { @@ -2195,13 +2197,13 @@ } }, { - "type": "LEDGER_ENTRY_CREATED", - "created": { + "type": "LEDGER_ENTRY_RESTORED", + "restored": { "lastModifiedLedgerSeq": 31, "data": { "type": "TTL", "ttl": { - "keyHash": "764f4e59e20ac1a357f9f26ab0eaf46d196ab74822db44f039353a6f114864aa", + "keyHash": "4791962cd1e2c7b8f8af3f96514f9777f0156a48261fb885a571a7f69b33a058", "liveUntilLedgerSeq": 50 } }, @@ -2219,9 +2221,7 @@ "ext": { "v": 0 }, - "returnValue": { - "type": "SCV_VOID" - } + "returnValue": null }, "events": [ { @@ -2242,14 +2242,14 @@ }, { "type": "SCV_ADDRESS", - "address": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD" + "address": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB" } ], "data": { "type": "SCV_I128", "i128": { "hi": 0, - "lo": 114167 + "lo": 1000100 } } } @@ -2274,14 +2274,14 @@ }, { "type": "SCV_ADDRESS", - "address": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD" + "address": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB" } ], "data": { "type": "SCV_I128", "i128": { "hi": -1, - "lo": 18446744073709531784 + "lo": 18446744073708605623 } } } @@ -2300,9 +2300,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", - "balance": 399885833, - "seqNum": 55834574849, + "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", + "balance": 398999900, + "seqNum": 47244640257, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -2350,9 +2350,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", - "balance": 399905665, - "seqNum": 55834574849, + "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", + "balance": 399945893, + "seqNum": 47244640257, "numSubEntries": 0, "inflationDest": null, "flags": 0, diff --git a/src/testdata/ledger-close-meta-enable-classic-events-v2-protocol-26.json b/src/testdata/ledger-close-meta-enable-classic-events-v2-protocol-26.json index 1945d07ffa..1e1fd24a98 100644 --- a/src/testdata/ledger-close-meta-enable-classic-events-v2-protocol-26.json +++ b/src/testdata/ledger-close-meta-enable-classic-events-v2-protocol-26.json @@ -6,24 +6,24 @@ "v": 0 }, "ledgerHeader": { - "hash": "49f60c4293eda65a3c1835406896cf6f01e6b1432a2e51183e18c1da57c15856", + "hash": "83d6e4f873f0336d83d32db3485f6ead2073ed35784ed6493860bea412c245db", "header": { "ledgerVersion": 26, - "previousLedgerHash": "a570dae70759517a0679eadb3c0725f6b1bc168bba6375eeea33b4ab40f51636", + "previousLedgerHash": "77172844c966b469aed8619a331b85d995590d352de312e9f6ccf80a56d2a427", "scpValue": { - "txSetHash": "3ef32e66de0ec1a08474e8e4b29aa6895464d634553f6b8d27386c0a78f1d045", + "txSetHash": "4e7c372203401e64ae7968d66e6ed6fbfca758dd416d8599bc121ef109d787ff", "closeTime": 1451692801, "upgrades": [], "ext": { "v": "STELLAR_VALUE_SIGNED", "lcValueSignature": { "nodeID": "GDDOUW25MRFLNXQMN3OODP6JQEXSGLMHAFZV4XPQ2D3GA4QFIDMEJG2O", - "signature": "1d555e1369037e6f5290b3fc7a185bebd7ee7ec1b5e7442fa2a5057c2505977e6b7a515e57ceb35efcf1458d74fff773621e45329c667e15c2eb18aff167e404" + "signature": "174c749e9f54febf519f85b3266d6da33001356b2d17c1975c69a797d9777d141d4075445f9fc4ded298b2dc2a426d33db857148b73e6c960e7f598a2ae5a90b" } } }, "txSetResultHash": "fd6fb3b06784653ffa2057ae36184c8d9ee185c4ae35f0ba9e076059746fb659", - "bucketListHash": "8fac2c03cca5f9f6b335f75e724b66800b8ce29f9949795793baa24b3fcb7ccb", + "bucketListHash": "29b124ff2c559a17e620a1d2bc6a183c45c59be84cce8c9a5c5348a2afb76c2a", "ledgerSeq": 10, "totalCoins": 1000000000000000000, "feePool": 8198834, @@ -49,7 +49,7 @@ "txSet": { "v": 1, "v1TxSet": { - "previousLedgerHash": "a570dae70759517a0679eadb3c0725f6b1bc168bba6375eeea33b4ab40f51636", + "previousLedgerHash": "77172844c966b469aed8619a331b85d995590d352de312e9f6ccf80a56d2a427", "phases": [ { "v": 0, diff --git a/src/testdata/ledger-close-meta-v2-protocol-26-soroban.json b/src/testdata/ledger-close-meta-v2-protocol-26-soroban.json index 4122f751f9..1aeafc0a09 100644 --- a/src/testdata/ledger-close-meta-v2-protocol-26-soroban.json +++ b/src/testdata/ledger-close-meta-v2-protocol-26-soroban.json @@ -6,24 +6,24 @@ "v": 0 }, "ledgerHeader": { - "hash": "f8e249ac924d72fe4b2a5bd6b28717b1d164fe0b5c7d32d64d7c27b63d7f70d7", + "hash": "059f74d286f715f5cb199115afe231ae62c98a665cbe6223eca865f6e03e4711", "header": { "ledgerVersion": 26, - "previousLedgerHash": "c5be64a7ce74a1a5d2950fb12c1542cb6cc2d69dfa0af500d84751a1812a36e7", + "previousLedgerHash": "7584ec52c748b8c4ad78730fe47b3e2528e47fd95dad346ca3c1eb61248a55ab", "scpValue": { - "txSetHash": "8fc7e93ed2170bf10afa649c59868ab3a4a9c6511b9e21d9a69a3c973d7ba0b5", + "txSetHash": "7eeacd54ebfb80905aab2931876c09b5da9324d7d2d704245c0a90189ef52cfd", "closeTime": 1451692801, "upgrades": [], "ext": { "v": "STELLAR_VALUE_SIGNED", "lcValueSignature": { "nodeID": "GDDOUW25MRFLNXQMN3OODP6JQEXSGLMHAFZV4XPQ2D3GA4QFIDMEJG2O", - "signature": "c0fe7e296a63c47976c65d0443ffe293df794f83243b02954011e2073b20213f5bcff79a1ee67d9a7ae8d18db2dbc7a9059831547fd7c8f1c3a53910f8304f07" + "signature": "43c151074ef7c010bc6ed7b78e93251370144d39c694f4f2b7d8cb35df87729b97cd3957b42bc541cb2f4c0401974bb563f8e1fc189b5057327f5e6e9773cb04" } } }, - "txSetResultHash": "d3bde14b15428d27136e8ce2b0f86cf9c84b9cacfd3258317e382187b5e2f08e", - "bucketListHash": "3e9b4cc079d0fdf987005647ac5b891aa0c313eb95f2d5f3c5ef4c711477fae9", + "txSetResultHash": "7f990d484919d1099f8d79eaf2780b3d19363dc01f7a15bb4f8b0881c5db9bcf", + "bucketListHash": "bafbb8761cb8d2dca18ad3eada232def91a210823c3b10d7ca29773bd3b64297", "ledgerSeq": 31, "totalCoins": 1000000000000000000, "feePool": 9167489, @@ -49,7 +49,7 @@ "txSet": { "v": 1, "v1TxSet": { - "previousLedgerHash": "c5be64a7ce74a1a5d2950fb12c1542cb6cc2d69dfa0af500d84751a1812a36e7", + "previousLedgerHash": "7584ec52c748b8c4ad78730fe47b3e2528e47fd95dad346ca3c1eb61248a55ab", "phases": [ { "v": 0, @@ -505,18 +505,19 @@ "v": 0 }, "result": { - "transactionHash": "89c02a589a36decde3f1e6f9aa8b629fc012daa68eefc76b3763bcc1fdb2609d", + "transactionHash": "d9d747111c25cd7e829c819f13fbfe771849c9a994dcc2dc554ac20841322f01", "result": { - "feeCharged": 54107, + "feeCharged": 94335, "result": { "code": "txSUCCESS", "results": [ { "code": "opINNER", "tr": { - "type": "RESTORE_FOOTPRINT", - "restoreFootprintResult": { - "code": "RESTORE_FOOTPRINT_SUCCESS" + "type": "INVOKE_HOST_FUNCTION", + "invokeHostFunctionResult": { + "code": "INVOKE_HOST_FUNCTION_SUCCESS", + "success": "cbbc48750debb8535093b3deaf88ac7f4cff87425576a58de2bac754acdb4616" } } } @@ -531,13 +532,13 @@ { "type": "LEDGER_ENTRY_STATE", "state": { - "lastModifiedLedgerSeq": 11, + "lastModifiedLedgerSeq": 13, "data": { "type": "ACCOUNT", "account": { - "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", + "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", "balance": 400000000, - "seqNum": 47244640256, + "seqNum": 55834574848, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -561,9 +562,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", - "balance": 398999900, - "seqNum": 47244640256, + "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", + "balance": 399885833, + "seqNum": 55834574848, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -595,9 +596,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", - "balance": 398999900, - "seqNum": 47244640256, + "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", + "balance": 399885833, + "seqNum": 55834574848, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -621,9 +622,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", - "balance": 398999900, - "seqNum": 47244640257, + "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", + "balance": 399885833, + "seqNum": 55834574849, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -672,9 +673,9 @@ }, "changes": [ { - "type": "LEDGER_ENTRY_RESTORED", - "restored": { - "lastModifiedLedgerSeq": 10, + "type": "LEDGER_ENTRY_CREATED", + "created": { + "lastModifiedLedgerSeq": 31, "data": { "type": "CONTRACT_DATA", "contractData": { @@ -684,7 +685,7 @@ "contract": "CAA3QKIP2SNVXUJTB4HKOGF55JTSSMQGED3FZYNHMNSXYV3DRRMAWA3Y", "key": { "type": "SCV_SYMBOL", - "sym": "archived" + "sym": "key" }, "durability": "PERSISTENT", "val": { @@ -699,13 +700,13 @@ } }, { - "type": "LEDGER_ENTRY_RESTORED", - "restored": { + "type": "LEDGER_ENTRY_CREATED", + "created": { "lastModifiedLedgerSeq": 31, "data": { "type": "TTL", "ttl": { - "keyHash": "4791962cd1e2c7b8f8af3f96514f9777f0156a48261fb885a571a7f69b33a058", + "keyHash": "764f4e59e20ac1a357f9f26ab0eaf46d196ab74822db44f039353a6f114864aa", "liveUntilLedgerSeq": 50 } }, @@ -723,7 +724,9 @@ "ext": { "v": 0 }, - "returnValue": null + "returnValue": { + "type": "SCV_VOID" + } }, "events": [], "diagnosticEvents": [] @@ -737,9 +740,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", - "balance": 398999900, - "seqNum": 47244640257, + "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", + "balance": 399885833, + "seqNum": 55834574849, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -787,9 +790,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", - "balance": 399945893, - "seqNum": 47244640257, + "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", + "balance": 399905665, + "seqNum": 55834574849, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -837,18 +840,18 @@ "v": 0 }, "result": { - "transactionHash": "47fd1151d61624bbce0b9a9f7883c60ccd70bcdf6f6c640a53e13e3fa856609f", + "transactionHash": "7c5d889bd9b1b3a9061b13f9a23443a54539751ee31a0ae661d996b45c44675e", "result": { - "feeCharged": 51269, + "feeCharged": 54053, "result": { - "code": "txSUCCESS", + "code": "txFAILED", "results": [ { "code": "opINNER", "tr": { - "type": "EXTEND_FOOTPRINT_TTL", - "extendFootprintTTLResult": { - "code": "EXTEND_FOOTPRINT_TTL_SUCCESS" + "type": "INVOKE_HOST_FUNCTION", + "invokeHostFunctionResult": { + "code": "INVOKE_HOST_FUNCTION_TRAPPED" } } } @@ -863,13 +866,13 @@ { "type": "LEDGER_ENTRY_STATE", "state": { - "lastModifiedLedgerSeq": 12, + "lastModifiedLedgerSeq": 14, "data": { "type": "ACCOUNT", "account": { - "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", + "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", "balance": 400000000, - "seqNum": 51539607552, + "seqNum": 60129542144, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -893,9 +896,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", - "balance": 398999900, - "seqNum": 51539607552, + "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", + "balance": 399905947, + "seqNum": 60129542144, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -927,9 +930,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", - "balance": 398999900, - "seqNum": 51539607552, + "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", + "balance": 399905947, + "seqNum": 60129542144, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -953,9 +956,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", - "balance": 398999900, - "seqNum": 51539607553, + "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", + "balance": 399905947, + "seqNum": 60129542145, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -997,87 +1000,9 @@ } } ], - "operations": [ - { - "ext": { - "v": 0 - }, - "changes": [ - { - "type": "LEDGER_ENTRY_STATE", - "state": { - "lastModifiedLedgerSeq": 9, - "data": { - "type": "TTL", - "ttl": { - "keyHash": "091ddece931776a53f93869b82c24e132cc12d00d961fac09bc3b9cb9021c62d", - "liveUntilLedgerSeq": 10009 - } - }, - "ext": { - "v": 0 - } - } - }, - { - "type": "LEDGER_ENTRY_UPDATED", - "updated": { - "lastModifiedLedgerSeq": 31, - "data": { - "type": "TTL", - "ttl": { - "keyHash": "091ddece931776a53f93869b82c24e132cc12d00d961fac09bc3b9cb9021c62d", - "liveUntilLedgerSeq": 10031 - } - }, - "ext": { - "v": 0 - } - } - }, - { - "type": "LEDGER_ENTRY_STATE", - "state": { - "lastModifiedLedgerSeq": 9, - "data": { - "type": "TTL", - "ttl": { - "keyHash": "60313f9b273db0b14c3e503cf6cc152dd14a0c57e5e81a23e86b4e27a23a2c06", - "liveUntilLedgerSeq": 10009 - } - }, - "ext": { - "v": 0 - } - } - }, - { - "type": "LEDGER_ENTRY_UPDATED", - "updated": { - "lastModifiedLedgerSeq": 31, - "data": { - "type": "TTL", - "ttl": { - "keyHash": "60313f9b273db0b14c3e503cf6cc152dd14a0c57e5e81a23e86b4e27a23a2c06", - "liveUntilLedgerSeq": 10031 - } - }, - "ext": { - "v": 0 - } - } - } - ], - "events": [] - } - ], + "operations": [], "txChangesAfter": [], - "sorobanMeta": { - "ext": { - "v": 0 - }, - "returnValue": null - }, + "sorobanMeta": null, "events": [], "diagnosticEvents": [] } @@ -1090,9 +1015,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", - "balance": 398999900, - "seqNum": 51539607553, + "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", + "balance": 399905947, + "seqNum": 60129542145, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1140,9 +1065,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", - "balance": 399948731, - "seqNum": 51539607553, + "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", + "balance": 399945947, + "seqNum": 60129542145, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1190,9 +1115,9 @@ "v": 0 }, "result": { - "transactionHash": "7c5d889bd9b1b3a9061b13f9a23443a54539751ee31a0ae661d996b45c44675e", + "transactionHash": "9f16a9c2ea090a97d6557e89266cad9a19a1a9db6c1d3698e4b189af34e5e585", "result": { - "feeCharged": 54053, + "feeCharged": 45161, "result": { "code": "txFAILED", "results": [ @@ -1201,7 +1126,7 @@ "tr": { "type": "INVOKE_HOST_FUNCTION", "invokeHostFunctionResult": { - "code": "INVOKE_HOST_FUNCTION_TRAPPED" + "code": "INVOKE_HOST_FUNCTION_RESOURCE_LIMIT_EXCEEDED" } } } @@ -1216,13 +1141,13 @@ { "type": "LEDGER_ENTRY_STATE", "state": { - "lastModifiedLedgerSeq": 14, + "lastModifiedLedgerSeq": 15, "data": { "type": "ACCOUNT", "account": { - "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", + "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", "balance": 400000000, - "seqNum": 60129542144, + "seqNum": 64424509440, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1246,9 +1171,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", - "balance": 399905947, - "seqNum": 60129542144, + "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", + "balance": 398953838, + "seqNum": 64424509440, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1280,9 +1205,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", - "balance": 399905947, - "seqNum": 60129542144, + "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", + "balance": 398953838, + "seqNum": 64424509440, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1306,9 +1231,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", - "balance": 399905947, - "seqNum": 60129542145, + "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", + "balance": 398953838, + "seqNum": 64424509441, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1365,9 +1290,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", - "balance": 399905947, - "seqNum": 60129542145, + "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", + "balance": 398953838, + "seqNum": 64424509441, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1415,9 +1340,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GAM4XNEHJUHN7VE3ZWJI23R2WS3SJS2BTUHZAC6XICXLXO2HPX4QI2IR", - "balance": 399945947, - "seqNum": 60129542145, + "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", + "balance": 399954839, + "seqNum": 64424509441, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1465,18 +1390,18 @@ "v": 0 }, "result": { - "transactionHash": "9f16a9c2ea090a97d6557e89266cad9a19a1a9db6c1d3698e4b189af34e5e585", + "transactionHash": "47fd1151d61624bbce0b9a9f7883c60ccd70bcdf6f6c640a53e13e3fa856609f", "result": { - "feeCharged": 45161, + "feeCharged": 51269, "result": { - "code": "txFAILED", + "code": "txSUCCESS", "results": [ { "code": "opINNER", "tr": { - "type": "INVOKE_HOST_FUNCTION", - "invokeHostFunctionResult": { - "code": "INVOKE_HOST_FUNCTION_RESOURCE_LIMIT_EXCEEDED" + "type": "EXTEND_FOOTPRINT_TTL", + "extendFootprintTTLResult": { + "code": "EXTEND_FOOTPRINT_TTL_SUCCESS" } } } @@ -1491,13 +1416,13 @@ { "type": "LEDGER_ENTRY_STATE", "state": { - "lastModifiedLedgerSeq": 15, + "lastModifiedLedgerSeq": 12, "data": { "type": "ACCOUNT", "account": { - "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", + "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", "balance": 400000000, - "seqNum": 64424509440, + "seqNum": 51539607552, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1521,9 +1446,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", - "balance": 398953838, - "seqNum": 64424509440, + "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", + "balance": 398999900, + "seqNum": 51539607552, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1555,9 +1480,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", - "balance": 398953838, - "seqNum": 64424509440, + "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", + "balance": 398999900, + "seqNum": 51539607552, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1581,9 +1506,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", - "balance": 398953838, - "seqNum": 64424509441, + "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", + "balance": 398999900, + "seqNum": 51539607553, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1625,9 +1550,87 @@ } } ], - "operations": [], + "operations": [ + { + "ext": { + "v": 0 + }, + "changes": [ + { + "type": "LEDGER_ENTRY_STATE", + "state": { + "lastModifiedLedgerSeq": 9, + "data": { + "type": "TTL", + "ttl": { + "keyHash": "091ddece931776a53f93869b82c24e132cc12d00d961fac09bc3b9cb9021c62d", + "liveUntilLedgerSeq": 10009 + } + }, + "ext": { + "v": 0 + } + } + }, + { + "type": "LEDGER_ENTRY_UPDATED", + "updated": { + "lastModifiedLedgerSeq": 31, + "data": { + "type": "TTL", + "ttl": { + "keyHash": "091ddece931776a53f93869b82c24e132cc12d00d961fac09bc3b9cb9021c62d", + "liveUntilLedgerSeq": 10031 + } + }, + "ext": { + "v": 0 + } + } + }, + { + "type": "LEDGER_ENTRY_STATE", + "state": { + "lastModifiedLedgerSeq": 9, + "data": { + "type": "TTL", + "ttl": { + "keyHash": "60313f9b273db0b14c3e503cf6cc152dd14a0c57e5e81a23e86b4e27a23a2c06", + "liveUntilLedgerSeq": 10009 + } + }, + "ext": { + "v": 0 + } + } + }, + { + "type": "LEDGER_ENTRY_UPDATED", + "updated": { + "lastModifiedLedgerSeq": 31, + "data": { + "type": "TTL", + "ttl": { + "keyHash": "60313f9b273db0b14c3e503cf6cc152dd14a0c57e5e81a23e86b4e27a23a2c06", + "liveUntilLedgerSeq": 10031 + } + }, + "ext": { + "v": 0 + } + } + } + ], + "events": [] + } + ], "txChangesAfter": [], - "sorobanMeta": null, + "sorobanMeta": { + "ext": { + "v": 0 + }, + "returnValue": null + }, "events": [], "diagnosticEvents": [] } @@ -1640,9 +1643,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", - "balance": 398953838, - "seqNum": 64424509441, + "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", + "balance": 398999900, + "seqNum": 51539607553, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1690,9 +1693,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GA2NXNEE2MHWGQP5XXACPYG2BDZFPKGYPFNST5V3ZZN75NSLQAEXX7CU", - "balance": 399954839, - "seqNum": 64424509441, + "accountID": "GCAEBM3GKNR6SV6N73FSGBXU6NSMZ2URQVMJQHXFQFY2PJPX6YBCSAKZ", + "balance": 399948731, + "seqNum": 51539607553, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1740,19 +1743,18 @@ "v": 0 }, "result": { - "transactionHash": "d9d747111c25cd7e829c819f13fbfe771849c9a994dcc2dc554ac20841322f01", + "transactionHash": "89c02a589a36decde3f1e6f9aa8b629fc012daa68eefc76b3763bcc1fdb2609d", "result": { - "feeCharged": 94335, + "feeCharged": 54107, "result": { "code": "txSUCCESS", "results": [ { "code": "opINNER", "tr": { - "type": "INVOKE_HOST_FUNCTION", - "invokeHostFunctionResult": { - "code": "INVOKE_HOST_FUNCTION_SUCCESS", - "success": "cbbc48750debb8535093b3deaf88ac7f4cff87425576a58de2bac754acdb4616" + "type": "RESTORE_FOOTPRINT", + "restoreFootprintResult": { + "code": "RESTORE_FOOTPRINT_SUCCESS" } } } @@ -1767,13 +1769,13 @@ { "type": "LEDGER_ENTRY_STATE", "state": { - "lastModifiedLedgerSeq": 13, + "lastModifiedLedgerSeq": 11, "data": { "type": "ACCOUNT", "account": { - "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", + "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", "balance": 400000000, - "seqNum": 55834574848, + "seqNum": 47244640256, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1797,9 +1799,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", - "balance": 399885833, - "seqNum": 55834574848, + "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", + "balance": 398999900, + "seqNum": 47244640256, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1831,9 +1833,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", - "balance": 399885833, - "seqNum": 55834574848, + "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", + "balance": 398999900, + "seqNum": 47244640256, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1857,9 +1859,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", - "balance": 399885833, - "seqNum": 55834574849, + "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", + "balance": 398999900, + "seqNum": 47244640257, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -1908,9 +1910,9 @@ }, "changes": [ { - "type": "LEDGER_ENTRY_CREATED", - "created": { - "lastModifiedLedgerSeq": 31, + "type": "LEDGER_ENTRY_RESTORED", + "restored": { + "lastModifiedLedgerSeq": 10, "data": { "type": "CONTRACT_DATA", "contractData": { @@ -1920,7 +1922,7 @@ "contract": "CAA3QKIP2SNVXUJTB4HKOGF55JTSSMQGED3FZYNHMNSXYV3DRRMAWA3Y", "key": { "type": "SCV_SYMBOL", - "sym": "key" + "sym": "archived" }, "durability": "PERSISTENT", "val": { @@ -1935,13 +1937,13 @@ } }, { - "type": "LEDGER_ENTRY_CREATED", - "created": { + "type": "LEDGER_ENTRY_RESTORED", + "restored": { "lastModifiedLedgerSeq": 31, "data": { "type": "TTL", "ttl": { - "keyHash": "764f4e59e20ac1a357f9f26ab0eaf46d196ab74822db44f039353a6f114864aa", + "keyHash": "4791962cd1e2c7b8f8af3f96514f9777f0156a48261fb885a571a7f69b33a058", "liveUntilLedgerSeq": 50 } }, @@ -1959,9 +1961,7 @@ "ext": { "v": 0 }, - "returnValue": { - "type": "SCV_VOID" - } + "returnValue": null }, "events": [], "diagnosticEvents": [] @@ -1975,9 +1975,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", - "balance": 399885833, - "seqNum": 55834574849, + "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", + "balance": 398999900, + "seqNum": 47244640257, "numSubEntries": 0, "inflationDest": null, "flags": 0, @@ -2025,9 +2025,9 @@ "data": { "type": "ACCOUNT", "account": { - "accountID": "GDWWCIR2FIWTY2D3CEDXZYRTRNTFZCC5PBCGC6XPMKCLUV7BRG2AT3RD", - "balance": 399905665, - "seqNum": 55834574849, + "accountID": "GB6MXQ5262ZJGDQNA6BL4TWE5SADVZXIKLPELFXKUE27X4SQTGQS44ZB", + "balance": 399945893, + "seqNum": 47244640257, "numSubEntries": 0, "inflationDest": null, "flags": 0, diff --git a/src/testdata/ledger-close-meta-v2-protocol-26.json b/src/testdata/ledger-close-meta-v2-protocol-26.json index 3d25288e3f..4258ad42c7 100644 --- a/src/testdata/ledger-close-meta-v2-protocol-26.json +++ b/src/testdata/ledger-close-meta-v2-protocol-26.json @@ -6,24 +6,24 @@ "v": 0 }, "ledgerHeader": { - "hash": "49f60c4293eda65a3c1835406896cf6f01e6b1432a2e51183e18c1da57c15856", + "hash": "83d6e4f873f0336d83d32db3485f6ead2073ed35784ed6493860bea412c245db", "header": { "ledgerVersion": 26, - "previousLedgerHash": "a570dae70759517a0679eadb3c0725f6b1bc168bba6375eeea33b4ab40f51636", + "previousLedgerHash": "77172844c966b469aed8619a331b85d995590d352de312e9f6ccf80a56d2a427", "scpValue": { - "txSetHash": "3ef32e66de0ec1a08474e8e4b29aa6895464d634553f6b8d27386c0a78f1d045", + "txSetHash": "4e7c372203401e64ae7968d66e6ed6fbfca758dd416d8599bc121ef109d787ff", "closeTime": 1451692801, "upgrades": [], "ext": { "v": "STELLAR_VALUE_SIGNED", "lcValueSignature": { "nodeID": "GDDOUW25MRFLNXQMN3OODP6JQEXSGLMHAFZV4XPQ2D3GA4QFIDMEJG2O", - "signature": "1d555e1369037e6f5290b3fc7a185bebd7ee7ec1b5e7442fa2a5057c2505977e6b7a515e57ceb35efcf1458d74fff773621e45329c667e15c2eb18aff167e404" + "signature": "174c749e9f54febf519f85b3266d6da33001356b2d17c1975c69a797d9777d141d4075445f9fc4ded298b2dc2a426d33db857148b73e6c960e7f598a2ae5a90b" } } }, "txSetResultHash": "fd6fb3b06784653ffa2057ae36184c8d9ee185c4ae35f0ba9e076059746fb659", - "bucketListHash": "8fac2c03cca5f9f6b335f75e724b66800b8ce29f9949795793baa24b3fcb7ccb", + "bucketListHash": "29b124ff2c559a17e620a1d2bc6a183c45c59be84cce8c9a5c5348a2afb76c2a", "ledgerSeq": 10, "totalCoins": 1000000000000000000, "feePool": 8198834, @@ -49,7 +49,7 @@ "txSet": { "v": 1, "v1TxSet": { - "previousLedgerHash": "a570dae70759517a0679eadb3c0725f6b1bc168bba6375eeea33b4ab40f51636", + "previousLedgerHash": "77172844c966b469aed8619a331b85d995590d352de312e9f6ccf80a56d2a427", "phases": [ { "v": 0, diff --git a/src/transactions/AllowTrustOpFrame.cpp b/src/transactions/AllowTrustOpFrame.cpp index 22d2c3c932..0c4d0aed3f 100644 --- a/src/transactions/AllowTrustOpFrame.cpp +++ b/src/transactions/AllowTrustOpFrame.cpp @@ -189,4 +189,11 @@ AllowTrustOpFrame::doCheckValid(uint32_t ledgerVersion, return true; } + +bool +AllowTrustOpFrame::doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const +{ + return sorobanConfig.isKeyFrozen(trustlineKey(mAllowTrust.trustor, mAsset)); +} } diff --git a/src/transactions/AllowTrustOpFrame.h b/src/transactions/AllowTrustOpFrame.h index 74f5fa8f80..a9bca0f38c 100644 --- a/src/transactions/AllowTrustOpFrame.h +++ b/src/transactions/AllowTrustOpFrame.h @@ -49,6 +49,9 @@ class AllowTrustOpFrame : public TrustFlagsOpFrameBase bool doCheckValid(uint32_t ledgerVersion, OperationResult& res) const override; + bool doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const override; + static AllowTrustResultCode getInnerCode(OperationResult const& res) { diff --git a/src/transactions/BeginSponsoringFutureReservesOpFrame.cpp b/src/transactions/BeginSponsoringFutureReservesOpFrame.cpp index 328b66a03e..7d47be534b 100644 --- a/src/transactions/BeginSponsoringFutureReservesOpFrame.cpp +++ b/src/transactions/BeginSponsoringFutureReservesOpFrame.cpp @@ -113,4 +113,11 @@ BeginSponsoringFutureReservesOpFrame::doCheckValid(uint32_t ledgerVersion, } return true; } + +bool +BeginSponsoringFutureReservesOpFrame::doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const +{ + return false; } +} \ No newline at end of file diff --git a/src/transactions/BeginSponsoringFutureReservesOpFrame.h b/src/transactions/BeginSponsoringFutureReservesOpFrame.h index 5979bd9c7e..82a72da304 100644 --- a/src/transactions/BeginSponsoringFutureReservesOpFrame.h +++ b/src/transactions/BeginSponsoringFutureReservesOpFrame.h @@ -33,6 +33,9 @@ class BeginSponsoringFutureReservesOpFrame : public OperationFrame bool doCheckValid(uint32_t ledgerVersion, OperationResult& res) const override; + bool doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const override; + static BeginSponsoringFutureReservesResultCode getInnerCode(OperationResult const& res) { diff --git a/src/transactions/BumpSequenceOpFrame.cpp b/src/transactions/BumpSequenceOpFrame.cpp index 140202afce..335f79b60a 100644 --- a/src/transactions/BumpSequenceOpFrame.cpp +++ b/src/transactions/BumpSequenceOpFrame.cpp @@ -79,4 +79,11 @@ BumpSequenceOpFrame::doCheckValid(uint32_t ledgerVersion, } return true; } + +bool +BumpSequenceOpFrame::doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const +{ + return false; } +} \ No newline at end of file diff --git a/src/transactions/BumpSequenceOpFrame.h b/src/transactions/BumpSequenceOpFrame.h index beddc077fa..c0f512a05f 100644 --- a/src/transactions/BumpSequenceOpFrame.h +++ b/src/transactions/BumpSequenceOpFrame.h @@ -32,6 +32,9 @@ class BumpSequenceOpFrame : public OperationFrame bool doCheckValid(uint32_t ledgerVersion, OperationResult& res) const override; + bool doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const override; + static BumpSequenceResultCode getInnerCode(OperationResult const& res) { diff --git a/src/transactions/ChangeTrustOpFrame.cpp b/src/transactions/ChangeTrustOpFrame.cpp index b9482a33ba..b2c5736d21 100644 --- a/src/transactions/ChangeTrustOpFrame.cpp +++ b/src/transactions/ChangeTrustOpFrame.cpp @@ -3,6 +3,7 @@ // of this distribution or at http://www.apache.org/licenses/LICENSE-2.0 #include "ChangeTrustOpFrame.h" +#include "crypto/SHA.h" #include "database/Database.h" #include "ledger/LedgerManager.h" #include "ledger/LedgerTxn.h" @@ -339,4 +340,30 @@ ChangeTrustOpFrame::doCheckValid(uint32_t ledgerVersion, } return true; } + +bool +ChangeTrustOpFrame::doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const +{ + auto const& ct = mChangeTrust; + if (ct.line.type() == ASSET_TYPE_CREDIT_ALPHANUM4 || + ct.line.type() == ASSET_TYPE_CREDIT_ALPHANUM12) + { + Asset asset; + asset.type(ct.line.type()); + if (ct.line.type() == ASSET_TYPE_CREDIT_ALPHANUM4) + { + asset.alphaNum4() = ct.line.alphaNum4(); + } + else + { + asset.alphaNum12() = ct.line.alphaNum12(); + } + if (sorobanConfig.isKeyFrozen(trustlineKey(getSourceID(), asset))) + { + return true; + } + } + return false; +} } diff --git a/src/transactions/ChangeTrustOpFrame.h b/src/transactions/ChangeTrustOpFrame.h index 654f04b6c7..8ca610cb50 100644 --- a/src/transactions/ChangeTrustOpFrame.h +++ b/src/transactions/ChangeTrustOpFrame.h @@ -36,6 +36,9 @@ class ChangeTrustOpFrame : public OperationFrame bool doCheckValid(uint32_t ledgerVersion, OperationResult& res) const override; + bool doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const override; + static ChangeTrustResultCode getInnerCode(OperationResult const& res) { diff --git a/src/transactions/ClaimClaimableBalanceOpFrame.cpp b/src/transactions/ClaimClaimableBalanceOpFrame.cpp index fec0d4c458..ac41a1d0e1 100644 --- a/src/transactions/ClaimClaimableBalanceOpFrame.cpp +++ b/src/transactions/ClaimClaimableBalanceOpFrame.cpp @@ -7,15 +7,17 @@ #include "ledger/LedgerTxn.h" #include "ledger/LedgerTxnEntry.h" #include "ledger/LedgerTxnHeader.h" +#include "ledger/NetworkConfig.h" #include "ledger/TrustLineWrapper.h" #include "transactions/SponsorshipUtils.h" #include "transactions/TransactionUtils.h" #include "util/ProtocolVersion.h" #include +#include +#include namespace stellar { - ClaimClaimableBalanceOpFrame::ClaimClaimableBalanceOpFrame( Operation const& op, TransactionFrame const& parentTx) : OperationFrame(op, parentTx) @@ -23,6 +25,34 @@ ClaimClaimableBalanceOpFrame::ClaimClaimableBalanceOpFrame( { } +bool +ClaimClaimableBalanceOpFrame::accessesFrozenKeyAtApplyTime( + std::optional const& sorobanConfig, + Asset const& asset, OperationResult& res) const +{ +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION + if (!sorobanConfig || !sorobanConfig->hasFrozenKeys()) + { + return false; + } + LedgerKey keyToCheck; + if (asset.type() == ASSET_TYPE_NATIVE) + { + keyToCheck = accountKey(getSourceID()); + } + else + { + keyToCheck = trustlineKey(getSourceID(), asset); + } + if (sorobanConfig->isKeyFrozen(keyToCheck)) + { + innerResult(res).code(CLAIM_CLAIMABLE_BALANCE_TRUSTLINE_FROZEN); + return true; + } +#endif + return false; +} + ThresholdLevel ClaimClaimableBalanceOpFrame::getThresholdLevel() const { @@ -73,12 +103,22 @@ bool ClaimClaimableBalanceOpFrame::doApply(AppConnector& app, AbstractLedgerTxn& ltx, OperationResult& res, OperationMetaBuilder& opMeta) const +{ + throw std::runtime_error("ClaimClaimableBalanceOp may only be applied with " + "doApply overload accepting sorobanConfig"); +} + +bool +ClaimClaimableBalanceOpFrame::doApply( + AppConnector& app, AbstractLedgerTxn& ltx, + std::optional const& sorobanConfig, + OperationResult& res, OperationMetaBuilder& opMeta) const { ZoneNamedN(applyZone, "ClaimClaimableBalanceOpFrame apply", true); auto claimableBalanceLtxEntry = stellar::loadClaimableBalance(ltx, mClaimClaimableBalance.balanceID); - if (!claimableBalanceLtxEntry) + if (!static_cast(claimableBalanceLtxEntry)) { innerResult(res).code(CLAIM_CLAIMABLE_BALANCE_DOES_NOT_EXIST); return false; @@ -103,7 +143,14 @@ ClaimClaimableBalanceOpFrame::doApply(AppConnector& app, AbstractLedgerTxn& ltx, } auto const& asset = claimableBalance.asset; + // CAP-77: Check if the relevant key is frozen before claiming. + if (accessesFrozenKeyAtApplyTime(sorobanConfig, asset, res)) + { + return false; + } + auto amount = claimableBalance.amount; + if (asset.type() == ASSET_TYPE_NATIVE) { auto sourceAccount = loadSourceAccount(ltx, header); @@ -161,4 +208,13 @@ ClaimClaimableBalanceOpFrame::insertLedgerKeysToPrefetch( { keys.emplace(claimableBalanceKey(mClaimClaimableBalance.balanceID)); } + +bool +ClaimClaimableBalanceOpFrame::doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const +{ + // Checks for the frozen keys are done at apply time in + // accessesFrozenKeyAtApplyTime, we can't check anything at validation time. + return false; +} } diff --git a/src/transactions/ClaimClaimableBalanceOpFrame.h b/src/transactions/ClaimClaimableBalanceOpFrame.h index c5b5e29074..ed932851ef 100644 --- a/src/transactions/ClaimClaimableBalanceOpFrame.h +++ b/src/transactions/ClaimClaimableBalanceOpFrame.h @@ -22,6 +22,10 @@ class ClaimClaimableBalanceOpFrame : public OperationFrame ClaimClaimableBalanceOp const& mClaimClaimableBalance; + bool accessesFrozenKeyAtApplyTime( + std::optional const& sorobanConfig, + Asset const& asset, OperationResult& res) const; + public: ClaimClaimableBalanceOpFrame(Operation const& op, TransactionFrame const& parentTx); @@ -31,11 +35,18 @@ class ClaimClaimableBalanceOpFrame : public OperationFrame bool doApply(AppConnector& app, AbstractLedgerTxn& ltx, OperationResult& res, OperationMetaBuilder& opMeta) const override; + bool doApply(AppConnector& app, AbstractLedgerTxn& ltx, + std::optional const& sorobanConfig, + OperationResult& res, + OperationMetaBuilder& opMeta) const override; bool doCheckValid(uint32_t ledgerVersion, OperationResult& res) const override; void insertLedgerKeysToPrefetch(UnorderedSet& keys) const override; + bool doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const override; + static ClaimClaimableBalanceResultCode getInnerCode(OperationResult const& res) { diff --git a/src/transactions/ClawbackClaimableBalanceOpFrame.cpp b/src/transactions/ClawbackClaimableBalanceOpFrame.cpp index e2c639c104..8dd33f7308 100644 --- a/src/transactions/ClawbackClaimableBalanceOpFrame.cpp +++ b/src/transactions/ClawbackClaimableBalanceOpFrame.cpp @@ -96,4 +96,11 @@ ClawbackClaimableBalanceOpFrame::insertLedgerKeysToPrefetch( { keys.emplace(claimableBalanceKey(mClawbackClaimableBalance.balanceID)); } + +bool +ClawbackClaimableBalanceOpFrame::doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const +{ + return false; +} } diff --git a/src/transactions/ClawbackClaimableBalanceOpFrame.h b/src/transactions/ClawbackClaimableBalanceOpFrame.h index df489dd29f..41c197bc65 100644 --- a/src/transactions/ClawbackClaimableBalanceOpFrame.h +++ b/src/transactions/ClawbackClaimableBalanceOpFrame.h @@ -35,6 +35,9 @@ class ClawbackClaimableBalanceOpFrame : public OperationFrame void insertLedgerKeysToPrefetch(UnorderedSet& keys) const override; + bool doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const override; + static ClawbackClaimableBalanceResultCode getInnerCode(OperationResult const& res) { diff --git a/src/transactions/ClawbackOpFrame.cpp b/src/transactions/ClawbackOpFrame.cpp index 1c4984ebe6..d8784f979d 100644 --- a/src/transactions/ClawbackOpFrame.cpp +++ b/src/transactions/ClawbackOpFrame.cpp @@ -106,4 +106,12 @@ ClawbackOpFrame::insertLedgerKeysToPrefetch(UnorderedSet& keys) const trustlineKey(toAccountID(mClawback.from), mClawback.asset)); } } + +bool +ClawbackOpFrame::doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const +{ + return sorobanConfig.isKeyFrozen( + trustlineKey(toAccountID(mClawback.from), mClawback.asset)); +} } diff --git a/src/transactions/ClawbackOpFrame.h b/src/transactions/ClawbackOpFrame.h index 84682cdc12..39fc779a80 100644 --- a/src/transactions/ClawbackOpFrame.h +++ b/src/transactions/ClawbackOpFrame.h @@ -34,6 +34,9 @@ class ClawbackOpFrame : public OperationFrame void insertLedgerKeysToPrefetch(UnorderedSet& keys) const override; + bool doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const override; + static ClawbackResultCode getInnerCode(OperationResult const& res) { diff --git a/src/transactions/CreateAccountOpFrame.cpp b/src/transactions/CreateAccountOpFrame.cpp index b8f38af991..31882c8ce9 100644 --- a/src/transactions/CreateAccountOpFrame.cpp +++ b/src/transactions/CreateAccountOpFrame.cpp @@ -198,4 +198,11 @@ CreateAccountOpFrame::insertLedgerKeysToPrefetch( { keys.emplace(accountKey(mCreateAccount.destination)); } + +bool +CreateAccountOpFrame::doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const +{ + return sorobanConfig.isKeyFrozen(accountKey(mCreateAccount.destination)); +} } diff --git a/src/transactions/CreateAccountOpFrame.h b/src/transactions/CreateAccountOpFrame.h index e4514be8ac..2fc0a6d9d5 100644 --- a/src/transactions/CreateAccountOpFrame.h +++ b/src/transactions/CreateAccountOpFrame.h @@ -35,6 +35,9 @@ class CreateAccountOpFrame : public OperationFrame void insertLedgerKeysToPrefetch(UnorderedSet& keys) const override; + bool doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const override; + static CreateAccountResultCode getInnerCode(OperationResult const& res) { diff --git a/src/transactions/CreateClaimableBalanceOpFrame.cpp b/src/transactions/CreateClaimableBalanceOpFrame.cpp index cf6953017a..adb7dcbb49 100644 --- a/src/transactions/CreateClaimableBalanceOpFrame.cpp +++ b/src/transactions/CreateClaimableBalanceOpFrame.cpp @@ -319,4 +319,13 @@ CreateClaimableBalanceOpFrame::getBalanceID() const return xdrSha256(hashPreimage); } + +bool +CreateClaimableBalanceOpFrame::doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const +{ + return mCreateClaimableBalance.asset.type() != ASSET_TYPE_NATIVE && + sorobanConfig.isKeyFrozen( + trustlineKey(getSourceID(), mCreateClaimableBalance.asset)); +} } diff --git a/src/transactions/CreateClaimableBalanceOpFrame.h b/src/transactions/CreateClaimableBalanceOpFrame.h index 64057d0352..659e711fc0 100644 --- a/src/transactions/CreateClaimableBalanceOpFrame.h +++ b/src/transactions/CreateClaimableBalanceOpFrame.h @@ -39,6 +39,9 @@ class CreateClaimableBalanceOpFrame : public OperationFrame void insertLedgerKeysToPrefetch(UnorderedSet& keys) const override; + bool doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const override; + static CreateClaimableBalanceResultCode getInnerCode(OperationResult const& res) { diff --git a/src/transactions/EndSponsoringFutureReservesOpFrame.cpp b/src/transactions/EndSponsoringFutureReservesOpFrame.cpp index 3664ea96b8..e0d96b45b8 100644 --- a/src/transactions/EndSponsoringFutureReservesOpFrame.cpp +++ b/src/transactions/EndSponsoringFutureReservesOpFrame.cpp @@ -73,4 +73,11 @@ EndSponsoringFutureReservesOpFrame::doCheckValid(uint32_t ledgerVersion, { return true; } + +bool +EndSponsoringFutureReservesOpFrame::doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const +{ + return false; +} } diff --git a/src/transactions/EndSponsoringFutureReservesOpFrame.h b/src/transactions/EndSponsoringFutureReservesOpFrame.h index 9d5ef61a8f..87e1ddf723 100644 --- a/src/transactions/EndSponsoringFutureReservesOpFrame.h +++ b/src/transactions/EndSponsoringFutureReservesOpFrame.h @@ -29,6 +29,9 @@ class EndSponsoringFutureReservesOpFrame : public OperationFrame bool doCheckValid(uint32_t ledgerVersion, OperationResult& res) const override; + bool doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const override; + static EndSponsoringFutureReservesResultCode getInnerCode(OperationResult const& res) { diff --git a/src/transactions/ExtendFootprintTTLOpFrame.cpp b/src/transactions/ExtendFootprintTTLOpFrame.cpp index b671bbdd6f..a1e960b0de 100644 --- a/src/transactions/ExtendFootprintTTLOpFrame.cpp +++ b/src/transactions/ExtendFootprintTTLOpFrame.cpp @@ -380,4 +380,13 @@ ExtendFootprintTTLOpFrame::getThresholdLevel() const return ThresholdLevel::LOW; } +bool +ExtendFootprintTTLOpFrame::doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const +{ + // Soroban footprint checks happen at transaction level, so we can safely + // say that the operation itself doesn't access frozen keys. + return false; +} + } diff --git a/src/transactions/ExtendFootprintTTLOpFrame.h b/src/transactions/ExtendFootprintTTLOpFrame.h index 1681b8caf6..72138d2837 100644 --- a/src/transactions/ExtendFootprintTTLOpFrame.h +++ b/src/transactions/ExtendFootprintTTLOpFrame.h @@ -62,6 +62,9 @@ class ExtendFootprintTTLOpFrame : public OperationFrame ThresholdLevel getThresholdLevel() const override; + bool doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const override; + friend class ExtendFootprintTTLApplyHelper; friend class ExtendFootprintTTLPreV23ApplyHelper; friend class ExtendFootprintTTLParallelApplyHelper; diff --git a/src/transactions/FeeBumpTransactionFrame.cpp b/src/transactions/FeeBumpTransactionFrame.cpp index cdc038333b..8d43d64c87 100644 --- a/src/transactions/FeeBumpTransactionFrame.cpp +++ b/src/transactions/FeeBumpTransactionFrame.cpp @@ -106,7 +106,7 @@ FeeBumpTransactionFrame::preParallelApply( try { mInnerTx->preParallelApply(/*chargeFee=*/false, app, ltx, meta, - txResult, sorobanConfig); + txResult, sorobanConfig, getContentsHash()); } catch (std::exception& e) { @@ -179,7 +179,7 @@ FeeBumpTransactionFrame::apply( // If this throws, then we may not have the correct TransactionResult so // we must crash. return mInnerTx->apply(false, app, ltx, meta, txResult, sorobanConfig, - sorobanBasePrngSeed); + sorobanBasePrngSeed, getContentsHash()); } catch (std::exception& e) { @@ -287,15 +287,34 @@ FeeBumpTransactionFrame::checkValid( auto txResult = FeeBumpMutableTransactionResult::createSuccess( *mInnerTx, feeCharged, 0); - SignatureChecker signatureChecker{ - ls.getLedgerHeader().current().ledgerVersion, getContentsHash(), - mEnvelope.feeBump().signatures}; + auto ledgerVersion = ls.getLedgerHeader().current().ledgerVersion; + SignatureChecker signatureChecker{ledgerVersion, getContentsHash(), + mEnvelope.feeBump().signatures}; if (commonValid(signatureChecker, ls, false, *txResult) != ValidationType::kFullyValid) { return txResult; } +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION + if (protocolVersionStartsFrom(ledgerVersion, SOROBAN_PROTOCOL_VERSION)) + { + // CAP-77: Check if fee bump source account is frozen + auto const& sorobanConfig = + app.getLedgerManager().getLastClosedSorobanNetworkConfig(); + if (sorobanConfig.hasFrozenKeys()) + { + auto feeAcctKey = accountKey(getFeeSourceID()); + if (sorobanConfig.isKeyFrozen(feeAcctKey) && + !sorobanConfig.isFreezeBypassTx(getContentsHash())) + { + txResult->setError(txFROZEN_KEY_ACCESSED); + return txResult; + } + } + } +#endif + if (!signatureChecker.checkAllSignaturesUsed()) { txResult->setError(txBAD_AUTH_EXTRA); @@ -304,7 +323,8 @@ FeeBumpTransactionFrame::checkValid( mInnerTx->checkValidWithOptionallyChargedFee( app, ls, current, false, lowerBoundCloseTimeOffset, - upperBoundCloseTimeOffset, *txResult, diagnosticEvents); + upperBoundCloseTimeOffset, getContentsHash(), *txResult, + diagnosticEvents); return txResult; } diff --git a/src/transactions/InflationOpFrame.cpp b/src/transactions/InflationOpFrame.cpp index 37639b1eec..4d623fc7d0 100644 --- a/src/transactions/InflationOpFrame.cpp +++ b/src/transactions/InflationOpFrame.cpp @@ -145,4 +145,11 @@ InflationOpFrame::getThresholdLevel() const { return ThresholdLevel::LOW; } + +bool +InflationOpFrame::doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const +{ + return false; } +} \ No newline at end of file diff --git a/src/transactions/InflationOpFrame.h b/src/transactions/InflationOpFrame.h index 610f52d67e..9cc1dbdf38 100644 --- a/src/transactions/InflationOpFrame.h +++ b/src/transactions/InflationOpFrame.h @@ -30,6 +30,9 @@ class InflationOpFrame : public OperationFrame OperationResult& res) const override; bool isOpSupported(LedgerHeader const& header) const override; + bool doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const override; + static InflationResultCode getInnerCode(OperationResult const& res) { diff --git a/src/transactions/InvokeHostFunctionOpFrame.cpp b/src/transactions/InvokeHostFunctionOpFrame.cpp index 04f1e894ca..fbede5e820 100644 --- a/src/transactions/InvokeHostFunctionOpFrame.cpp +++ b/src/transactions/InvokeHostFunctionOpFrame.cpp @@ -1330,4 +1330,13 @@ InvokeHostFunctionOpFrame::isSoroban() const { return true; } + +bool +InvokeHostFunctionOpFrame::doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const +{ + // Soroban footprint checks happen at transaction level, so we can safely + // say that the operation itself doesn't access frozen keys. + return false; +} } diff --git a/src/transactions/InvokeHostFunctionOpFrame.h b/src/transactions/InvokeHostFunctionOpFrame.h index 0e990a3d59..c1538f4661 100644 --- a/src/transactions/InvokeHostFunctionOpFrame.h +++ b/src/transactions/InvokeHostFunctionOpFrame.h @@ -75,6 +75,9 @@ class InvokeHostFunctionOpFrame : public OperationFrame virtual bool isSoroban() const override; + bool doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const override; + friend class InvokeHostFunctionApplyHelper; friend class InvokeHostFunctionPreV23ApplyHelper; friend class InvokeHostFunctionParallelApplyHelper; diff --git a/src/transactions/LiquidityPoolDepositOpFrame.cpp b/src/transactions/LiquidityPoolDepositOpFrame.cpp index d0cf8d59d9..4ddc90d6ac 100644 --- a/src/transactions/LiquidityPoolDepositOpFrame.cpp +++ b/src/transactions/LiquidityPoolDepositOpFrame.cpp @@ -5,6 +5,7 @@ #include "transactions/LiquidityPoolDepositOpFrame.h" #include "ledger/LedgerTxn.h" #include "ledger/LedgerTxnEntry.h" +#include "ledger/NetworkConfig.h" #include "ledger/TrustLineWrapper.h" #include "transactions/TransactionUtils.h" #include "util/ProtocolVersion.h" @@ -13,6 +14,65 @@ namespace stellar { +namespace +{ +bool +isBadPrice(int64_t amountA, int64_t amountB, Price const& minPrice, + Price const& maxPrice) +{ + // a * d < b * n is equivalent to a/b < n/d but avoids rounding. + if (amountA == 0 || amountB == 0 || + bigMultiply(amountA, minPrice.d) < bigMultiply(amountB, minPrice.n) || + bigMultiply(amountA, maxPrice.d) > bigMultiply(amountB, maxPrice.n)) + { + return true; + } + return false; +} + +bool +minAmongValid(int64_t& res, int64_t x, bool xValid, int64_t y, bool yValid) +{ + if (xValid && yValid) + { + res = std::min(x, y); + } + else if (xValid) + { + res = x; + } + else if (yValid) + { + res = y; + } + else + { + return false; + } + return true; +} + +void +updateBalance(LedgerTxnHeader& header, TrustLineWrapper& tl, + LedgerTxnEntry& acc, int64_t delta) +{ + if (tl) + { + if (!tl.addBalance(header, delta)) + { + throw std::runtime_error("insufficient balance"); + } + } + else + { + if (!stellar::addBalance(header, acc, delta)) + { + throw std::runtime_error("insufficient balance"); + } + } +} + +} // namespace LiquidityPoolDepositOpFrame::LiquidityPoolDepositOpFrame( Operation const& op, TransactionFrame const& parentTx) @@ -29,20 +89,6 @@ LiquidityPoolDepositOpFrame::isOpSupported(LedgerHeader const& header) const !isPoolDepositDisabled(header); } -static bool -isBadPrice(int64_t amountA, int64_t amountB, Price const& minPrice, - Price const& maxPrice) -{ - // a * d < b * n is equivalent to a/b < n/d but avoids rounding. - if (amountA == 0 || amountB == 0 || - bigMultiply(amountA, minPrice.d) < bigMultiply(amountB, minPrice.n) || - bigMultiply(amountA, maxPrice.d) > bigMultiply(amountB, maxPrice.n)) - { - return true; - } - return false; -} - bool LiquidityPoolDepositOpFrame::depositIntoEmptyPool( int64_t& amountA, int64_t& amountB, int64_t& amountPoolShares, @@ -75,26 +121,32 @@ LiquidityPoolDepositOpFrame::depositIntoEmptyPool( return true; } -static bool -minAmongValid(int64_t& res, int64_t x, bool xValid, int64_t y, bool yValid) +bool +LiquidityPoolDepositOpFrame::accessesFrozenKeyAtApplyTime( + std::optional const& sorobanConfig, + LiquidityPoolConstantProductParameters const& cpp) const { - if (xValid && yValid) - { - res = std::min(x, y); - } - else if (xValid) +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION + if (!sorobanConfig || !sorobanConfig->hasFrozenKeys()) { - res = x; + return false; } - else if (yValid) + // Check asset A trustline (if non-native, otherwise account freezing + // would apply) + if (cpp.assetA.type() != ASSET_TYPE_NATIVE && + sorobanConfig->isKeyFrozen(trustlineKey(getSourceID(), cpp.assetA))) { - res = y; + return true; } - else + // Check asset B trustline (if non-native, otherwise account freezing + // would apply) + if (cpp.assetB.type() != ASSET_TYPE_NATIVE && + sorobanConfig->isKeyFrozen(trustlineKey(getSourceID(), cpp.assetB))) { - return false; + return true; } - return true; +#endif + return false; } bool @@ -168,30 +220,20 @@ LiquidityPoolDepositOpFrame::depositIntoNonEmptyPool( return true; } -static void -updateBalance(LedgerTxnHeader& header, TrustLineWrapper& tl, - LedgerTxnEntry& acc, int64_t delta) -{ - if (tl) - { - if (!tl.addBalance(header, delta)) - { - throw std::runtime_error("insufficient balance"); - } - } - else - { - if (!stellar::addBalance(header, acc, delta)) - { - throw std::runtime_error("insufficient balance"); - } - } -} - bool LiquidityPoolDepositOpFrame::doApply(AppConnector& app, AbstractLedgerTxn& ltx, OperationResult& res, OperationMetaBuilder& opMeta) const +{ + throw std::runtime_error("LiquidityPoolDepositOp may only be applied with " + "doApply overload accepting sorobanConfig"); +} + +bool +LiquidityPoolDepositOpFrame::doApply( + AppConnector& app, AbstractLedgerTxn& ltx, + std::optional const& sorobanConfig, + OperationResult& res, OperationMetaBuilder& opMeta) const { ZoneNamedN(applyZone, "LiquidityPoolDepositOpFrame apply", true); @@ -232,6 +274,16 @@ LiquidityPoolDepositOpFrame::doApply(AppConnector& app, AbstractLedgerTxn& ltx, return false; } + // CAP-77: Check if any of the relevant trustlines are frozen. + auto header = ltx.loadHeader(); + if (accessesFrozenKeyAtApplyTime(sorobanConfig, cpp())) + { +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION + innerResult(res).code(LIQUIDITY_POOL_DEPOSIT_TRUSTLINE_FROZEN); +#endif + return false; + } + // If one of the assets is native, we'll also need the source account LedgerTxnEntry source; if (cpp().assetA.type() == ASSET_TYPE_NATIVE || @@ -242,8 +294,6 @@ LiquidityPoolDepositOpFrame::doApply(AppConnector& app, AbstractLedgerTxn& ltx, source = loadAccount(ltx, getSourceID()); } - auto header = ltx.loadHeader(); - int64_t amountA = 0; int64_t amountB = 0; int64_t amountPoolShares = 0; @@ -375,4 +425,13 @@ LiquidityPoolDepositOpFrame::insertLedgerKeysToPrefetch( keys.emplace(poolShareTrustLineKey(getSourceID(), mLiquidityPoolDeposit.liquidityPoolID)); } + +bool +LiquidityPoolDepositOpFrame::doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const +{ + // Checks for the frozen keys are done at apply time in + // accessesFrozenKeyAtApplyTime, we can't check anything at validation time. + return false; +} } diff --git a/src/transactions/LiquidityPoolDepositOpFrame.h b/src/transactions/LiquidityPoolDepositOpFrame.h index 31ba4d1135..e9ac3ad9dc 100644 --- a/src/transactions/LiquidityPoolDepositOpFrame.h +++ b/src/transactions/LiquidityPoolDepositOpFrame.h @@ -35,6 +35,10 @@ class LiquidityPoolDepositOpFrame : public OperationFrame LiquidityPoolConstantProduct const& cp, OperationResult& res) const; + bool accessesFrozenKeyAtApplyTime( + std::optional const& sorobanConfig, + LiquidityPoolConstantProductParameters const& cpp) const; + LiquidityPoolDepositOp const& mLiquidityPoolDeposit; public: @@ -46,11 +50,18 @@ class LiquidityPoolDepositOpFrame : public OperationFrame bool doApply(AppConnector& app, AbstractLedgerTxn& ltx, OperationResult& res, OperationMetaBuilder& opMeta) const override; + bool doApply(AppConnector& app, AbstractLedgerTxn& ltx, + std::optional const& sorobanConfig, + OperationResult& res, + OperationMetaBuilder& opMeta) const override; bool doCheckValid(uint32_t ledgerVersion, OperationResult& res) const override; void insertLedgerKeysToPrefetch(UnorderedSet& keys) const override; + bool doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const override; + static LiquidityPoolDepositResultCode getInnerCode(OperationResult const& res) { diff --git a/src/transactions/LiquidityPoolWithdrawOpFrame.cpp b/src/transactions/LiquidityPoolWithdrawOpFrame.cpp index 8ab2ba3275..8c03357c3c 100644 --- a/src/transactions/LiquidityPoolWithdrawOpFrame.cpp +++ b/src/transactions/LiquidityPoolWithdrawOpFrame.cpp @@ -5,6 +5,7 @@ #include "transactions/LiquidityPoolWithdrawOpFrame.h" #include "ledger/LedgerTxn.h" #include "ledger/LedgerTxnEntry.h" +#include "ledger/NetworkConfig.h" #include "ledger/TrustLineWrapper.h" #include "transactions/TransactionUtils.h" #include "util/ProtocolVersion.h" @@ -28,10 +29,51 @@ LiquidityPoolWithdrawOpFrame::isOpSupported(LedgerHeader const& header) const !isPoolWithdrawalDisabled(header); } +bool +LiquidityPoolWithdrawOpFrame::accessesFrozenKeyAtApplyTime( + std::optional const& sorobanConfig, + LiquidityPoolEntry const& pool) const +{ +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION + if (!sorobanConfig || !sorobanConfig->hasFrozenKeys()) + { + return false; + } + auto const& constantProduct = pool.body.constantProduct(); + // Check asset A trustline (if non-native, otherwise account freezing + // would apply) + if (constantProduct.params.assetA.type() != ASSET_TYPE_NATIVE && + sorobanConfig->isKeyFrozen( + trustlineKey(getSourceID(), constantProduct.params.assetA))) + { + return true; + } + // Check asset B trustline (if non-native, otherwise account freezing + // would apply) + if (constantProduct.params.assetB.type() != ASSET_TYPE_NATIVE && + sorobanConfig->isKeyFrozen( + trustlineKey(getSourceID(), constantProduct.params.assetB))) + { + return true; + } +#endif + return false; +} + bool LiquidityPoolWithdrawOpFrame::doApply(AppConnector& app, AbstractLedgerTxn& ltx, OperationResult& res, OperationMetaBuilder& opMeta) const +{ + throw std::runtime_error("LiquidityPoolWithdrawOp may only be applied with " + "doApply overload accepting sorobanConfig"); +} + +bool +LiquidityPoolWithdrawOpFrame::doApply( + AppConnector& app, AbstractLedgerTxn& ltx, + std::optional const& sorobanConfig, + OperationResult& res, OperationMetaBuilder& opMeta) const { ZoneNamedN(applyZone, "LiquidityPoolWithdrawOpFrame apply", true); @@ -56,10 +98,20 @@ LiquidityPoolWithdrawOpFrame::doApply(AppConnector& app, AbstractLedgerTxn& ltx, // use a lambda so we don't hold a reference to the internals of // LiquidityPoolEntry - auto constantProduct = [&]() -> auto& { + auto constantProduct = [&poolEntry]() -> auto& { return poolEntry.current().data.liquidityPool().body.constantProduct(); }; + // CAP-77: Check if any of the relevant trustlines are frozen. + if (accessesFrozenKeyAtApplyTime(sorobanConfig, + poolEntry.current().data.liquidityPool())) + { +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION + innerResult(res).code(LIQUIDITY_POOL_WITHDRAW_TRUSTLINE_FROZEN); +#endif + return false; + } + auto amountA = getPoolWithdrawalAmount(mLiquidityPoolWithdraw.amount, constantProduct().totalPoolShares, constantProduct().reserveA); @@ -173,4 +225,12 @@ LiquidityPoolWithdrawOpFrame::tryAddAssetBalance( return true; } +bool +LiquidityPoolWithdrawOpFrame::doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const +{ + // Checks for the frozen keys are done at apply time in + // accessesFrozenKeyAtApplyTime, we can't check anything at validation time. + return false; +} } diff --git a/src/transactions/LiquidityPoolWithdrawOpFrame.h b/src/transactions/LiquidityPoolWithdrawOpFrame.h index 394213bd55..77dc512c9e 100644 --- a/src/transactions/LiquidityPoolWithdrawOpFrame.h +++ b/src/transactions/LiquidityPoolWithdrawOpFrame.h @@ -24,6 +24,10 @@ class LiquidityPoolWithdrawOpFrame : public OperationFrame LedgerTxnHeader const& header, Asset const& asset, int64_t minAmount, int64_t amount) const; + bool accessesFrozenKeyAtApplyTime( + std::optional const& sorobanConfig, + LiquidityPoolEntry const& pool) const; + public: LiquidityPoolWithdrawOpFrame(Operation const& op, TransactionFrame const& parentTx); @@ -33,11 +37,18 @@ class LiquidityPoolWithdrawOpFrame : public OperationFrame bool doApply(AppConnector& app, AbstractLedgerTxn& ltx, OperationResult& res, OperationMetaBuilder& opMeta) const override; + bool doApply(AppConnector& app, AbstractLedgerTxn& ltx, + std::optional const& sorobanConfig, + OperationResult& res, + OperationMetaBuilder& opMeta) const override; bool doCheckValid(uint32_t ledgerVersion, OperationResult& res) const override; void insertLedgerKeysToPrefetch(UnorderedSet& keys) const override; + bool doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const override; + static LiquidityPoolWithdrawResultCode getInnerCode(OperationResult const& res) { diff --git a/src/transactions/ManageDataOpFrame.cpp b/src/transactions/ManageDataOpFrame.cpp index 7c8c6076e0..7fcb6cf482 100644 --- a/src/transactions/ManageDataOpFrame.cpp +++ b/src/transactions/ManageDataOpFrame.cpp @@ -127,4 +127,11 @@ ManageDataOpFrame::insertLedgerKeysToPrefetch( { keys.emplace(dataKey(getSourceID(), mManageData.dataName)); } + +bool +ManageDataOpFrame::doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const +{ + return false; +} } diff --git a/src/transactions/ManageDataOpFrame.h b/src/transactions/ManageDataOpFrame.h index 2410ede468..26793db82c 100644 --- a/src/transactions/ManageDataOpFrame.h +++ b/src/transactions/ManageDataOpFrame.h @@ -32,6 +32,9 @@ class ManageDataOpFrame : public OperationFrame void insertLedgerKeysToPrefetch(UnorderedSet& keys) const override; + bool doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const override; + static ManageDataResultCode getInnerCode(OperationResult const& res) { diff --git a/src/transactions/ManageOfferOpFrameBase.cpp b/src/transactions/ManageOfferOpFrameBase.cpp index 9255b580bf..6069415bda 100644 --- a/src/transactions/ManageOfferOpFrameBase.cpp +++ b/src/transactions/ManageOfferOpFrameBase.cpp @@ -6,6 +6,7 @@ #include "ledger/LedgerTxn.h" #include "ledger/LedgerTxnEntry.h" #include "ledger/LedgerTxnHeader.h" +#include "ledger/NetworkConfig.h" #include "ledger/TrustLineWrapper.h" #include "transactions/OfferExchange.h" #include "transactions/SponsorshipUtils.h" @@ -215,6 +216,16 @@ bool ManageOfferOpFrameBase::doApply(AppConnector& app, AbstractLedgerTxn& ltxOuter, OperationResult& res, OperationMetaBuilder& opMeta) const +{ + throw std::runtime_error("ManageOfferOp may only be applied with doApply " + "overload accepting sorobanConfig"); +} + +bool +ManageOfferOpFrameBase::doApply( + AppConnector& app, AbstractLedgerTxn& ltxOuter, + std::optional const& sorobanConfig, + OperationResult& res, OperationMetaBuilder& opMeta) const { ZoneNamedN(applyZone, "ManageOfferOp apply", true); std::string pairStr = assetToString(mSheep); @@ -335,9 +346,9 @@ ManageOfferOpFrameBase::doApply(AppConnector& app, AbstractLedgerTxn& ltxOuter, } int64_t maxOffersToCross = INT64_MAX; + auto ledgerVersion = ltx.loadHeader().current().ledgerVersion; if (protocolVersionStartsFrom( - ltx.loadHeader().current().ledgerVersion, - FIRST_PROTOCOL_SUPPORTING_OPERATION_LIMITS)) + ledgerVersion, FIRST_PROTOCOL_SUPPORTING_OPERATION_LIMITS)) { maxOffersToCross = getMaxOffersToCross(); } @@ -348,7 +359,8 @@ ManageOfferOpFrameBase::doApply(AppConnector& app, AbstractLedgerTxn& ltxOuter, ConvertResult r = convertWithOffersAndPools( ltx, mSheep, maxSheepSend, sheepSent, mWheat, maxWheatReceive, wheatReceived, RoundingType::NORMAL, - [this, passive, &maxWheatPrice](LedgerTxnEntry const& entry) { + [this, passive, &maxWheatPrice, + sorobanConfig](LedgerTxnEntry const& entry) { auto const& o = entry.current().data.offer(); releaseAssertOrThrow(o.offerID != mOfferID); if ((passive && (o.price >= maxWheatPrice)) || @@ -361,6 +373,12 @@ ManageOfferOpFrameBase::doApply(AppConnector& app, AbstractLedgerTxn& ltxOuter, // we are crossing our own offer return OfferFilterResult::eStopCrossSelf; } + // CAP-77: Skip offers with frozen seller account or + // trustlines + if (sorobanConfig && offerAccessesFrozenKey(o, *sorobanConfig)) + { + return OfferFilterResult::eSkipFrozen; + } return OfferFilterResult::eKeep; }, offerTrail, maxOffersToCross); @@ -644,4 +662,22 @@ ManageOfferOpFrameBase::insertLedgerKeysToPrefetch( addIssuerAndTrustline(mSheep); addIssuerAndTrustline(mWheat); } + +bool +ManageOfferOpFrameBase::doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const +{ + if (mSheep.type() != ASSET_TYPE_NATIVE && + sorobanConfig.isKeyFrozen(trustlineKey(getSourceID(), mSheep))) + { + return true; + } + if (mWheat.type() != ASSET_TYPE_NATIVE && + sorobanConfig.isKeyFrozen(trustlineKey(getSourceID(), mWheat))) + { + return true; + } + + return false; +} } diff --git a/src/transactions/ManageOfferOpFrameBase.h b/src/transactions/ManageOfferOpFrameBase.h index cbab7d5006..9d4f8d250e 100644 --- a/src/transactions/ManageOfferOpFrameBase.h +++ b/src/transactions/ManageOfferOpFrameBase.h @@ -46,12 +46,19 @@ class ManageOfferOpFrameBase : public OperationFrame bool doApply(AppConnector& app, AbstractLedgerTxn& ltxOuter, OperationResult& res, OperationMetaBuilder& opMeta) const override; + bool doApply(AppConnector& app, AbstractLedgerTxn& ltxOuter, + std::optional const& sorobanConfig, + OperationResult& res, + OperationMetaBuilder& opMeta) const override; bool isDexOperation() const override; void insertLedgerKeysToPrefetch(UnorderedSet& keys) const override; + bool doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const override; + virtual bool isAmountValid() const = 0; virtual bool isDeleteOffer() const = 0; diff --git a/src/transactions/MergeOpFrame.cpp b/src/transactions/MergeOpFrame.cpp index 55e7820298..89df1cd309 100644 --- a/src/transactions/MergeOpFrame.cpp +++ b/src/transactions/MergeOpFrame.cpp @@ -285,4 +285,12 @@ MergeOpFrame::doCheckValid(uint32_t ledgerVersion, OperationResult& res) const } return true; } + +bool +MergeOpFrame::doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const +{ + return sorobanConfig.isKeyFrozen( + accountKey(toAccountID(mOperation.body.destination()))); +} } diff --git a/src/transactions/MergeOpFrame.h b/src/transactions/MergeOpFrame.h index 80fb11b4c7..8f766b6692 100644 --- a/src/transactions/MergeOpFrame.h +++ b/src/transactions/MergeOpFrame.h @@ -39,6 +39,9 @@ class MergeOpFrame : public OperationFrame bool doCheckValid(uint32_t ledgerVersion, OperationResult& res) const override; + bool doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const override; + static AccountMergeResultCode getInnerCode(OperationResult const& res) { diff --git a/src/transactions/OfferExchange.cpp b/src/transactions/OfferExchange.cpp index ce514622e3..f55476103c 100644 --- a/src/transactions/OfferExchange.cpp +++ b/src/transactions/OfferExchange.cpp @@ -1543,6 +1543,20 @@ convertWithOffers( return ConvertResult::eFilterStopBadPrice; case OfferFilterResult::eStopCrossSelf: return ConvertResult::eFilterStopCrossSelf; + case OfferFilterResult::eSkipFrozen: + { + // CAP-77: Remove the frozen offer, release liabilities, + // and continue matching with subsequent offers. + auto& offer = wheatOffer.current().data.offer(); + auto header = ltx.loadHeader(); + releaseLiabilities(ltx, header, wheatOffer); + auto accountB = stellar::loadAccount(ltx, offer.sellerID); + removeEntryWithPossibleSponsorship( + ltx, header, wheatOffer.current(), accountB); + wheatOffer.erase(); + ltx.commit(); + continue; + } default: throw std::runtime_error("unexpected filter result"); } diff --git a/src/transactions/OfferExchange.h b/src/transactions/OfferExchange.h index a3ebc13249..715fc2f4ed 100644 --- a/src/transactions/OfferExchange.h +++ b/src/transactions/OfferExchange.h @@ -292,7 +292,8 @@ enum class OfferFilterResult { eKeep, eStopBadPrice, - eStopCrossSelf + eStopCrossSelf, + eSkipFrozen }; enum class ConvertResult diff --git a/src/transactions/OperationFrame.cpp b/src/transactions/OperationFrame.cpp index c883ec1aea..ccd8448165 100644 --- a/src/transactions/OperationFrame.cpp +++ b/src/transactions/OperationFrame.cpp @@ -163,7 +163,7 @@ OperationFrame::apply( } else { - applyRes = doApply(app, ltx, res, opMeta); + applyRes = doApply(app, ltx, sorobanConfig, res, opMeta); } CLOG_TRACE(Tx, "{}", xdrToCerealString(res, "OperationResult")); @@ -374,6 +374,18 @@ OperationFrame::doApplyForSoroban( OperationResult& res, std::optional& refundableFeeTracker, OperationMetaBuilder& opMeta) const +{ + // This implementation is just a stub for classic operations, it's not + // supposed to be called by them. + throw std::runtime_error( + "doApplyForSoroban should be overridden for Soroban operations"); +} + +bool +OperationFrame::doApply( + AppConnector& app, AbstractLedgerTxn& ltx, + std::optional const& sorobanConfig, + OperationResult& res, OperationMetaBuilder& opMeta) const { return doApply(app, ltx, res, opMeta); } @@ -412,6 +424,21 @@ OperationFrame::getSorobanResources() const return mParentTx.sorobanResources(); } +bool +OperationFrame::accessesFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const +{ + if (mOperation.sourceAccount) + { + auto opSrcKey = accountKey(toAccountID(*mOperation.sourceAccount)); + if (sorobanConfig.isKeyFrozen(opSrcKey)) + { + return true; + } + } + return doesAccessFrozenKey(sorobanConfig); +} + Memo const& OperationFrame::getTxMemo() const { diff --git a/src/transactions/OperationFrame.h b/src/transactions/OperationFrame.h index a97cfed47b..709b96daf1 100644 --- a/src/transactions/OperationFrame.h +++ b/src/transactions/OperationFrame.h @@ -54,6 +54,10 @@ class OperationFrame Hash const& sorobanBasePrngSeed, OperationResult& res, std::optional& refundableFeeTracker, OperationMetaBuilder& opMeta) const; + virtual bool + doApply(AppConnector& app, AbstractLedgerTxn& ltx, + std::optional const& sorobanConfig, + OperationResult& res, OperationMetaBuilder& opMeta) const; virtual bool doApply(AppConnector& app, AbstractLedgerTxn& ltx, OperationResult& res, OperationMetaBuilder& opMeta) const = 0; @@ -77,6 +81,9 @@ class OperationFrame LedgerTxnEntry loadSourceAccount(AbstractLedgerTxn& ltx, LedgerTxnHeader const& header) const; + virtual bool + doesAccessFrozenKey(SorobanNetworkConfig const& sorobanConfig) const = 0; + public: static std::shared_ptr makeHelper(Operation const& op, TransactionFrame const& parentTx, @@ -131,6 +138,8 @@ class OperationFrame SorobanResources const& getSorobanResources() const; + bool accessesFrozenKey(SorobanNetworkConfig const& sorobanConfig) const; + Memo const& getTxMemo() const; SorobanTransactionData::_ext_t const& getResourcesExt() const; }; diff --git a/src/transactions/PathPaymentOpFrameBase.cpp b/src/transactions/PathPaymentOpFrameBase.cpp index a201daf683..6eedd24c48 100644 --- a/src/transactions/PathPaymentOpFrameBase.cpp +++ b/src/transactions/PathPaymentOpFrameBase.cpp @@ -6,6 +6,7 @@ #include "ledger/LedgerTxn.h" #include "ledger/LedgerTxnEntry.h" #include "ledger/LedgerTxnHeader.h" +#include "ledger/NetworkConfig.h" #include "ledger/TrustLineWrapper.h" #include "transactions/TransactionUtils.h" #include "util/GlobalChecks.h" @@ -27,6 +28,26 @@ PathPaymentOpFrameBase::getDestID() const return toAccountID(getDestMuxedAccount()); } +bool +PathPaymentOpFrameBase::doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const +{ + auto const& srcAsset = getSourceAsset(); + if (srcAsset.type() != ASSET_TYPE_NATIVE && + sorobanConfig.isKeyFrozen(trustlineKey(getSourceID(), srcAsset))) + { + return true; + } + + auto const& destAsset = getDestAsset(); + auto destID = getDestID(); + if (destAsset.type() != ASSET_TYPE_NATIVE) + { + return sorobanConfig.isKeyFrozen(trustlineKey(destID, destAsset)); + } + return sorobanConfig.isKeyFrozen(accountKey(destID)); +} + void PathPaymentOpFrameBase::insertLedgerKeysToPrefetch( UnorderedSet& keys) const @@ -71,6 +92,8 @@ PathPaymentOpFrameBase::checkIssuer(AbstractLedgerTxn& ltx, Asset const& asset, bool PathPaymentOpFrameBase::convert( + AppConnector& app, + std::optional const& sorobanConfig, AbstractLedgerTxn& ltx, int64_t maxOffersToCross, Asset const& sendAsset, int64_t maxSend, int64_t& amountSend, Asset const& recvAsset, int64_t maxRecv, int64_t& amountRecv, RoundingType round, @@ -83,13 +106,18 @@ PathPaymentOpFrameBase::convert( ConvertResult r = convertWithOffersAndPools( ltx, sendAsset, maxSend, amountSend, recvAsset, maxRecv, amountRecv, round, - [this](LedgerTxnEntry const& o) { + [this, sorobanConfig](LedgerTxnEntry const& o) { auto const& offer = o.current().data.offer(); if (offer.sellerID == getSourceID()) { // we are crossing our own offer return OfferFilterResult::eStopCrossSelf; } + // CAP-77: Skip offers with frozen seller account or trustlines + if (sorobanConfig && offerAccessesFrozenKey(offer, *sorobanConfig)) + { + return OfferFilterResult::eSkipFrozen; + } return OfferFilterResult::eKeep; }, offerTrail, maxOffersToCross); diff --git a/src/transactions/PathPaymentOpFrameBase.h b/src/transactions/PathPaymentOpFrameBase.h index 54d0e9f709..a0fb292cf2 100644 --- a/src/transactions/PathPaymentOpFrameBase.h +++ b/src/transactions/PathPaymentOpFrameBase.h @@ -14,7 +14,9 @@ class AbstractLedgerTxn; class PathPaymentOpFrameBase : public OperationFrame { protected: - bool convert(AbstractLedgerTxn& ltx, int64_t maxOffersToCross, + bool convert(AppConnector& app, + std::optional const& sorobanConfig, + AbstractLedgerTxn& ltx, int64_t maxOffersToCross, Asset const& sendAsset, int64_t maxSend, int64_t& amountSend, Asset const& recvAsset, int64_t maxRecv, int64_t& amountRecv, RoundingType round, std::vector& offerTrail, @@ -41,6 +43,9 @@ class PathPaymentOpFrameBase : public OperationFrame bool isDexOperation() const override; + bool doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const override; + virtual bool checkTransfer(int64_t maxSend, int64_t amountSend, int64_t maxRecv, int64_t amountRecv) const = 0; diff --git a/src/transactions/PathPaymentStrictReceiveOpFrame.cpp b/src/transactions/PathPaymentStrictReceiveOpFrame.cpp index 0ad43263f3..9356ad72d2 100644 --- a/src/transactions/PathPaymentStrictReceiveOpFrame.cpp +++ b/src/transactions/PathPaymentStrictReceiveOpFrame.cpp @@ -28,6 +28,16 @@ PathPaymentStrictReceiveOpFrame::doApply(AppConnector& app, AbstractLedgerTxn& ltx, OperationResult& res, OperationMetaBuilder& opMeta) const +{ + throw std::runtime_error("PathPaymentStrictReceiveOp may only be applied " + "with doApply overload accepting sorobanConfig"); +} + +bool +PathPaymentStrictReceiveOpFrame::doApply( + AppConnector& app, AbstractLedgerTxn& ltx, + std::optional const& sorobanConfig, + OperationResult& res, OperationMetaBuilder& opMeta) const { ZoneNamedN(applyZone, "PathPaymentStrictReceiveOp apply", true); std::string pathStr = assetToString(getSourceAsset()); @@ -105,10 +115,10 @@ PathPaymentStrictReceiveOpFrame::doApply(AppConnector& app, int64_t amountSend = 0; int64_t amountRecv = 0; std::vector offerTrail; - if (!convert(ltx, maxOffersToCross, sendAsset, INT64_MAX, amountSend, - recvAsset, maxAmountRecv, amountRecv, - RoundingType::PATH_PAYMENT_STRICT_RECEIVE, offerTrail, - res)) + if (!convert(app, sorobanConfig, ltx, maxOffersToCross, sendAsset, + INT64_MAX, amountSend, recvAsset, maxAmountRecv, + amountRecv, RoundingType::PATH_PAYMENT_STRICT_RECEIVE, + offerTrail, res)) { return false; } diff --git a/src/transactions/PathPaymentStrictReceiveOpFrame.h b/src/transactions/PathPaymentStrictReceiveOpFrame.h index b83573ff1f..a5fdbf708b 100644 --- a/src/transactions/PathPaymentStrictReceiveOpFrame.h +++ b/src/transactions/PathPaymentStrictReceiveOpFrame.h @@ -26,6 +26,10 @@ class PathPaymentStrictReceiveOpFrame : public PathPaymentOpFrameBase bool doApply(AppConnector& app, AbstractLedgerTxn& ltx, OperationResult& res, OperationMetaBuilder& opMeta) const override; + bool doApply(AppConnector& app, AbstractLedgerTxn& ltx, + std::optional const& sorobanConfig, + OperationResult& res, + OperationMetaBuilder& opMeta) const override; bool doCheckValid(uint32_t ledgerVersion, OperationResult& res) const override; diff --git a/src/transactions/PathPaymentStrictSendOpFrame.cpp b/src/transactions/PathPaymentStrictSendOpFrame.cpp index 4016b352a0..0dcc57a269 100644 --- a/src/transactions/PathPaymentStrictSendOpFrame.cpp +++ b/src/transactions/PathPaymentStrictSendOpFrame.cpp @@ -34,6 +34,16 @@ bool PathPaymentStrictSendOpFrame::doApply(AppConnector& app, AbstractLedgerTxn& ltx, OperationResult& res, OperationMetaBuilder& opMeta) const +{ + throw std::runtime_error("PathPaymentStrictSendOp may only be applied with " + "doApply overload accepting sorobanConfig"); +} + +bool +PathPaymentStrictSendOpFrame::doApply( + AppConnector& app, AbstractLedgerTxn& ltx, + std::optional const& sorobanConfig, + OperationResult& res, OperationMetaBuilder& opMeta) const { ZoneNamedN(applyZone, "PathPaymentStrictSendOp apply", true); std::string pathStr = assetToString(getSourceAsset()); @@ -95,9 +105,10 @@ PathPaymentStrictSendOpFrame::doApply(AppConnector& app, AbstractLedgerTxn& ltx, int64_t amountSend = 0; int64_t amountRecv = 0; std::vector offerTrail; - if (!convert(ltx, maxOffersToCross, sendAsset, maxAmountSend, - amountSend, recvAsset, INT64_MAX, amountRecv, - RoundingType::PATH_PAYMENT_STRICT_SEND, offerTrail, res)) + if (!convert(app, sorobanConfig, ltx, maxOffersToCross, sendAsset, + maxAmountSend, amountSend, recvAsset, INT64_MAX, + amountRecv, RoundingType::PATH_PAYMENT_STRICT_SEND, + offerTrail, res)) { return false; } diff --git a/src/transactions/PathPaymentStrictSendOpFrame.h b/src/transactions/PathPaymentStrictSendOpFrame.h index a24ef88555..a302b7b88c 100644 --- a/src/transactions/PathPaymentStrictSendOpFrame.h +++ b/src/transactions/PathPaymentStrictSendOpFrame.h @@ -28,6 +28,10 @@ class PathPaymentStrictSendOpFrame : public PathPaymentOpFrameBase bool doApply(AppConnector& app, AbstractLedgerTxn& ltx, OperationResult& res, OperationMetaBuilder& opMeta) const override; + bool doApply(AppConnector& app, AbstractLedgerTxn& ltx, + std::optional const& sorobanConfig, + OperationResult& res, + OperationMetaBuilder& opMeta) const override; bool doCheckValid(uint32_t ledgerVersion, OperationResult& res) const override; diff --git a/src/transactions/PaymentOpFrame.cpp b/src/transactions/PaymentOpFrame.cpp index bf1e852ff0..9c1a7aeb58 100644 --- a/src/transactions/PaymentOpFrame.cpp +++ b/src/transactions/PaymentOpFrame.cpp @@ -28,6 +28,16 @@ bool PaymentOpFrame::doApply(AppConnector& app, AbstractLedgerTxn& ltx, OperationResult& res, OperationMetaBuilder& opMeta) const +{ + throw std::runtime_error("PaymentOp may only be applied " + "with doApply overload accepting sorobanConfig"); +} + +bool +PaymentOpFrame::doApply( + AppConnector& app, AbstractLedgerTxn& ltx, + std::optional const& sorobanConfig, + OperationResult& res, OperationMetaBuilder& opMeta) const { ZoneNamedN(applyZone, "PaymentOp apply", true); std::string payStr = assetToString(mPayment.asset); @@ -72,7 +82,7 @@ PaymentOpFrame::doApply(AppConnector& app, AbstractLedgerTxn& ltx, PathPaymentStrictReceiveOpFrame ppayment(op, mParentTx); if (!ppayment.doCheckValid(ledgerVersion, ppRes) || - !ppayment.doApply(app, ltx, ppRes, opMeta)) + !ppayment.doApply(app, ltx, sorobanConfig, ppRes, opMeta)) { if (ppRes.code() != opINNER) { @@ -155,4 +165,25 @@ PaymentOpFrame::insertLedgerKeysToPrefetch(UnorderedSet& keys) const keys.emplace(trustlineKey(getSourceID(), mPayment.asset)); } } + +bool +PaymentOpFrame::doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const +{ + if (mPayment.asset.type() == ASSET_TYPE_NATIVE) + { + return sorobanConfig.isKeyFrozen( + accountKey(toAccountID(mPayment.destination))); + } + if (sorobanConfig.isKeyFrozen(trustlineKey(getSourceID(), mPayment.asset))) + { + return true; + } + if (sorobanConfig.isKeyFrozen( + trustlineKey(toAccountID(mPayment.destination), mPayment.asset))) + { + return true; + } + return false; +} } diff --git a/src/transactions/PaymentOpFrame.h b/src/transactions/PaymentOpFrame.h index bc37cf1e20..7422c1869b 100644 --- a/src/transactions/PaymentOpFrame.h +++ b/src/transactions/PaymentOpFrame.h @@ -25,11 +25,18 @@ class PaymentOpFrame : public OperationFrame bool doApply(AppConnector& app, AbstractLedgerTxn& ltx, OperationResult& res, OperationMetaBuilder& opMeta) const override; + bool doApply(AppConnector& app, AbstractLedgerTxn& ltx, + std::optional const& sorobanConfig, + OperationResult& res, + OperationMetaBuilder& opMeta) const override; bool doCheckValid(uint32_t ledgerVersion, OperationResult& res) const override; void insertLedgerKeysToPrefetch(UnorderedSet& keys) const override; + bool doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const override; + static PaymentResultCode getInnerCode(OperationResult const& res) { diff --git a/src/transactions/RestoreFootprintOpFrame.cpp b/src/transactions/RestoreFootprintOpFrame.cpp index 7fce2812b2..e7be52a973 100644 --- a/src/transactions/RestoreFootprintOpFrame.cpp +++ b/src/transactions/RestoreFootprintOpFrame.cpp @@ -468,4 +468,13 @@ RestoreFootprintOpFrame::getThresholdLevel() const { return ThresholdLevel::LOW; } + +bool +RestoreFootprintOpFrame::doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const +{ + // Soroban footprint checks happen at transaction level, so we can safely + // say that the operation itself doesn't access frozen keys. + return false; +} } diff --git a/src/transactions/RestoreFootprintOpFrame.h b/src/transactions/RestoreFootprintOpFrame.h index 6297431c4c..0db0b25e19 100644 --- a/src/transactions/RestoreFootprintOpFrame.h +++ b/src/transactions/RestoreFootprintOpFrame.h @@ -60,6 +60,10 @@ class RestoreFootprintOpFrame : public OperationFrame virtual bool isSoroban() const override; ThresholdLevel getThresholdLevel() const override; + + bool doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const override; + friend class RestoreFootprintApplyHelper; friend class RestoreFootprintPreV23ApplyHelper; friend class RestoreFootprintParallelApplyHelper; diff --git a/src/transactions/RevokeSponsorshipOpFrame.cpp b/src/transactions/RevokeSponsorshipOpFrame.cpp index 82e7f922b9..b90e87d5f4 100644 --- a/src/transactions/RevokeSponsorshipOpFrame.cpp +++ b/src/transactions/RevokeSponsorshipOpFrame.cpp @@ -454,4 +454,26 @@ RevokeSponsorshipOpFrame::doCheckValid(uint32_t ledgerVersion, } return true; } + +bool +RevokeSponsorshipOpFrame::doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const +{ + if (mRevokeSponsorshipOp.type() == REVOKE_SPONSORSHIP_LEDGER_ENTRY) + { + if (sorobanConfig.isKeyFrozen(mRevokeSponsorshipOp.ledgerKey())) + { + return true; + } + } + else if (mRevokeSponsorshipOp.type() == REVOKE_SPONSORSHIP_SIGNER) + { + if (sorobanConfig.isKeyFrozen( + accountKey(mRevokeSponsorshipOp.signer().accountID))) + { + return true; + } + } + return false; +} } diff --git a/src/transactions/RevokeSponsorshipOpFrame.h b/src/transactions/RevokeSponsorshipOpFrame.h index f205553946..eeaf02022c 100644 --- a/src/transactions/RevokeSponsorshipOpFrame.h +++ b/src/transactions/RevokeSponsorshipOpFrame.h @@ -51,6 +51,9 @@ class RevokeSponsorshipOpFrame : public OperationFrame bool doCheckValid(uint32_t ledgerVersion, OperationResult& res) const override; + bool doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const override; + static RevokeSponsorshipResultCode getInnerCode(OperationResult const& res) { diff --git a/src/transactions/SetOptionsOpFrame.cpp b/src/transactions/SetOptionsOpFrame.cpp index f3089e5f12..251e877a16 100644 --- a/src/transactions/SetOptionsOpFrame.cpp +++ b/src/transactions/SetOptionsOpFrame.cpp @@ -326,4 +326,11 @@ SetOptionsOpFrame::doCheckValid(uint32_t ledgerVersion, return true; } + +bool +SetOptionsOpFrame::doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const +{ + return false; } +} \ No newline at end of file diff --git a/src/transactions/SetOptionsOpFrame.h b/src/transactions/SetOptionsOpFrame.h index 2e740199fb..b83081ca41 100644 --- a/src/transactions/SetOptionsOpFrame.h +++ b/src/transactions/SetOptionsOpFrame.h @@ -33,6 +33,9 @@ class SetOptionsOpFrame : public OperationFrame bool doCheckValid(uint32_t ledgerVersion, OperationResult& res) const override; + bool doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const override; + static SetOptionsResultCode getInnerCode(OperationResult const& res) { diff --git a/src/transactions/SetTrustLineFlagsOpFrame.cpp b/src/transactions/SetTrustLineFlagsOpFrame.cpp index 41256ac282..fcdfcd847c 100644 --- a/src/transactions/SetTrustLineFlagsOpFrame.cpp +++ b/src/transactions/SetTrustLineFlagsOpFrame.cpp @@ -219,4 +219,12 @@ SetTrustLineFlagsOpFrame::isRevocationToMaintainLiabilitiesValid( return true; } +bool +SetTrustLineFlagsOpFrame::doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const +{ + return sorobanConfig.isKeyFrozen( + trustlineKey(mSetTrustLineFlags.trustor, mSetTrustLineFlags.asset)); +} + } diff --git a/src/transactions/SetTrustLineFlagsOpFrame.h b/src/transactions/SetTrustLineFlagsOpFrame.h index 7710cf7047..66744a404d 100644 --- a/src/transactions/SetTrustLineFlagsOpFrame.h +++ b/src/transactions/SetTrustLineFlagsOpFrame.h @@ -55,6 +55,9 @@ class SetTrustLineFlagsOpFrame : public TrustFlagsOpFrameBase void insertLedgerKeysToPrefetch(UnorderedSet& keys) const override; + bool doesAccessFrozenKey( + SorobanNetworkConfig const& sorobanConfig) const override; + static SetTrustLineFlagsResultCode getInnerCode(OperationResult const& res) { diff --git a/src/transactions/TransactionFrame.cpp b/src/transactions/TransactionFrame.cpp index ac72451617..50264a5e85 100644 --- a/src/transactions/TransactionFrame.cpp +++ b/src/transactions/TransactionFrame.cpp @@ -1064,6 +1064,52 @@ TransactionFrame::updateSorobanMetrics(AppConnector& app) const metrics.accumulateLedgerWriteByte(r.writeBytes); } +bool +TransactionFrame::accessesFrozenKey(SorobanNetworkConfig const& cfg) const +{ + if (!cfg.hasFrozenKeys()) + { + return false; + } + + // Transaction source account + auto srcAcctKey = accountKey(getSourceID()); + if (cfg.isKeyFrozen(srcAcctKey)) + { + return true; + } + + // Soroban footprint: check if any readOnly/readWrite key is frozen + if (isSoroban()) + { + auto const& sorobanData = mEnvelope.v1().tx.ext.sorobanData(); + for (auto const& lk : sorobanData.resources.footprint.readOnly) + { + if (cfg.isKeyFrozen(lk)) + { + return true; + } + } + for (auto const& lk : sorobanData.resources.footprint.readWrite) + { + if (cfg.isKeyFrozen(lk)) + { + return true; + } + } + } + + // Check every operation + for (auto const& op : mOperations) + { + if (op->accessesFrozenKey(cfg)) + { + return true; + } + } + return false; +} + FeePair TransactionFrame::computeSorobanResourceFee( uint32_t protocolVersion, SorobanResources const& txResources, @@ -1229,7 +1275,7 @@ TransactionFrame::commonValidPreSeqNum( AppConnector& app, SorobanNetworkConfig const* cfg, LedgerSnapshot const& ls, bool chargeFee, uint64_t lowerBoundCloseTimeOffset, uint64_t upperBoundCloseTimeOffset, - std::optional sorobanResourceFee, + Hash const& envelopeContentsHash, std::optional sorobanResourceFee, MutableTransactionResultBase& txResult, DiagnosticEventManager& diagnosticEvents) const { @@ -1453,6 +1499,22 @@ TransactionFrame::commonValidPreSeqNum( return std::nullopt; } +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION + // CAP-77: Frozen ledger key checks. + // `cfg` check here could be a protocol-gated assertion as we're expected + // to have network config in protocol supporting CAP-77. However, that + // would break the tests that invoke `apply` directly and unconditionally + // pass `nullopt` for the config. + // We can clean this up after updating all the tests to pass the config + // properly. + if (cfg && accessesFrozenKey(*cfg) && + !cfg->isFreezeBypassTx(envelopeContentsHash)) + { + txResult.setInnermostError(txFROZEN_KEY_ACCESSED); + return std::nullopt; + } +#endif + return sourceAccount; } @@ -1503,9 +1565,9 @@ TransactionFrame::processSignatures( } bool allOpsValid = true; - // From protocol 10-13, there's a dangling reference bug where we check op - // signatures even if no OperationResult object exists. This check ensures - // opResult actually exists. + // From protocol 10-13, there's a dangling reference bug where we check + // op signatures even if no OperationResult object exists. This check + // ensures opResult actually exists. if (auto code = txResult.getInnermostResultCode(); code == txSUCCESS || code == txFAILED) { @@ -1539,9 +1601,9 @@ TransactionFrame::isBadSeq(LedgerHeaderWrapper const& header, return true; } - // If seqNum == INT64_MAX, seqNum >= getSeqNum() is guaranteed to be true - // because SequenceNumber is int64, so isBadSeq will always return true in - // that case. + // If seqNum == INT64_MAX, seqNum >= getSeqNum() is guaranteed to be + // true because SequenceNumber is int64, so isBadSeq will always return + // true in that case. if (protocolVersionStartsFrom(header.current().ledgerVersion, ProtocolVersion::V_19)) { @@ -1558,16 +1620,14 @@ TransactionFrame::isBadSeq(LedgerHeaderWrapper const& header, } TransactionFrame::ValidationType -TransactionFrame::commonValid(AppConnector& app, - SorobanNetworkConfig const* cfg, - SignatureChecker& signatureChecker, - LedgerSnapshot const& ls, SequenceNumber current, - bool applying, bool chargeFee, - uint64_t lowerBoundCloseTimeOffset, - uint64_t upperBoundCloseTimeOffset, - std::optional sorobanResourceFee, - MutableTransactionResultBase& txResult, - DiagnosticEventManager& diagnosticEvents) const +TransactionFrame::commonValid( + AppConnector& app, SorobanNetworkConfig const* cfg, + SignatureChecker& signatureChecker, LedgerSnapshot const& ls, + SequenceNumber current, bool applying, bool chargeFee, + uint64_t lowerBoundCloseTimeOffset, uint64_t upperBoundCloseTimeOffset, + Hash const& envelopeContentsHash, std::optional sorobanResourceFee, + MutableTransactionResultBase& txResult, + DiagnosticEventManager& diagnosticEvents) const { ZoneScoped; ValidationType res = ValidationType::kInvalid; @@ -1575,8 +1635,8 @@ TransactionFrame::commonValid(AppConnector& app, auto validate = [this, &signatureChecker, applying, lowerBoundCloseTimeOffset, upperBoundCloseTimeOffset, &app, chargeFee, sorobanResourceFee, &txResult, - &diagnosticEvents, ¤t, &res, - &cfg](LedgerSnapshot const& ls) { + &diagnosticEvents, ¤t, &res, &cfg, + &envelopeContentsHash](LedgerSnapshot const& ls) { if (applying && (lowerBoundCloseTimeOffset != 0 || upperBoundCloseTimeOffset != 0)) { @@ -1584,12 +1644,12 @@ TransactionFrame::commonValid(AppConnector& app, "Applying transaction with non-current closeTime"); } - // Get the source account during commonValidPreSeqNum to avoid redundant - // account loading + // Get the source account during commonValidPreSeqNum to avoid + // redundant account loading auto sourceAccount = commonValidPreSeqNum( app, cfg, ls, chargeFee, lowerBoundCloseTimeOffset, - upperBoundCloseTimeOffset, sorobanResourceFee, txResult, - diagnosticEvents); + upperBoundCloseTimeOffset, envelopeContentsHash, sorobanResourceFee, + txResult, diagnosticEvents); if (!sourceAccount) { @@ -1692,9 +1752,9 @@ TransactionFrame::processFeeSeqNum(AbstractLedgerTxn& ltx, if (fee > 0) { fee = std::min(acc.balance, fee); - // Note: TransactionUtil addBalance checks that reserve plus liabilities - // are respected. In this case, we allow it to fall below that since it - // will be caught later in commonValid. + // Note: TransactionUtil addBalance checks that reserve plus + // liabilities are respected. In this case, we allow it to fall + // below that since it will be caught later in commonValid. stellar::addBalance(acc.balance, -fee); header.current().feePool += fee; } @@ -1704,8 +1764,8 @@ TransactionFrame::processFeeSeqNum(AbstractLedgerTxn& ltx, { if (acc.seqNum + 1 != getSeqNum()) { - // this should not happen as the transaction set is sanitized for - // sequence numbers + // this should not happen as the transaction set is sanitized + // for sequence numbers throw std::runtime_error("Unexpected account state"); } acc.seqNum = getSeqNum(); @@ -1791,7 +1851,8 @@ void TransactionFrame::checkValidWithOptionallyChargedFee( AppConnector& app, LedgerSnapshot const& ls, SequenceNumber current, bool chargeFee, uint64_t lowerBoundCloseTimeOffset, - uint64_t upperBoundCloseTimeOffset, MutableTransactionResultBase& txResult, + uint64_t upperBoundCloseTimeOffset, Hash const& envelopeContentsHash, + MutableTransactionResultBase& txResult, DiagnosticEventManager& diagnosticEvents) const { ZoneScoped; @@ -1803,19 +1864,22 @@ TransactionFrame::checkValidWithOptionallyChargedFee( std::optional sorobanResourceFee; SorobanNetworkConfig const* sorobanConfig = nullptr; - if (protocolVersionStartsFrom(ls.getLedgerHeader().current().ledgerVersion, - SOROBAN_PROTOCOL_VERSION) && - isSoroban()) + auto ledgerVersion = ls.getLedgerHeader().current().ledgerVersion; + // Load sorobanConfig for all transactions at protocol >= V20. + if (protocolVersionStartsFrom(ledgerVersion, SOROBAN_PROTOCOL_VERSION)) { sorobanConfig = &app.getLedgerManager().getLastClosedSorobanNetworkConfig(); - sorobanResourceFee = computePreApplySorobanResourceFee( - ls.getLedgerHeader().current().ledgerVersion, *sorobanConfig, - app.getConfig()); + if (isSoroban()) + { + sorobanResourceFee = computePreApplySorobanResourceFee( + ledgerVersion, *sorobanConfig, app.getConfig()); + } } if (commonValid(app, sorobanConfig, signatureChecker, ls, current, false, chargeFee, lowerBoundCloseTimeOffset, - upperBoundCloseTimeOffset, sorobanResourceFee, txResult, + upperBoundCloseTimeOffset, envelopeContentsHash, + sorobanResourceFee, txResult, diagnosticEvents) != ValidationType::kMaybeValid) { return; @@ -1871,16 +1935,17 @@ TransactionFrame::checkValid(AppConnector& app, LedgerSnapshot const& ls, { return MutableTransactionResult::createTxError(txMALFORMED); } - // Setting the fees in this flow is potentially misleading, as these aren't - // the fees that would end up being applied. However, this is what Core - // used to return for a while, and some users may rely on this, so we - // maintain this logic for the time being. + // Setting the fees in this flow is potentially misleading, as these + // aren't the fees that would end up being applied. However, this is + // what Core used to return for a while, and some users may rely on + // this, so we maintain this logic for the time being. int64_t minBaseFee = ls.getLedgerHeader().current().baseFee; auto feeCharged = getFee(ls.getLedgerHeader().current(), minBaseFee, false); auto txResult = MutableTransactionResult::createSuccess(*this, feeCharged); checkValidWithOptionallyChargedFee( app, ls, current, true, lowerBoundCloseTimeOffset, - upperBoundCloseTimeOffset, *txResult, diagnosticEvents); + upperBoundCloseTimeOffset, getContentsHash(), *txResult, + diagnosticEvents); return txResult; } @@ -1933,10 +1998,12 @@ maybeTriggerTestInternalError(TransactionEnvelope const& env) #endif std::unique_ptr -TransactionFrame::commonPreApply( - bool chargeFee, AppConnector& app, AbstractLedgerTxn& ltx, - TransactionMetaBuilder& meta, MutableTransactionResultBase& txResult, - SorobanNetworkConfig const* sorobanConfig) const +TransactionFrame::commonPreApply(bool chargeFee, AppConnector& app, + AbstractLedgerTxn& ltx, + TransactionMetaBuilder& meta, + MutableTransactionResultBase& txResult, + SorobanNetworkConfig const* sorobanConfig, + Hash const& envelopeContentsHash) const { mCachedAccountPreProtocol8.reset(); uint32_t ledgerVersion = ltx.loadHeader().current().ledgerVersion; @@ -1977,9 +2044,10 @@ TransactionFrame::commonPreApply( } LedgerTxn ltxTx(ltx); LedgerSnapshot lsTx(ltxTx); - auto cv = commonValid(app, sorobanConfig, *signatureChecker, lsTx, 0, true, - chargeFee, 0, 0, sorobanResourceFee, txResult, - meta.getDiagnosticEventManager()); + auto cv = + commonValid(app, sorobanConfig, *signatureChecker, lsTx, 0, true, + chargeFee, 0, 0, envelopeContentsHash, sorobanResourceFee, + txResult, meta.getDiagnosticEventManager()); if (cv >= ValidationType::kInvalidUpdateSeqNum) { processSeqNum(ltxTx); @@ -2007,14 +2075,17 @@ TransactionFrame::preParallelApply( MutableTransactionResultBase& resPayload, SorobanNetworkConfig const& sorobanConfig) const { - preParallelApply(true, app, ltx, meta, resPayload, sorobanConfig); + preParallelApply(true, app, ltx, meta, resPayload, sorobanConfig, + getContentsHash()); } void -TransactionFrame::preParallelApply( - bool chargeFee, AppConnector& app, AbstractLedgerTxn& ltx, - TransactionMetaBuilder& meta, MutableTransactionResultBase& txResult, - SorobanNetworkConfig const& sorobanConfig) const +TransactionFrame::preParallelApply(bool chargeFee, AppConnector& app, + AbstractLedgerTxn& ltx, + TransactionMetaBuilder& meta, + MutableTransactionResultBase& txResult, + SorobanNetworkConfig const& sorobanConfig, + Hash const& envelopeContentsHash) const { ZoneScoped; releaseAssert(threadIsMain() || @@ -2024,7 +2095,8 @@ TransactionFrame::preParallelApply( releaseAssertOrThrow(isSoroban()); auto signatureChecker = - commonPreApply(chargeFee, app, ltx, meta, txResult, &sorobanConfig); + commonPreApply(chargeFee, app, ltx, meta, txResult, &sorobanConfig, + envelopeContentsHash); bool ok = signatureChecker != nullptr; if (ok) { @@ -2032,9 +2104,10 @@ TransactionFrame::preParallelApply( auto& opResult = txResult.getOpResultAt(0); - // Pre parallel soroban, OperationFrame::checkValid is called right - // before OperationFrame::doApply, but we do it here instead to - // avoid making OperationFrame::checkValid thread safe. + // 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()); @@ -2044,8 +2117,8 @@ TransactionFrame::preParallelApply( } } - // If validation fails, we check the result code in the parallel step to - // make sure we don't apply the transaction. + // 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()); } catch (std::exception& e) @@ -2371,14 +2444,15 @@ TransactionFrame::apply( bool chargeFee, AppConnector& app, AbstractLedgerTxn& ltx, TransactionMetaBuilder& meta, MutableTransactionResultBase& txResult, std::optional const& sorobanConfig, - Hash const& sorobanBasePrngSeed) const + Hash const& sorobanBasePrngSeed, Hash const& envelopeContentsHash) const { ZoneScoped; try { auto signatureChecker = commonPreApply(chargeFee, app, ltx, meta, txResult, - sorobanConfig ? &sorobanConfig.value() : nullptr); + sorobanConfig ? &sorobanConfig.value() : nullptr, + envelopeContentsHash); bool ok = signatureChecker != nullptr; try { @@ -2429,7 +2503,7 @@ TransactionFrame::apply( Hash const& sorobanBasePrngSeed) const { return apply(true, app, ltx, meta, txResult, sorobanConfig, - sorobanBasePrngSeed); + sorobanBasePrngSeed, getContentsHash()); } void diff --git a/src/transactions/TransactionFrame.h b/src/transactions/TransactionFrame.h index d619dbc970..dd31c62cca 100644 --- a/src/transactions/TransactionFrame.h +++ b/src/transactions/TransactionFrame.h @@ -103,6 +103,7 @@ class TransactionFrame : public TransactionFrameBase LedgerSnapshot const& ls, bool chargeFee, uint64_t lowerBoundCloseTimeOffset, uint64_t upperBoundCloseTimeOffset, + Hash const& envelopeContentsHash, std::optional sorobanResourceFee, MutableTransactionResultBase& txResult, DiagnosticEventManager& diagnosticEvents) const; @@ -117,6 +118,7 @@ class TransactionFrame : public TransactionFrameBase bool applying, bool chargeFee, uint64_t lowerBoundCloseTimeOffset, uint64_t upperBoundCloseTimeOffset, + Hash const& envelopeContentsHash, std::optional sorobanResourceFee, MutableTransactionResultBase& txResult, DiagnosticEventManager& diagnosticEvents) const; @@ -148,6 +150,8 @@ class TransactionFrame : public TransactionFrameBase int64_t refundSorobanFee(AbstractLedgerTxn& ltx, AccountID const& feeSource, MutableTransactionResultBase& txResult) const; void updateSorobanMetrics(AppConnector& app) const; + bool accessesFrozenKey(SorobanNetworkConfig const& cfg) const; + #ifdef BUILD_TESTS public: #endif @@ -236,7 +240,7 @@ class TransactionFrame : public TransactionFrameBase void checkValidWithOptionallyChargedFee( AppConnector& app, LedgerSnapshot const& ls, SequenceNumber current, bool chargeFee, uint64_t lowerBoundCloseTimeOffset, - uint64_t upperBoundCloseTimeOffset, + uint64_t upperBoundCloseTimeOffset, Hash const& envelopeContentsHash, MutableTransactionResultBase& result, DiagnosticEventManager& diagnosticEvents) const; MutableTxResultPtr @@ -278,12 +282,14 @@ class TransactionFrame : public TransactionFrameBase commonPreApply(bool chargeFee, AppConnector& app, AbstractLedgerTxn& ltx, TransactionMetaBuilder& meta, MutableTransactionResultBase& txResult, - SorobanNetworkConfig const* sorobanConfig) const; + SorobanNetworkConfig const* sorobanConfig, + Hash const& envelopeContentsHash) const; void preParallelApply(bool chargeFee, AppConnector& app, AbstractLedgerTxn& ltx, TransactionMetaBuilder& meta, MutableTransactionResultBase& txResult, - SorobanNetworkConfig const& sorobanConfig) const; + SorobanNetworkConfig const& sorobanConfig, + Hash const& envelopeContentsHash) const; void preParallelApply(AppConnector& app, AbstractLedgerTxn& ltx, @@ -304,7 +310,8 @@ class TransactionFrame : public TransactionFrameBase TransactionMetaBuilder& meta, MutableTransactionResultBase& txResult, std::optional const& sorobanConfig, - Hash const& sorobanBasePrngSeed) const; + Hash const& sorobanBasePrngSeed, + Hash const& envelopeContentsHash) const; bool apply(AppConnector& app, AbstractLedgerTxn& ltx, TransactionMetaBuilder& meta, MutableTransactionResultBase& txResult, diff --git a/src/transactions/TransactionUtils.cpp b/src/transactions/TransactionUtils.cpp index 64eb2afe50..0fba73dbbb 100644 --- a/src/transactions/TransactionUtils.cpp +++ b/src/transactions/TransactionUtils.cpp @@ -229,6 +229,36 @@ offerKey(AccountID const& sellerID, uint64_t offerID) return key; } +bool +offerAccessesFrozenKey(OfferEntry const& offer, + SorobanNetworkConfig const& sorobanConfig) +{ + if (!sorobanConfig.hasFrozenKeys()) + { + return false; + } + + // Frozen seller account only matters when at least one side of the offer + // is native (the account entry holds the native balance). + if ((offer.selling.type() == ASSET_TYPE_NATIVE || + offer.buying.type() == ASSET_TYPE_NATIVE) && + sorobanConfig.isKeyFrozen(accountKey(offer.sellerID))) + { + return true; + } + if (offer.selling.type() != ASSET_TYPE_NATIVE && + sorobanConfig.isKeyFrozen(trustlineKey(offer.sellerID, offer.selling))) + { + return true; + } + if (offer.buying.type() != ASSET_TYPE_NATIVE && + sorobanConfig.isKeyFrozen(trustlineKey(offer.sellerID, offer.buying))) + { + return true; + } + return false; +} + LedgerKey dataKey(AccountID const& accountID, std::string const& dataName) { diff --git a/src/transactions/TransactionUtils.h b/src/transactions/TransactionUtils.h index 514d46ddf7..27d264430e 100644 --- a/src/transactions/TransactionUtils.h +++ b/src/transactions/TransactionUtils.h @@ -65,6 +65,8 @@ LedgerKey accountKey(AccountID const& accountID); LedgerKey trustlineKey(AccountID const& accountID, Asset const& asset); LedgerKey trustlineKey(AccountID const& accountID, TrustLineAsset const& asset); LedgerKey offerKey(AccountID const& sellerID, uint64_t offerID); +bool offerAccessesFrozenKey(OfferEntry const& offer, + SorobanNetworkConfig const& sorobanConfig); LedgerKey dataKey(AccountID const& accountID, std::string const& dataName); LedgerKey claimableBalanceKey(ClaimableBalanceID const& balanceID); LedgerKey liquidityPoolKey(PoolID const& poolID); diff --git a/src/transactions/test/FrozenLedgerKeysTests.cpp b/src/transactions/test/FrozenLedgerKeysTests.cpp new file mode 100644 index 0000000000..9d91f36718 --- /dev/null +++ b/src/transactions/test/FrozenLedgerKeysTests.cpp @@ -0,0 +1,2005 @@ +// 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 + +#include "crypto/SHA.h" +#include "ledger/LedgerManager.h" +#include "ledger/LedgerTxn.h" +#include "ledger/LedgerTxnHeader.h" +#include "ledger/NetworkConfig.h" +#include "ledger/TrustLineWrapper.h" +#include "ledger/test/LedgerTestUtils.h" +#include "main/Application.h" +#include "test/Catch2.h" +#include "test/TestAccount.h" +#include "test/TestExceptions.h" +#include "test/TestUtils.h" +#include "test/TxTests.h" +#include "test/test.h" +#include "transactions/SponsorshipUtils.h" +#include "transactions/TransactionUtils.h" +#include "util/ProtocolVersion.h" +#include "util/XDROperators.h" +#include "util/types.h" +#include +#include +#include + +namespace stellar +{ +namespace +{ +using namespace stellar::txtest; +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION +EncodedLedgerKey +encodeLedgerKey(LedgerKey const& key) +{ + return xdr::xdr_to_opaque(key); +} + +void +freezeKey(Application& app, LedgerKey const& key) +{ + modifySorobanNetworkConfig(app, [&](SorobanNetworkConfig& cfg) { + cfg.mFrozenLedgerKeys.insert(key); + }); +} + +void +unfreezeKey(Application& app, LedgerKey const& key) +{ + modifySorobanNetworkConfig(app, [&](SorobanNetworkConfig& cfg) { + cfg.mFrozenLedgerKeys.erase(key); + }); +} + +void +freezeAndUnfreezeKeys(Application& app, std::vector const& toFreeze, + std::vector const& toUnfreeze) +{ + modifySorobanNetworkConfig(app, [&](SorobanNetworkConfig& cfg) { + cfg.mFrozenLedgerKeys.insert(toFreeze.begin(), toFreeze.end()); + for (auto const& k : toUnfreeze) + { + cfg.mFrozenLedgerKeys.erase(k); + } + }); +} + +void +bypassTxHash(Application& app, Hash const& txHash) +{ + modifySorobanNetworkConfig(app, [&](SorobanNetworkConfig& cfg) { + cfg.mFreezeBypassTxs.insert(txHash); + }); +} + +void +unbypassTxHash(Application& app, Hash const& txHash) +{ + modifySorobanNetworkConfig(app, [&](SorobanNetworkConfig& cfg) { + cfg.mFreezeBypassTxs.erase(txHash); + }); +} + +void +bypassAndUnbypassTxHashes(Application& app, std::vector const& toBypass, + std::vector const& toUnbypass) +{ + modifySorobanNetworkConfig(app, [&](SorobanNetworkConfig& cfg) { + cfg.mFreezeBypassTxs.insert(toBypass.begin(), toBypass.end()); + for (auto const& h : toUnbypass) + { + cfg.mFreezeBypassTxs.erase(h); + } + }); +} + +UnorderedSet +loadFrozenKeysFromLedger(Application& app) +{ + LedgerSnapshot ls(app); + auto configKey = configSettingKey(CONFIG_SETTING_FROZEN_LEDGER_KEYS); + auto entry = ls.load(configKey); + REQUIRE(entry); + + auto const& frozenKeys = + entry.current().data.configSetting().frozenLedgerKeys().keys; + UnorderedSet result; + for (auto const& encodedKey : frozenKeys) + { + LedgerKey lk; + xdr::xdr_from_opaque(encodedKey, lk); + result.insert(lk); + } + + // Verify that we cache the correct frozen key set. + auto const& sorobanConfig = + app.getLedgerManager().getLastClosedSorobanNetworkConfig(); + REQUIRE(sorobanConfig.frozenLedgerKeys() == result); + return result; +} + +UnorderedSet +loadFreezeBypassTxsFromLedger(Application& app) +{ + LedgerSnapshot ls(app); + auto configKey = configSettingKey(CONFIG_SETTING_FREEZE_BYPASS_TXS); + auto entry = ls.load(configKey); + REQUIRE(entry); + + auto const& bypassTxs = + entry.current().data.configSetting().freezeBypassTxs().txHashes; + UnorderedSet result; + for (auto const& txHash : bypassTxs) + { + result.insert(txHash); + } + + // Verify that we cache the correct bypass tx hash set. + auto const& sorobanConfig = + app.getLedgerManager().getLastClosedSorobanNetworkConfig(); + REQUIRE(sorobanConfig.freezeBypassTxs() == result); + return result; +} + +TEST_CASE("frozen ledger keys config setting does not exist prior to p26", + "[frozenledgerkeys][upgrades]") +{ + VirtualClock clock; + auto cfg = getTestConfig(); + cfg.LEDGER_PROTOCOL_VERSION = static_cast(ProtocolVersion::V_25); + cfg.TESTING_UPGRADE_LEDGER_PROTOCOL_VERSION = + static_cast(ProtocolVersion::V_25); + auto app = createTestApplication(clock, cfg); + auto root = app->getRoot(); + LedgerSnapshot ls(*app); + auto configKey = configSettingKey(CONFIG_SETTING_FROZEN_LEDGER_KEYS); + auto entry = ls.load(configKey); + REQUIRE(!entry); +} + +TEST_CASE("freeze bypass txs config setting does not exist prior to p26", + "[frozenledgerkeys][upgrades]") +{ + VirtualClock clock; + auto cfg = getTestConfig(); + cfg.LEDGER_PROTOCOL_VERSION = static_cast(ProtocolVersion::V_25); + cfg.TESTING_UPGRADE_LEDGER_PROTOCOL_VERSION = + static_cast(ProtocolVersion::V_25); + auto app = createTestApplication(clock, cfg); + auto root = app->getRoot(); + LedgerSnapshot ls(*app); + auto configKey = configSettingKey(CONFIG_SETTING_FREEZE_BYPASS_TXS); + auto entry = ls.load(configKey); + REQUIRE(!entry); +} + +TEST_CASE_VERSIONS("frozen ledger keys config setting upgrades", + "[frozenledgerkeys][upgrades]") +{ + auto cfg = getTestConfig(); + + VirtualClock clock; + auto app = createTestApplication(clock, cfg); + + for_versions_from(26, *app, [&] { + auto const protocolVersion = app->getLedgerManager() + .getLastClosedLedgerHeader() + .header.ledgerVersion; + + auto initFrozenKeys = loadFrozenKeysFromLedger(*app); + // Frozen keys are initially empty. + REQUIRE(initFrozenKeys.empty()); + + // CONFIG_SETTING_FROZEN_LEDGER_KEYS cannot be upgraded directly; + // only the delta mechanism is allowed. + REQUIRE(SorobanNetworkConfig::isNonUpgradeableConfigSettingEntry( + CONFIG_SETTING_FROZEN_LEDGER_KEYS)); + + auto makeAccountKey = [&](uint8_t id) { + AccountID accountID{}; + accountID.ed25519()[0] = id; + return accountKey(accountID); + }; + + SECTION("frozen keys delta validation accepts valid key types") + { + // Create a delta with valid key types: ACCOUNT, TRUSTLINE, + // CONTRACT_DATA, CONTRACT_CODE + ConfigSettingEntry deltaEntry; + deltaEntry.configSettingID(CONFIG_SETTING_FROZEN_LEDGER_KEYS_DELTA); + + auto accKey = makeAccountKey(1); + Asset asset{}; + asset.type(ASSET_TYPE_CREDIT_ALPHANUM4); + asset.alphaNum4().issuer = makeAccountKey(2).account().accountID; + strToAssetCode(asset.alphaNum4().assetCode, "USD"); + auto tlKey = trustlineKey(accKey.account().accountID, asset); + + SCAddress contractAddr; + contractAddr.type(SC_ADDRESS_TYPE_CONTRACT); + contractAddr.contractId() = sha256("test_contract"); + SCVal scKey; + scKey.type(SCV_SYMBOL); + scKey.sym() = "key"; + auto cdKey = contractDataKey(contractAddr, scKey, + ContractDataDurability::PERSISTENT); + auto ccKey = contractCodeKey(sha256("test_code")); + + deltaEntry.frozenLedgerKeysDelta().keysToFreeze.emplace_back( + encodeLedgerKey(accKey)); + deltaEntry.frozenLedgerKeysDelta().keysToFreeze.emplace_back( + encodeLedgerKey(tlKey)); + deltaEntry.frozenLedgerKeysDelta().keysToFreeze.emplace_back( + encodeLedgerKey(cdKey)); + deltaEntry.frozenLedgerKeysDelta().keysToFreeze.emplace_back( + encodeLedgerKey(ccKey)); + + REQUIRE(SorobanNetworkConfig::isValidConfigSettingEntry( + deltaEntry, protocolVersion)); + } + + SECTION("frozen keys delta validation rejects liquidity pool share " + "trustlines") + { + ConfigSettingEntry deltaEntry; + deltaEntry.configSettingID(CONFIG_SETTING_FROZEN_LEDGER_KEYS_DELTA); + + auto accKey = makeAccountKey(1); + PoolID poolID = sha256("test_pool"); + auto poolShareTlKey = + poolShareTrustLineKey(accKey.account().accountID, poolID); + + deltaEntry.frozenLedgerKeysDelta().keysToFreeze.emplace_back( + encodeLedgerKey(poolShareTlKey)); + + REQUIRE(!SorobanNetworkConfig::isValidConfigSettingEntry( + deltaEntry, protocolVersion)); + } + + SECTION("frozen keys delta validation rejects issuer trustline keys") + { + ConfigSettingEntry deltaEntry; + deltaEntry.configSettingID(CONFIG_SETTING_FROZEN_LEDGER_KEYS_DELTA); + + auto accKey = makeAccountKey(1); + Asset asset{}; + asset.type(ASSET_TYPE_CREDIT_ALPHANUM4); + asset.alphaNum4().issuer = accKey.account().accountID; + strToAssetCode(asset.alphaNum4().assetCode, "USD"); + auto issuerTlKey = trustlineKey(accKey.account().accountID, asset); + + deltaEntry.frozenLedgerKeysDelta().keysToFreeze.emplace_back( + encodeLedgerKey(issuerTlKey)); + + REQUIRE(!SorobanNetworkConfig::isValidConfigSettingEntry( + deltaEntry, protocolVersion)); + } + + SECTION("frozen keys delta validation rejects invalid key types") + { + for (auto t : xdr::xdr_traits::enum_values()) + { + auto type = static_cast(t); + if (type == ACCOUNT || type == TRUSTLINE || + type == CONTRACT_DATA || type == CONTRACT_CODE) + { + continue; + } + + ConfigSettingEntry deltaEntry; + deltaEntry.configSettingID( + CONFIG_SETTING_FROZEN_LEDGER_KEYS_DELTA); + + LedgerKey lk; + lk.type(type); + deltaEntry.frozenLedgerKeysDelta().keysToFreeze.emplace_back( + encodeLedgerKey(lk)); + + REQUIRE(!SorobanNetworkConfig::isValidConfigSettingEntry( + deltaEntry, protocolVersion)); + } + } + + SECTION("frozen keys delta validation rejects malformed XDR") + { + ConfigSettingEntry deltaEntry; + deltaEntry.configSettingID(CONFIG_SETTING_FROZEN_LEDGER_KEYS_DELTA); + + LedgerKey lk; + lk.type(ACCOUNT); + deltaEntry.frozenLedgerKeysDelta().keysToFreeze.emplace_back( + encodeLedgerKey(lk)); + REQUIRE(SorobanNetworkConfig::isValidConfigSettingEntry( + deltaEntry, protocolVersion)); + // Corrupt the XDR by changing some bytes in the encoded key. + deltaEntry.frozenLedgerKeysDelta().keysToFreeze[0][0] = 0xFF; + deltaEntry.frozenLedgerKeysDelta().keysToFreeze[0][3] = 0xFF; + REQUIRE(!SorobanNetworkConfig::isValidConfigSettingEntry( + deltaEntry, protocolVersion)); + } + + SECTION("adds and remove frozen keys") + { + auto k1 = makeAccountKey(1); + freezeKey(*app, k1); + + // Add a key + auto frozenKeys = loadFrozenKeysFromLedger(*app); + REQUIRE(frozenKeys.size() == 1); + REQUIRE(frozenKeys.count(k1) == 1); + + // Add another key + auto k2 = makeAccountKey(2); + freezeKey(*app, k2); + + frozenKeys = loadFrozenKeysFromLedger(*app); + REQUIRE(frozenKeys.size() == 2); + REQUIRE(frozenKeys.count(k1) == 1); + REQUIRE(frozenKeys.count(k2) == 1); + + // Remove a key + unfreezeKey(*app, k1); + frozenKeys = loadFrozenKeysFromLedger(*app); + REQUIRE(frozenKeys.size() == 1); + REQUIRE(frozenKeys.count(k2) == 1); + + // Add and remove in a single upgrade + freezeAndUnfreezeKeys(*app, {k1}, {k2}); + frozenKeys = loadFrozenKeysFromLedger(*app); + REQUIRE(frozenKeys.size() == 1); + REQUIRE(frozenKeys.count(k1) == 1); + + // Add multiple keys at once + auto k3 = makeAccountKey(3); + auto k4 = makeAccountKey(4); + freezeAndUnfreezeKeys(*app, {k2, k3, k4}, {}); + frozenKeys = loadFrozenKeysFromLedger(*app); + REQUIRE(frozenKeys.size() == 4); + REQUIRE(frozenKeys.count(k1) == 1); + REQUIRE(frozenKeys.count(k2) == 1); + REQUIRE(frozenKeys.count(k3) == 1); + REQUIRE(frozenKeys.count(k4) == 1); + + // Remove multiple keys at once + freezeAndUnfreezeKeys(*app, {}, {k1, k3}); + frozenKeys = loadFrozenKeysFromLedger(*app); + REQUIRE(frozenKeys.size() == 2); + REQUIRE(frozenKeys.count(k2) == 1); + REQUIRE(frozenKeys.count(k4) == 1); + + // Removing non-existent key is no-op + unfreezeKey(*app, k1); + auto frozenKeys2 = loadFrozenKeysFromLedger(*app); + REQUIRE(frozenKeys2 == frozenKeys); + + // Adding a key that's already frozen is no-op + freezeKey(*app, k2); + frozenKeys2 = loadFrozenKeysFromLedger(*app); + REQUIRE(frozenKeys2 == frozenKeys); + } + + SECTION("same key in both freeze and unfreeze results in key not " + "frozen") + { + auto k1 = makeAccountKey(1); + freezeKey(*app, k1); + auto frozenKeys = loadFrozenKeysFromLedger(*app); + REQUIRE(frozenKeys.size() == 1); + REQUIRE(frozenKeys.count(k1) == 1); + + // Apply a delta with the same key in both lists. + // Implementation inserts first, then removes, so the key + // ends up not frozen. + freezeAndUnfreezeKeys(*app, {k1}, {k1}); + frozenKeys = loadFrozenKeysFromLedger(*app); + REQUIRE(frozenKeys.empty()); + } + }); +} + +TEST_CASE_VERSIONS("freeze bypass txs config setting upgrades", + "[frozenledgerkeys][upgrades]") +{ + auto cfg = getTestConfig(); + + VirtualClock clock; + auto app = createTestApplication(clock, cfg); + + for_versions_from(26, *app, [&] { + auto const protocolVersion = app->getLedgerManager() + .getLastClosedLedgerHeader() + .header.ledgerVersion; + + auto bypassTxs = loadFreezeBypassTxsFromLedger(*app); + // Bypass tx hashes are initially empty. + REQUIRE(bypassTxs.empty()); + + // CONFIG_SETTING_FREEZE_BYPASS_TXS cannot be upgraded directly; + // only the delta mechanism is allowed. + REQUIRE(SorobanNetworkConfig::isNonUpgradeableConfigSettingEntry( + CONFIG_SETTING_FREEZE_BYPASS_TXS)); + + auto makeHash = [&](uint8_t id) { + Hash h{}; + h[0] = id; + return h; + }; + + SECTION("freeze bypass txs delta validation accepts hashes") + { + ConfigSettingEntry deltaEntry; + deltaEntry.configSettingID(CONFIG_SETTING_FREEZE_BYPASS_TXS_DELTA); + + deltaEntry.freezeBypassTxsDelta().addTxs.emplace_back(makeHash(1)); + deltaEntry.freezeBypassTxsDelta().removeTxs.emplace_back( + makeHash(2)); + + REQUIRE(SorobanNetworkConfig::isValidConfigSettingEntry( + deltaEntry, protocolVersion)); + } + + SECTION("adds and removes bypass tx hashes") + { + auto h1 = makeHash(1); + bypassTxHash(*app, h1); + + // Add a hash + bypassTxs = loadFreezeBypassTxsFromLedger(*app); + REQUIRE(bypassTxs.size() == 1); + REQUIRE(bypassTxs.count(h1) == 1); + + // Add another hash + auto h2 = makeHash(2); + bypassTxHash(*app, h2); + + bypassTxs = loadFreezeBypassTxsFromLedger(*app); + REQUIRE(bypassTxs.size() == 2); + REQUIRE(bypassTxs.count(h1) == 1); + REQUIRE(bypassTxs.count(h2) == 1); + + // Remove a hash + unbypassTxHash(*app, h1); + bypassTxs = loadFreezeBypassTxsFromLedger(*app); + REQUIRE(bypassTxs.size() == 1); + REQUIRE(bypassTxs.count(h2) == 1); + + // Add and remove in a single upgrade + bypassAndUnbypassTxHashes(*app, {h1}, {h2}); + bypassTxs = loadFreezeBypassTxsFromLedger(*app); + REQUIRE(bypassTxs.size() == 1); + REQUIRE(bypassTxs.count(h1) == 1); + + // Add multiple hashes at once + auto h3 = makeHash(3); + auto h4 = makeHash(4); + bypassAndUnbypassTxHashes(*app, {h2, h3, h4}, {}); + bypassTxs = loadFreezeBypassTxsFromLedger(*app); + REQUIRE(bypassTxs.size() == 4); + REQUIRE(bypassTxs.count(h1) == 1); + REQUIRE(bypassTxs.count(h2) == 1); + REQUIRE(bypassTxs.count(h3) == 1); + REQUIRE(bypassTxs.count(h4) == 1); + + // Remove multiple hashes at once + bypassAndUnbypassTxHashes(*app, {}, {h1, h3}); + bypassTxs = loadFreezeBypassTxsFromLedger(*app); + REQUIRE(bypassTxs.size() == 2); + REQUIRE(bypassTxs.count(h2) == 1); + REQUIRE(bypassTxs.count(h4) == 1); + + // Removing non-existent hash is no-op + unbypassTxHash(*app, h1); + auto bypassTxs2 = loadFreezeBypassTxsFromLedger(*app); + REQUIRE(bypassTxs2 == bypassTxs); + + // Adding an existing hash is no-op + bypassTxHash(*app, h2); + bypassTxs2 = loadFreezeBypassTxsFromLedger(*app); + REQUIRE(bypassTxs2 == bypassTxs); + } + }); +} + +TEST_CASE("freeze bypass tx hash allows frozen key access at validation time", + "[frozenledgerkeys][tx][bypass]") +{ + VirtualClock clock; + auto cfg = getTestConfig(); + auto app = createTestApplication(clock, cfg); + auto root = app->getRoot(); + auto const& lm = app->getLedgerManager(); + + auto a1 = + root->create("A1", lm.getLastMinBalance(10) + 10 * lm.getLastTxFee()); + auto a2 = + root->create("A2", lm.getLastMinBalance(10) + 10 * lm.getLastTxFee()); + auto feeSource = root->create("feeSource", lm.getLastMinBalance(10) + + 10 * lm.getLastTxFee()); + + enum class TxWrapperKind + { + REGULAR, + FEE_BUMP + }; + + auto makeTx = [&](TxWrapperKind txWrapperKind) { + std::vector ops = {payment(a2.getPublicKey(), 100)}; + auto innerTx = transactionFromOperations(*app, a1.getSecretKey(), + a1.nextSequenceNumber(), ops); + if (txWrapperKind == TxWrapperKind::REGULAR) + { + return std::make_pair(TransactionTestFramePtr(innerTx), + innerTx->getContentsHash()); + } + + auto tx = feeBump(*app, feeSource, innerTx, 10000); + return std::make_pair(tx, tx->getContentsHash()); + }; + + auto checkFrozen = [&](TransactionTestFramePtr& tx, + bool expectInnerFrozenResult) { + LedgerSnapshot ls(*app); + auto result = tx->checkValid(app->getAppConnector(), ls, 0, 0, 0); + REQUIRE(!result->isSuccess()); + if (expectInnerFrozenResult) + { + REQUIRE(result->getResultCode() == txFEE_BUMP_INNER_FAILED); + auto const& fbRes = result->getXDR(); + auto const& innerRes = fbRes.result.innerResultPair().result; + REQUIRE(innerRes.result.code() == txFROZEN_KEY_ACCESSED); + } + else + { + REQUIRE(result->getResultCode() == txFROZEN_KEY_ACCESSED); + } + }; + + auto checkValid = [&](TransactionTestFramePtr& tx) { + LedgerSnapshot ls(*app); + auto result = tx->checkValid(app->getAppConnector(), ls, 0, 0, 0); + REQUIRE(result->isSuccess()); + }; + + auto checkBypassScenario = [&](TxWrapperKind txWrapperKind, + std::function freezeKeyFn, + bool expectInnerFrozenResult) { + freezeKeyFn(); + auto [tx, bypassHash] = makeTx(txWrapperKind); + checkFrozen(tx, expectInnerFrozenResult); + + bypassTxHash(*app, bypassHash); + checkValid(tx); + }; + + SECTION("bypass frozen tx source account in regular tx") + { + checkBypassScenario( + TxWrapperKind::REGULAR, + [&] { freezeKey(*app, accountKey(a1.getPublicKey())); }, false); + } + + SECTION("bypass frozen inner tx source account in fee bump") + { + checkBypassScenario( + TxWrapperKind::FEE_BUMP, + [&] { freezeKey(*app, accountKey(a1.getPublicKey())); }, true); + } + + SECTION("bypass frozen key accessed by operation in regular tx") + { + checkBypassScenario( + TxWrapperKind::REGULAR, + [&] { freezeKey(*app, accountKey(a2.getPublicKey())); }, false); + } + + SECTION("bypass frozen key accessed by operation in fee bump inner tx") + { + checkBypassScenario( + TxWrapperKind::FEE_BUMP, + [&] { freezeKey(*app, accountKey(a2.getPublicKey())); }, true); + } + + SECTION("bypass frozen fee bump source account") + { + checkBypassScenario( + TxWrapperKind::FEE_BUMP, + [&] { freezeKey(*app, accountKey(feeSource.getPublicKey())); }, + false); + } + + SECTION("inner tx hash does not bypass frozen fee bump source") + { + freezeKey(*app, accountKey(feeSource.getPublicKey())); + + // Build the inner tx explicitly so we can capture its contents + // hash separately from the fee bump contents hash. + std::vector ops = {payment(a2.getPublicKey(), 100)}; + auto innerTx = transactionFromOperations(*app, a1.getSecretKey(), + a1.nextSequenceNumber(), ops); + auto innerHash = innerTx->getContentsHash(); + + auto tx = feeBump(*app, feeSource, innerTx, 10000); + auto feeBumpHash = tx->getContentsHash(); + REQUIRE(innerHash != feeBumpHash); + + // Bypass only the inner tx hash -- fee bump source is still + // frozen and the fee bump contents hash is not bypassed. + bypassTxHash(*app, innerHash); + checkFrozen(tx, false); + + // Now bypass the actual fee bump hash -- should pass. + bypassTxHash(*app, feeBumpHash); + checkValid(tx); + } +} + +TEST_CASE("frozen ledger keys in Soroban footprint", + "[frozenledgerkeys][tx][soroban]") +{ + VirtualClock clock; + auto cfg = getTestConfig(); + auto app = createTestApplication(clock, cfg); + overrideSorobanNetworkConfigForTest(*app); + auto root = app->getRoot(); + + auto a1 = root->create("A1", app->getLedgerManager().getLastMinBalance(2) + + 10'000'000); + + std::unordered_set const sorobanFootprintTypes = { + CONTRACT_DATA, CONTRACT_CODE, ACCOUNT, TRUSTLINE}; + + auto isFreezableSorobanKey = [](LedgerKey const& key) { + if (key.type() != TRUSTLINE) + { + return true; + } + + auto const& tl = key.trustLine(); + return tl.asset.type() != ASSET_TYPE_POOL_SHARE && + !isIssuer(tl.accountID, tl.asset); + }; + + for (int i = 0; i < 10; ++i) + { + INFO("iteration " << i); + + UnorderedSet seenKeys; + auto readOnly = LedgerTestUtils::generateValidUniqueLedgerKeysWithTypes( + sorobanFootprintTypes, 20, seenKeys); + auto readWrite = + LedgerTestUtils::generateValidUniqueLedgerKeysWithTypes( + sorobanFootprintTypes, 20, seenKeys); + + std::vector freezableKeys; + freezableKeys.reserve(readOnly.size() + readWrite.size()); + + for (auto const& key : readOnly) + { + if (isFreezableSorobanKey(key)) + { + freezableKeys.emplace_back(key); + } + } + + for (auto const& key : readWrite) + { + if (isFreezableSorobanKey(key)) + { + freezableKeys.emplace_back(key); + } + } + + REQUIRE(!freezableKeys.empty()); + stellar::uniform_int_distribution dist(0, freezableKeys.size() - + 1); + auto frozenKey = freezableKeys[dist(Catch::rng())]; + freezeKey(*app, frozenKey); + + SorobanResources resources; + resources.instructions = 800'000; + resources.diskReadBytes = 1000; + resources.writeBytes = 1000; + resources.footprint.readOnly.insert(resources.footprint.readOnly.end(), + readOnly.begin(), readOnly.end()); + resources.footprint.readWrite.insert( + resources.footprint.readWrite.end(), readWrite.begin(), + readWrite.end()); + + auto tx = createUploadWasmTx(*app, a1, 1000, DEFAULT_TEST_RESOURCE_FEE, + resources); + + LedgerSnapshot ls(*app); + auto result = tx->checkValid(app->getAppConnector(), ls, 0, 0, 0); + REQUIRE(!result->isSuccess()); + REQUIRE(result->getResultCode() == txFROZEN_KEY_ACCESSED); + + unfreezeKey(*app, frozenKey); + } +} + +TEST_CASE("source account frozen", "[frozenledgerkeys][tx]") +{ + VirtualClock clock; + auto cfg = getTestConfig(); + auto app = createTestApplication(clock, cfg); + auto root = app->getRoot(); + auto const& lm = app->getLedgerManager(); + + auto a1 = + root->create("A1", lm.getLastMinBalance(10) + 10 * lm.getLastTxFee()); + auto a2 = + root->create("A2", lm.getLastMinBalance(10) + 10 * lm.getLastTxFee()); + + auto checkTx = [&](TransactionTestFramePtr& tx) { + LedgerSnapshot ls(*app); + auto result = tx->checkValid(app->getAppConnector(), ls, 0, 0, 0); + REQUIRE(!result->isSuccess()); + REQUIRE(result->getResultCode() == txFROZEN_KEY_ACCESSED); + }; + + SECTION("tx source account frozen") + { + auto a1Key = accountKey(a1.getPublicKey()); + freezeKey(*app, a1Key); + + auto tx = transactionFromOperations(*app, a1.getSecretKey(), + a1.nextSequenceNumber(), + {payment(a2.getPublicKey(), 100)}); + checkTx(tx); + } + + SECTION("fee bump source account frozen") + { + auto feeSource = root->create("feeSource", lm.getLastMinBalance(10) + + 10 * lm.getLastTxFee()); + auto feeSrcKey = accountKey(feeSource.getPublicKey()); + freezeKey(*app, feeSrcKey); + auto innerTx = transactionFromOperations( + *app, a1.getSecretKey(), a1.nextSequenceNumber(), + {payment(a2.getPublicKey(), 100)}); + auto feeBumpTx = feeBump(*app, feeSource, innerTx, 10000); + checkTx(feeBumpTx); + } + + SECTION("op source account frozen") + { + auto opSource = root->create("opSource", lm.getLastMinBalance(10) + + 10 * lm.getLastTxFee()); + auto opSrcKey = accountKey(opSource.getPublicKey()); + freezeKey(*app, opSrcKey); + + auto payOp = payment(a2.getPublicKey(), 100); + payOp.sourceAccount.activate() = + toMuxedAccount(opSource.getPublicKey()); + + auto tx = transactionFromOperations(*app, a1.getSecretKey(), + a1.nextSequenceNumber(), {payOp}); + tx->addSignature(opSource.getSecretKey()); + checkTx(tx); + } + + SECTION("one of multiple ops source account frozen") + { + auto opSource1 = root->create("opSource1", lm.getLastMinBalance(10) + + 10 * lm.getLastTxFee()); + auto opSrcKey1 = accountKey(opSource1.getPublicKey()); + auto opSource2 = root->create("opSource2", lm.getLastMinBalance(10) + + 10 * lm.getLastTxFee()); + auto opSrcKey2 = accountKey(opSource2.getPublicKey()); + freezeKey(*app, opSrcKey2); + + auto op1 = payment(a2.getPublicKey(), 100); + op1.sourceAccount.activate() = toMuxedAccount(opSource1.getPublicKey()); + auto op2 = payment(a2.getPublicKey(), 100); + auto op3 = payment(a2.getPublicKey(), 100); + op3.sourceAccount.activate() = toMuxedAccount(opSource2.getPublicKey()); + + auto tx = transactionFromOperations( + *app, a1.getSecretKey(), a1.nextSequenceNumber(), {op1, op2, op3}); + tx->addSignature(opSource1.getSecretKey()); + + checkTx(tx); + } + + SECTION("tx source AND destination both frozen") + { + auto a1Key = accountKey(a1.getPublicKey()); + auto a2Key = accountKey(a2.getPublicKey()); + freezeKey(*app, a1Key); + freezeKey(*app, a2Key); + + auto tx = transactionFromOperations(*app, a1.getSecretKey(), + a1.nextSequenceNumber(), + {payment(a2.getPublicKey(), 100)}); + checkTx(tx); + } + + SECTION("tx source frozen via muxed account ID") + { + auto a1Key = accountKey(a1.getPublicKey()); + freezeKey(*app, a1Key); + + // Build a tx envelope with a muxed source account (includes a + // memo id). The frozen key is keyed on the underlying ed25519 + // account, so it must still be detected. + auto muxedSrc = toMuxedAccount(a1.getPublicKey(), 12345); + auto payOp = payment(a2.getPublicKey(), 100); + TransactionEnvelope env(ENVELOPE_TYPE_TX); + env.v1().tx.sourceAccount = muxedSrc; + env.v1().tx.fee = 100; + env.v1().tx.seqNum = a1.nextSequenceNumber(); + env.v1().tx.operations.emplace_back(payOp); + + auto tx = TransactionTestFrame::fromTxFrame( + TransactionFrameBase::makeTransactionFromWire(app->getNetworkID(), + env)); + tx->addSignature(a1.getSecretKey()); + checkTx(tx); + } + + SECTION("op source frozen via muxed account ID") + { + auto opSource = root->create("opSourceMux", lm.getLastMinBalance(10) + + 10 * lm.getLastTxFee()); + auto opSrcKey = accountKey(opSource.getPublicKey()); + freezeKey(*app, opSrcKey); + + auto payOp = payment(a2.getPublicKey(), 100); + payOp.sourceAccount.activate() = + toMuxedAccount(opSource.getPublicKey(), 99999); + + auto tx = transactionFromOperations(*app, a1.getSecretKey(), + a1.nextSequenceNumber(), {payOp}); + tx->addSignature(opSource.getSecretKey()); + checkTx(tx); + } + + SECTION("unfreeze restores tx validity") + { + auto a1Key = accountKey(a1.getPublicKey()); + freezeKey(*app, a1Key); + + auto tx = transactionFromOperations(*app, a1.getSecretKey(), + a1.nextSequenceNumber(), + {payment(a2.getPublicKey(), 100)}); + checkTx(tx); + + unfreezeKey(*app, a1Key); + + LedgerSnapshot ls(*app); + auto result = tx->checkValid(app->getAppConnector(), ls, 0, 0, 0); + REQUIRE(result->isSuccess()); + } +} + +TEST_CASE("source trustline frozen", "[frozenledgerkeys][tx]") +{ + VirtualClock clock; + auto cfg = getTestConfig(); + auto app = createTestApplication(clock, cfg); + auto root = app->getRoot(); + auto const& lm = app->getLedgerManager(); + + auto issuer = root->create("issuer", lm.getLastMinBalance(10) + + 10 * lm.getLastTxFee()); + auto a1 = + root->create("A1", lm.getLastMinBalance(10) + 10 * lm.getLastTxFee()); + auto a2 = + root->create("A2", lm.getLastMinBalance(10) + 10 * lm.getLastTxFee()); + + auto usd = makeAsset(issuer, "USD"); + auto eur = makeAsset(issuer, "EUR"); + + a1.changeTrust(usd, INT64_MAX); + a1.changeTrust(eur, INT64_MAX); + a2.changeTrust(usd, INT64_MAX); + a2.changeTrust(eur, INT64_MAX); + issuer.pay(a1, usd, 10000); + issuer.pay(a1, eur, 10000); + issuer.pay(a2, usd, 10000); + issuer.pay(a2, eur, 10000); + + auto checkAccessesFrozenKey = [&](Operation const& op) { + auto tx = transactionFromOperations(*app, a1.getSecretKey(), + a1.nextSequenceNumber(), {op}); + LedgerSnapshot ls(*app); + auto result = tx->checkValid(app->getAppConnector(), ls, 0, 0, 0); + REQUIRE(!result->isSuccess()); + REQUIRE(result->getResultCode() == txFROZEN_KEY_ACCESSED); + }; + + auto a1UsdTL = trustlineKey(a1.getPublicKey(), usd); + auto a1EurTL = trustlineKey(a1.getPublicKey(), eur); + + SECTION("PaymentOp source trustline frozen") + { + freezeKey(*app, a1UsdTL); + checkAccessesFrozenKey(payment(a2.getPublicKey(), usd, 100)); + } + + SECTION("PathPaymentStrictReceive sendAsset trustline frozen") + { + freezeKey(*app, a1UsdTL); + checkAccessesFrozenKey( + pathPayment(a2.getPublicKey(), usd, 100, usd, 100, {})); + } + + SECTION("PathPaymentStrictSend sendAsset trustline frozen") + { + freezeKey(*app, a1UsdTL); + checkAccessesFrozenKey( + pathPaymentStrictSend(a2.getPublicKey(), usd, 100, usd, 90, {})); + } + + SECTION("ManageSellOffer selling trustline frozen") + { + freezeKey(*app, a1UsdTL); + checkAccessesFrozenKey(manageOffer(0, usd, eur, Price{1, 1}, 100)); + } + + SECTION("ManageSellOffer buying trustline frozen") + { + freezeKey(*app, a1EurTL); + checkAccessesFrozenKey(manageOffer(0, usd, eur, Price{1, 1}, 100)); + } + + SECTION("ManageBuyOffer selling trustline frozen") + { + freezeKey(*app, a1UsdTL); + checkAccessesFrozenKey(manageBuyOffer(0, usd, eur, Price{1, 1}, 100)); + } + + SECTION("ManageBuyOffer buying trustline frozen") + { + freezeKey(*app, a1EurTL); + checkAccessesFrozenKey(manageBuyOffer(0, usd, eur, Price{1, 1}, 100)); + } + + SECTION("CreatePassiveSellOffer selling trustline frozen") + { + freezeKey(*app, a1UsdTL); + checkAccessesFrozenKey(createPassiveOffer(usd, eur, Price{1, 1}, 100)); + } + + SECTION("CreatePassiveSellOffer buying trustline frozen") + { + freezeKey(*app, a1EurTL); + checkAccessesFrozenKey(createPassiveOffer(usd, eur, Price{1, 1}, 100)); + } + + SECTION("ChangeTrustOp trustline frozen") + { + freezeKey(*app, a1UsdTL); + checkAccessesFrozenKey(changeTrust(usd, INT64_MAX - 1)); + } + SECTION("CreateClaimableBalance source trustline frozen") + { + freezeKey(*app, a1UsdTL); + + Claimant claimant; + claimant.v0().destination = a2.getPublicKey(); + claimant.v0().predicate.type(CLAIM_PREDICATE_UNCONDITIONAL); + + checkAccessesFrozenKey(createClaimableBalance(usd, 100, {claimant})); + } +} + +TEST_CASE("operation destination frozen", "[frozenledgerkeys][tx]") +{ + VirtualClock clock; + auto cfg = getTestConfig(); + auto app = createTestApplication(clock, cfg); + auto root = app->getRoot(); + auto const& lm = app->getLedgerManager(); + + auto issuer = root->create("issuer", lm.getLastMinBalance(10) + + 10 * lm.getLastTxFee()); + auto a1 = + root->create("A1", lm.getLastMinBalance(10) + 10 * lm.getLastTxFee()); + auto a2 = + root->create("A2", lm.getLastMinBalance(10) + 10 * lm.getLastTxFee()); + + auto usd = makeAsset(issuer, "USD"); + auto xlm = makeNativeAsset(); + auto eur = makeAsset(issuer, "EUR"); + + a1.changeTrust(usd, INT64_MAX); + a1.changeTrust(eur, INT64_MAX); + a2.changeTrust(usd, INT64_MAX); + a2.changeTrust(eur, INT64_MAX); + issuer.pay(a1, usd, 10000); + issuer.pay(a1, eur, 10000); + issuer.pay(a2, usd, 10000); + issuer.pay(a2, eur, 10000); + + auto checkAccessesFrozenKeyWithSource = [&](auto& sourceAccount, + Operation const& op) { + auto tx = + transactionFromOperations(*app, sourceAccount.getSecretKey(), + sourceAccount.nextSequenceNumber(), {op}); + LedgerSnapshot ls(*app); + auto result = tx->checkValid(app->getAppConnector(), ls, 0, 0, 0); + REQUIRE(!result->isSuccess()); + REQUIRE(result->getResultCode() == txFROZEN_KEY_ACCESSED); + }; + + auto checkAccessesFrozenKey = [&](Operation const& op) { + checkAccessesFrozenKeyWithSource(a1, op); + }; + + auto a2UsdTL = trustlineKey(a2.getPublicKey(), usd); + + SECTION("PaymentOp destination trustline frozen") + { + freezeKey(*app, a2UsdTL); + checkAccessesFrozenKey(payment(a2.getPublicKey(), usd, 100)); + } + + SECTION("PaymentOp native destination account frozen") + { + auto a2Key = accountKey(a2.getPublicKey()); + freezeKey(*app, a2Key); + checkAccessesFrozenKey(payment(a2.getPublicKey(), 100)); + } + + SECTION("PathPaymentStrictReceive destination trustline frozen") + { + freezeKey(*app, a2UsdTL); + checkAccessesFrozenKey( + pathPayment(a2.getPublicKey(), usd, 100, usd, 100, {})); + } + + SECTION("PathPaymentStrictReceive native destination account frozen") + { + auto a2Key = accountKey(a2.getPublicKey()); + freezeKey(*app, a2Key); + checkAccessesFrozenKey( + pathPayment(a2.getPublicKey(), xlm, 100, xlm, 100, {})); + } + + SECTION("PathPaymentStrictSend destination trustline frozen") + { + freezeKey(*app, a2UsdTL); + checkAccessesFrozenKey( + pathPaymentStrictSend(a2.getPublicKey(), usd, 100, usd, 90, {})); + } + + SECTION("PathPaymentStrictSend native destination account frozen") + { + auto a2Key = accountKey(a2.getPublicKey()); + freezeKey(*app, a2Key); + checkAccessesFrozenKey( + pathPaymentStrictSend(a2.getPublicKey(), xlm, 100, xlm, 100, {})); + } + + SECTION("AllowTrustOp trustor trustline frozen") + { + freezeKey(*app, a2UsdTL); + checkAccessesFrozenKeyWithSource( + issuer, allowTrust(a2.getPublicKey(), usd, AUTHORIZED_FLAG)); + } + + SECTION("SetTrustLineFlagsOp trustor trustline frozen") + { + // Issuer must have AUTH_REVOCABLE flag to set trustline flags + issuer.setOptions(setFlags(AUTH_REVOCABLE_FLAG)); + freezeKey(*app, a2UsdTL); + checkAccessesFrozenKeyWithSource( + issuer, setTrustLineFlags(a2.getPublicKey(), usd, + setTrustLineFlags(AUTHORIZED_FLAG))); + } + + SECTION("ClawbackOp from trustline frozen") + { + // Issuer needs clawback enabled + auto clawbackIssuer = root->create( + "clawIssuer", lm.getLastMinBalance(10) + 10 * lm.getLastTxFee()); + clawbackIssuer.setOptions( + setFlags(AUTH_REVOCABLE_FLAG | AUTH_CLAWBACK_ENABLED_FLAG)); + auto clawAsset = makeAsset(clawbackIssuer, "CLW"); + a2.changeTrust(clawAsset, INT64_MAX); + clawbackIssuer.pay(a2, clawAsset, 1000); + + auto a2ClwTL = trustlineKey(a2.getPublicKey(), clawAsset); + freezeKey(*app, a2ClwTL); + + checkAccessesFrozenKeyWithSource( + clawbackIssuer, clawback(a2.getPublicKey(), clawAsset, 100)); + } + + SECTION("RevokeSponsorshipOp ledger key frozen") + { + auto a2Key = accountKey(a2.getPublicKey()); + freezeKey(*app, a2Key); + checkAccessesFrozenKey(revokeSponsorship(a2Key)); + } + + SECTION("RevokeSponsorshipOp signer account frozen") + { + auto a2Key = accountKey(a2.getPublicKey()); + freezeKey(*app, a2Key); + + SignerKey signerKey; + signerKey.type(SIGNER_KEY_TYPE_ED25519); + signerKey.ed25519() = a1.getPublicKey().ed25519(); + + checkAccessesFrozenKey(revokeSponsorship(a2.getPublicKey(), signerKey)); + } + + SECTION("AccountMergeOp destination account frozen") + { + auto a2Key = accountKey(a2.getPublicKey()); + freezeKey(*app, a2Key); + checkAccessesFrozenKey(accountMerge(a2.getPublicKey())); + } + + SECTION("CreateAccountOp destination account frozen") + { + AccountID destKey{}; + freezeKey(*app, accountKey(destKey)); + checkAccessesFrozenKey(createAccount(destKey, 10000000)); + } +} + +TEST_CASE("frozen ledger keys apply time validation", "[frozenledgerkeys][tx]") +{ + VirtualClock clock; + auto cfg = getTestConfig(); + auto app = createTestApplication(clock, cfg); + auto root = app->getRoot(); + auto const& lm = app->getLedgerManager(); + + auto issuer = root->create("issuer", lm.getLastMinBalance(10) + + 10 * lm.getLastTxFee()); + auto a1 = + root->create("A1", lm.getLastMinBalance(10) + 10 * lm.getLastTxFee()); + auto a2 = + root->create("A2", lm.getLastMinBalance(10) + 10 * lm.getLastTxFee()); + + auto usd = makeAsset(issuer, "USD"); + auto eur = makeAsset(issuer, "EUR"); + + a1.changeTrust(usd, INT64_MAX); + a1.changeTrust(eur, INT64_MAX); + a2.changeTrust(usd, INT64_MAX); + a2.changeTrust(eur, INT64_MAX); + issuer.pay(a1, usd, 10000); + issuer.pay(a1, eur, 10000); + issuer.pay(a2, usd, 10000); + + SECTION("claim claimable balance trustline frozen") + { + Claimant claimant; + claimant.v0().destination = a2.getPublicKey(); + claimant.v0().predicate.type(CLAIM_PREDICATE_UNCONDITIONAL); + a1.createClaimableBalance(usd, 100, {claimant}); + auto cbID = a1.getBalanceID(0); + + auto a2UsdTL = trustlineKey(a2.getPublicKey(), usd); + freezeKey(*app, a2UsdTL); + + auto tx = transactionFromOperations(*app, a2.getSecretKey(), + a2.nextSequenceNumber(), + {claimClaimableBalance(cbID)}); + auto r = closeLedger(*app, {tx}); + checkTx(0, r, txFAILED); + REQUIRE(r.results[0] + .result.result.results()[0] + .tr() + .claimClaimableBalanceResult() + .code() == CLAIM_CLAIMABLE_BALANCE_TRUSTLINE_FROZEN); + } + + auto checkLPTrustlineFrozen = [&](LedgerKey const& frozenTrustline, + bool isDeposit) { + auto share = + makeChangeTrustAssetPoolShare(eur, usd, LIQUIDITY_POOL_FEE_V18); + auto poolID = xdrSha256(share.liquidityPool()); + a1.changeTrust(share, INT64_MAX); + a1.liquidityPoolDeposit(poolID, 1000, 1000, Price{1, 1}, Price{1, 1}); + + auto op = isDeposit ? liquidityPoolDeposit(poolID, 100, 100, + Price{1, 1}, Price{1, 1}) + : liquidityPoolWithdraw(poolID, 100, 0, 0); + + freezeKey(*app, frozenTrustline); + auto tx = transactionFromOperations(*app, a1.getSecretKey(), + a1.nextSequenceNumber(), {op}); + auto r = closeLedger(*app, {tx}); + checkTx(0, r, txFAILED); + + auto const& opResult = r.results[0].result.result.results()[0].tr(); + if (isDeposit) + { + REQUIRE(opResult.liquidityPoolDepositResult().code() == + LIQUIDITY_POOL_DEPOSIT_TRUSTLINE_FROZEN); + } + else + { + REQUIRE(opResult.liquidityPoolWithdrawResult().code() == + LIQUIDITY_POOL_WITHDRAW_TRUSTLINE_FROZEN); + } + }; + SECTION("liquidity pool deposit assetA trustline frozen") + { + auto a1EurTL = trustlineKey(a1.getPublicKey(), eur); + checkLPTrustlineFrozen(a1EurTL, true); + } + + SECTION("liquidity pool deposit assetB trustline frozen") + { + auto a1UsdTL = trustlineKey(a1.getPublicKey(), usd); + checkLPTrustlineFrozen(a1UsdTL, true); + } + + SECTION("liquidity pool withdraw assetA trustline frozen") + { + auto a1EurTL = trustlineKey(a1.getPublicKey(), eur); + checkLPTrustlineFrozen(a1EurTL, false); + } + + SECTION("liquidity pool withdraw assetB trustline frozen") + { + auto a1UsdTL = trustlineKey(a1.getPublicKey(), usd); + checkLPTrustlineFrozen(a1UsdTL, false); + } +} + +TEST_CASE("sponsorship can be removed with frozen sponsor", + "[frozenledgerkeys][tx][sponsorship]") +{ + VirtualClock clock; + auto cfg = getTestConfig(); + auto app = createTestApplication(clock, cfg); + auto root = app->getRoot(); + auto const& lm = app->getLedgerManager(); + + auto minBalance = lm.getLastMinBalance(10) + 20 * lm.getLastTxFee(); + auto frozenSponsor = root->create("frozenSponsor", minBalance); + auto sponsored = root->create("sponsored", minBalance); + auto issuer = root->create("issuer", minBalance); + + auto usd = makeAsset(issuer, "USD"); + + auto getNumSponsoringFor = [&](TestAccount const& account) { + LedgerTxn ltx(app->getLedgerTxnRoot()); + auto ltxe = loadAccount(ltx, account.getPublicKey(), true); + REQUIRE(ltxe); + return getNumSponsoring(ltxe.current()); + }; + + auto getNumSponsoredFor = [&](TestAccount const& account) { + LedgerTxn ltx(app->getLedgerTxnRoot()); + auto ltxe = loadAccount(ltx, account.getPublicKey(), true); + REQUIRE(ltxe); + return getNumSponsored(ltxe.current()); + }; + + auto createSponsoredTLTx = transactionFrameFromOps( + app->getNetworkID(), frozenSponsor, + {frozenSponsor.op(beginSponsoringFutureReserves(sponsored)), + sponsored.op(changeTrust(usd, INT64_MAX)), + sponsored.op(endSponsoringFutureReserves())}, + {sponsored}); + auto createRes = closeLedger(*app, {createSponsoredTLTx}); + checkTx(0, createRes, txSUCCESS); + + REQUIRE(sponsored.hasTrustLine(usd)); + REQUIRE(getNumSponsoringFor(frozenSponsor) == 1); + REQUIRE(getNumSponsoredFor(sponsored) == 1); + + freezeKey(*app, accountKey(frozenSponsor.getPublicKey())); + + auto removeTx = transactionFromOperations(*app, sponsored.getSecretKey(), + sponsored.nextSequenceNumber(), + {changeTrust(usd, 0)}); + auto removeRes = closeLedger(*app, {removeTx}); + checkTx(0, removeRes, txSUCCESS); + + REQUIRE(!sponsored.hasTrustLine(usd)); + REQUIRE(getNumSponsoringFor(frozenSponsor) == 0); + REQUIRE(getNumSponsoredFor(sponsored) == 0); +} + +TEST_CASE("deauth removes offers on frozen account", "[frozenledgerkeys][tx]") +{ + VirtualClock clock; + auto cfg = getTestConfig(); + auto app = createTestApplication(clock, cfg); + auto root = app->getRoot(); + auto const& lm = app->getLedgerManager(); + + auto minBalance = lm.getLastMinBalance(10) + 20 * lm.getLastTxFee(); + auto issuer = root->create("issuer", minBalance); + issuer.setOptions(setFlags(AUTH_REQUIRED_FLAG | AUTH_REVOCABLE_FLAG)); + auto frozenAcct = root->create("frozenAcct", minBalance); + + auto usd = makeAsset(issuer, "USD"); + auto xlm = makeNativeAsset(); + + frozenAcct.changeTrust(usd, INT64_MAX); + issuer.allowTrust(usd, frozenAcct.getPublicKey(), AUTHORIZED_FLAG); + issuer.pay(frozenAcct, usd, 5000); + + auto offerID = frozenAcct.manageOffer(0, usd, xlm, Price{1, 1}, 1000); + + // Verify the offer exists and selling liabilities are set. + { + LedgerTxn ltx(app->getLedgerTxnRoot()); + REQUIRE(loadOffer(ltx, frozenAcct.getPublicKey(), offerID)); + auto tl = loadTrustLine(ltx, frozenAcct.getPublicKey(), usd); + REQUIRE(tl); + REQUIRE(tl.getSellingLiabilities(ltx.loadHeader()) > 0); + } + + // Freeze the ACCOUNT (not the trustline). + freezeKey(*app, accountKey(frozenAcct.getPublicKey())); + + // The issuer deauthorizes the (non-frozen) trustline. This triggers offer + // removal which releases liabilities on the frozen account — an allowed + // modification per CAP-77. + auto deauthTx = transactionFromOperations( + *app, issuer.getSecretKey(), issuer.nextSequenceNumber(), + {setTrustLineFlags(frozenAcct.getPublicKey(), usd, + clearTrustLineFlags(AUTHORIZED_FLAG))}); + auto r = closeLedger(*app, {deauthTx}); + checkTx(0, r, txSUCCESS); + + // The offer should be removed and liabilities released, even though the + // account is frozen. + { + LedgerTxn ltx(app->getLedgerTxnRoot()); + REQUIRE(!loadOffer(ltx, frozenAcct.getPublicKey(), offerID)); + auto tl = loadTrustLine(ltx, frozenAcct.getPublicKey(), usd); + REQUIRE(tl); + REQUIRE(tl.getSellingLiabilities(ltx.loadHeader()) == 0); + } +} + +// Below are the helper types/functions for the DEX tests. + +enum class DexOfferOpKind +{ + MANAGE_SELL, + MANAGE_BUY, + CREATE_PASSIVE_SELL +}; + +enum class DexPathOpKind +{ + STRICT_RECEIVE, + STRICT_SEND +}; + +enum class DexAssetPairKind +{ + CREDIT_NATIVE, + NATIVE_CREDIT, + CREDIT_CREDIT +}; + +enum class FrozenSide +{ + SELLING, + BUYING, + BOTH +}; + +char const* +toString(DexOfferOpKind opKind) +{ + switch (opKind) + { + case DexOfferOpKind::MANAGE_SELL: + return "manage-sell"; + case DexOfferOpKind::MANAGE_BUY: + return "manage-buy"; + case DexOfferOpKind::CREATE_PASSIVE_SELL: + return "create-passive-sell"; + } + throw std::runtime_error("unexpected dex offer op kind"); +} + +char const* +toString(DexPathOpKind opKind) +{ + switch (opKind) + { + case DexPathOpKind::STRICT_RECEIVE: + return "path-strict-receive"; + case DexPathOpKind::STRICT_SEND: + return "path-strict-send"; + } + throw std::runtime_error("unexpected dex path op kind"); +} + +char const* +toString(DexAssetPairKind pairKind) +{ + switch (pairKind) + { + case DexAssetPairKind::CREDIT_NATIVE: + return "credit-native"; + case DexAssetPairKind::NATIVE_CREDIT: + return "native-credit"; + case DexAssetPairKind::CREDIT_CREDIT: + return "credit-credit"; + } + throw std::runtime_error("unexpected dex asset pair kind"); +} + +char const* +toString(FrozenSide side) +{ + switch (side) + { + case FrozenSide::SELLING: + return "selling-frozen"; + case FrozenSide::BUYING: + return "buying-frozen"; + case FrozenSide::BOTH: + return "both-frozen"; + } + throw std::runtime_error("unexpected frozen side"); +} + +std::vector +getFrozenAssets(Asset const& selling, Asset const& buying, FrozenSide side) +{ + switch (side) + { + case FrozenSide::SELLING: + return {selling}; + case FrozenSide::BUYING: + return {buying}; + case FrozenSide::BOTH: + return {selling, buying}; + } + throw std::runtime_error("unexpected frozen side"); +} + +struct DexAssetState +{ + int64_t balance; + Liabilities liabilities; +}; + +DexAssetState +loadDexAssetState(Application& app, TestAccount const& account, + Asset const& asset) +{ + DexAssetState res; + res.liabilities.selling = 0; + res.liabilities.buying = 0; + LedgerTxn ltx(app.getLedgerTxnRoot()); + auto header = ltx.loadHeader(); + if (asset.type() == ASSET_TYPE_NATIVE) + { + auto acc = stellar::loadAccount(ltx, account.getPublicKey()); + REQUIRE(acc); + res.balance = acc.current().data.account().balance; + res.liabilities.selling = getSellingLiabilities(header, acc); + res.liabilities.buying = getBuyingLiabilities(header, acc); + } + else + { + auto tl = stellar::loadTrustLine(ltx, account.getPublicKey(), asset); + REQUIRE(tl); + res.balance = tl.getBalance(); + res.liabilities.selling = tl.getSellingLiabilities(header); + res.liabilities.buying = tl.getBuyingLiabilities(header); + } + return res; +} + +bool +offerExists(Application& app, TestAccount const& seller, int64_t offerID) +{ + LedgerTxn ltx(app.getLedgerTxnRoot()); + return static_cast(loadOffer(ltx, seller.getPublicKey(), offerID)); +} + +LedgerKey +frozenKeyForAsset(TestAccount const& account, Asset const& asset) +{ + if (asset.type() == ASSET_TYPE_NATIVE) + { + return accountKey(account.getPublicKey()); + } + return trustlineKey(account.getPublicKey(), asset); +} + +Operation +makeOfferDexOp(DexOfferOpKind opKind, Asset const& selling, Asset const& buying) +{ + switch (opKind) + { + case DexOfferOpKind::MANAGE_SELL: + return manageOffer(0, selling, buying, Price{1, 100}, 300); + case DexOfferOpKind::MANAGE_BUY: + return manageBuyOffer(0, selling, buying, Price{2, 1}, 300); + case DexOfferOpKind::CREATE_PASSIVE_SELL: + return createPassiveOffer(selling, buying, Price{1, 100}, 300); + } + throw std::runtime_error("unexpected dex offer op kind"); +} + +template +size_t +claimedOfferCount(T const& tr, DexOfferOpKind opKind) +{ + switch (opKind) + { + case DexOfferOpKind::MANAGE_SELL: + return tr.manageSellOfferResult().success().offersClaimed.size(); + case DexOfferOpKind::MANAGE_BUY: + return tr.manageBuyOfferResult().success().offersClaimed.size(); + case DexOfferOpKind::CREATE_PASSIVE_SELL: + return tr.manageSellOfferResult().success().offersClaimed.size(); + } + throw std::runtime_error("unexpected dex offer op kind"); +} + +template +size_t +claimedOfferCount(T const& tr, DexPathOpKind opKind) +{ + if (opKind == DexPathOpKind::STRICT_RECEIVE) + { + return tr.pathPaymentStrictReceiveResult().success().offers.size(); + } + return tr.pathPaymentStrictSendResult().success().offers.size(); +} + +TEST_CASE("frozen ledger keys DEX offer operations", + "[frozenledgerkeys][tx][offers]") +{ + VirtualClock clock; + auto cfg = getTestConfig(); + auto app = createTestApplication(clock, cfg); + auto root = app->getRoot(); + auto const& lm = app->getLedgerManager(); + + auto minBalance = lm.getLastMinBalance(20) + 100 * lm.getLastTxFee(); + auto issuerA = root->create("issuerA", minBalance); + auto issuerB = root->create("issuerB", minBalance); + auto frozenSeller1 = root->create("frozenSeller1", minBalance); + auto frozenSeller2 = root->create("frozenSeller2", minBalance); + auto activeSeller = root->create("activeSeller", minBalance); + auto buyer = root->create("buyer", minBalance); + + auto xlm = makeNativeAsset(); + auto usd = makeAsset(issuerA, "USD"); + auto eur = makeAsset(issuerB, "EUR"); + + auto ensureTrust = [&](TestAccount& account, Asset const& asset) { + if (asset.type() != ASSET_TYPE_NATIVE && !account.hasTrustLine(asset)) + { + account.changeTrust(asset, INT64_MAX); + } + }; + + auto fundAsset = [&](TestAccount& account, Asset const& asset, + int64_t amount) { + if (asset.type() == ASSET_TYPE_NATIVE) + { + root->pay(account, amount); + } + else + { + ensureTrust(account, asset); + if (asset == usd) + { + issuerA.pay(account, asset, amount); + } + else + { + REQUIRE(asset == eur); + issuerB.pay(account, asset, amount); + } + } + }; + + auto opKind = + GENERATE(DexOfferOpKind::MANAGE_SELL, DexOfferOpKind::MANAGE_BUY, + DexOfferOpKind::CREATE_PASSIVE_SELL); + auto pairKind = GENERATE(DexAssetPairKind::CREDIT_NATIVE, + DexAssetPairKind::NATIVE_CREDIT, + DexAssetPairKind::CREDIT_CREDIT); + auto side = + GENERATE(FrozenSide::SELLING, FrozenSide::BUYING, FrozenSide::BOTH); + // Generate two offers: first is always frozen, second is active or frozen + // based on this flag. + auto secondOfferIsActive = GENERATE(true, false); + + DYNAMIC_SECTION(fmt::format( + "{} [{}][{}][{}]", + secondOfferIsActive ? "second offer active" : "second offer frozen", + toString(opKind), toString(pairKind), toString(side))) + { + Asset makerSelling; + Asset makerBuying; + switch (pairKind) + { + case DexAssetPairKind::CREDIT_NATIVE: + makerSelling = usd; + makerBuying = xlm; + break; + case DexAssetPairKind::NATIVE_CREDIT: + makerSelling = xlm; + makerBuying = usd; + break; + case DexAssetPairKind::CREDIT_CREDIT: + makerSelling = usd; + makerBuying = eur; + break; + } + + auto prepareMaker = [&](TestAccount& account) { + ensureTrust(account, makerSelling); + ensureTrust(account, makerBuying); + fundAsset(account, makerSelling, 10'000); + }; + + prepareMaker(frozenSeller1); + prepareMaker(frozenSeller2); + prepareMaker(activeSeller); + ensureTrust(buyer, makerSelling); + ensureTrust(buyer, makerBuying); + fundAsset(buyer, makerBuying, 10'000); + + auto frozenOffer1 = frozenSeller1.manageOffer( + 0, makerSelling, makerBuying, Price{1, 1}, 1'000); + auto frozenOffer2 = int64_t{0}; + auto activeOffer = int64_t{0}; + if (secondOfferIsActive) + { + activeOffer = activeSeller.manageOffer(0, makerSelling, makerBuying, + Price{2, 1}, 1'000); + } + else + { + frozenOffer2 = frozenSeller2.manageOffer( + 0, makerSelling, makerBuying, Price{2, 1}, 1'000); + } + + auto frozenAssets = getFrozenAssets(makerSelling, makerBuying, side); + struct FrozenState + { + TestAccount* seller; + Asset asset; + DexAssetState pre; + }; + std::vector frozenStates; + + auto freezeForSeller = [&](TestAccount& seller) { + for (auto const& asset : frozenAssets) + { + freezeKey(*app, frozenKeyForAsset(seller, asset)); + auto pre = loadDexAssetState(*app, seller, asset); + REQUIRE(pre.liabilities.selling + pre.liabilities.buying > 0); + frozenStates.emplace_back(FrozenState{&seller, asset, pre}); + } + }; + + freezeForSeller(frozenSeller1); + if (!secondOfferIsActive) + { + freezeForSeller(frozenSeller2); + } + + auto frozen1SellingPre = + loadDexAssetState(*app, frozenSeller1, makerSelling).balance; + auto frozen1BuyingPre = + loadDexAssetState(*app, frozenSeller1, makerBuying).balance; + auto frozen2SellingPre = + loadDexAssetState(*app, frozenSeller2, makerSelling).balance; + auto frozen2BuyingPre = + loadDexAssetState(*app, frozenSeller2, makerBuying).balance; + auto activeSellingPre = + loadDexAssetState(*app, activeSeller, makerSelling).balance; + auto activeBuyingPre = + loadDexAssetState(*app, activeSeller, makerBuying).balance; + auto buyerSellingPre = + loadDexAssetState(*app, buyer, makerBuying).balance; + auto buyerBuyingPre = + loadDexAssetState(*app, buyer, makerSelling).balance; + + auto op = makeOfferDexOp(opKind, makerBuying, makerSelling); + op.sourceAccount.activate() = toMuxedAccount(buyer.getPublicKey()); + // Pay for transaction from the root account in order to have clean XLM + // balance changes. + auto tx = transactionFromOperations(*app, root->getSecretKey(), + root->nextSequenceNumber(), {op}); + tx->addSignature(buyer.getSecretKey()); + auto r = closeLedger(*app, {tx}); + checkTx(0, r, txSUCCESS); + + auto const& tr = r.results[0].result.result.results()[0].tr(); + if (secondOfferIsActive) + { + REQUIRE(claimedOfferCount(tr, opKind) > 0); + } + else + { + REQUIRE(claimedOfferCount(tr, opKind) == 0); + } + + REQUIRE(!offerExists(*app, frozenSeller1, frozenOffer1)); + if (secondOfferIsActive) + { + REQUIRE(offerExists(*app, activeSeller, activeOffer)); + } + else + { + REQUIRE(!offerExists(*app, frozenSeller2, frozenOffer2)); + } + + auto frozen1SellingPost = + loadDexAssetState(*app, frozenSeller1, makerSelling).balance; + auto frozen1BuyingPost = + loadDexAssetState(*app, frozenSeller1, makerBuying).balance; + auto frozen2SellingPost = + loadDexAssetState(*app, frozenSeller2, makerSelling).balance; + auto frozen2BuyingPost = + loadDexAssetState(*app, frozenSeller2, makerBuying).balance; + auto activeSellingPost = + loadDexAssetState(*app, activeSeller, makerSelling).balance; + auto activeBuyingPost = + loadDexAssetState(*app, activeSeller, makerBuying).balance; + auto buyerSellingPost = + loadDexAssetState(*app, buyer, makerBuying).balance; + auto buyerBuyingPost = + loadDexAssetState(*app, buyer, makerSelling).balance; + + REQUIRE(frozen1SellingPost == frozen1SellingPre); + REQUIRE(frozen1BuyingPost == frozen1BuyingPre); + // With second offer active, exactly one unfrozen offer at price 2:1 is + // crossed. Math: + // - manage-buy buys 300 units -> pays 600 units. + // - manage-sell/create-passive sell 300 units -> receive 150 units. + int64_t expectedActiveSold = 0; + int64_t expectedActiveBought = 0; + switch (opKind) + { + case DexOfferOpKind::MANAGE_BUY: + expectedActiveSold = 300; + expectedActiveBought = 600; + break; + case DexOfferOpKind::MANAGE_SELL: + case DexOfferOpKind::CREATE_PASSIVE_SELL: + expectedActiveSold = 150; + expectedActiveBought = 300; + break; + } + + if (secondOfferIsActive) + { + REQUIRE(activeSellingPre - activeSellingPost == expectedActiveSold); + REQUIRE(activeBuyingPost - activeBuyingPre == expectedActiveBought); + REQUIRE(buyerBuyingPost - buyerBuyingPre == expectedActiveSold); + REQUIRE(buyerSellingPre - buyerSellingPost == expectedActiveBought); + } + else + { + REQUIRE(activeSellingPost == activeSellingPre); + REQUIRE(activeBuyingPost == activeBuyingPre); + REQUIRE(frozen2SellingPost == frozen2SellingPre); + REQUIRE(frozen2BuyingPost == frozen2BuyingPre); + REQUIRE(buyerBuyingPost == buyerBuyingPre); + REQUIRE(buyerSellingPost == buyerSellingPre); + } + + for (auto const& frozenState : frozenStates) + { + auto post = + loadDexAssetState(*app, *frozenState.seller, frozenState.asset); + REQUIRE(post.balance == frozenState.pre.balance); + REQUIRE(post.liabilities.selling == 0); + REQUIRE(post.liabilities.buying == 0); + } + } +} + +TEST_CASE("frozen ledger keys DEX path payments", + "[frozenledgerkeys][tx][offers]") +{ + VirtualClock clock; + auto cfg = getTestConfig(); + auto app = createTestApplication(clock, cfg); + auto root = app->getRoot(); + auto const& lm = app->getLedgerManager(); + + auto minBalance = lm.getLastMinBalance(20) + 100 * lm.getLastTxFee(); + auto issuerB = root->create("issuerB", minBalance); + auto issuerC = root->create("issuerC", minBalance); + + auto payer = root->create("payer", minBalance); + auto destination = root->create("destination", minBalance); + + auto l1Best = root->create("l1Best", minBalance); + auto l1Fallback = root->create("l1Fallback", minBalance); + auto l2Best = root->create("l2Best", minBalance); + auto l2Fallback = root->create("l2Fallback", minBalance); + auto l3Best = root->create("l3Best", minBalance); + auto l3Fallback = root->create("l3Fallback", minBalance); + + auto a = makeNativeAsset(); + auto b = makeAsset(issuerB, "USD"); + auto c = makeAsset(issuerC, "EUR"); + + auto ensureTrust = [&](TestAccount& account, Asset const& asset) { + if (asset.type() != ASSET_TYPE_NATIVE && !account.hasTrustLine(asset)) + { + account.changeTrust(asset, INT64_MAX); + } + }; + + auto fundAsset = [&](TestAccount& account, Asset const& asset, + int64_t amount) { + if (asset.type() == ASSET_TYPE_NATIVE) + { + root->pay(account, amount); + } + else + { + ensureTrust(account, asset); + if (asset == b) + { + issuerB.pay(account, asset, amount); + } + else + { + REQUIRE(asset == c); + issuerC.pay(account, asset, amount); + } + } + }; + + struct LegState + { + TestAccount* best; + TestAccount* fallback; + Asset selling; + Asset buying; + int64_t bestOffer; + int64_t fallbackOffer; + }; + + std::array legs = {LegState{&l1Best, &l1Fallback, b, a, 0, 0}, + LegState{&l2Best, &l2Fallback, c, b, 0, 0}, + LegState{&l3Best, &l3Fallback, a, c, 0, 0}}; + + auto prepareMaker = [&](TestAccount& account, Asset const& selling, + Asset const& buying) { + ensureTrust(account, selling); + ensureTrust(account, buying); + fundAsset(account, selling, 10'000); + }; + + for (auto& leg : legs) + { + prepareMaker(*leg.best, leg.selling, leg.buying); + prepareMaker(*leg.fallback, leg.selling, leg.buying); + leg.bestOffer = leg.best->manageOffer(0, leg.selling, leg.buying, + Price{1, 1}, 1'000); + leg.fallbackOffer = leg.fallback->manageOffer( + 0, leg.selling, leg.buying, Price{2, 1}, 1'000); + } + + auto makePathDexOp = [&](DexPathOpKind opKind, PublicKey const& destination, + Asset const& a, Asset const& b, Asset const& c) { + if (opKind == DexPathOpKind::STRICT_RECEIVE) + { + return pathPayment(destination, a, 10'000, a, 200, {b, c}); + } + return pathPaymentStrictSend(destination, a, 400, a, 1, {b, c}); + }; + + auto opKind = + GENERATE(DexPathOpKind::STRICT_RECEIVE, DexPathOpKind::STRICT_SEND); + auto side = + GENERATE(FrozenSide::SELLING, FrozenSide::BUYING, FrozenSide::BOTH); + auto legToFreeze = GENERATE(0, 1, 2); + + DYNAMIC_SECTION(fmt::format("A->B->C->A [{}][leg={}][{}]", toString(opKind), + legToFreeze, toString(side))) + { + auto& targetLeg = legs[legToFreeze]; + auto frozenAssets = + getFrozenAssets(targetLeg.selling, targetLeg.buying, side); + + struct FrozenState + { + Asset asset; + DexAssetState pre; + }; + std::vector frozenStates; + for (auto const& asset : frozenAssets) + { + freezeKey(*app, frozenKeyForAsset(*targetLeg.best, asset)); + auto pre = loadDexAssetState(*app, *targetLeg.best, asset); + REQUIRE(pre.liabilities.selling + pre.liabilities.buying > 0); + frozenStates.emplace_back(FrozenState{asset, pre}); + } + + auto bestSellingPre = + loadDexAssetState(*app, *targetLeg.best, targetLeg.selling).balance; + auto bestBuyingPre = + loadDexAssetState(*app, *targetLeg.best, targetLeg.buying).balance; + auto fallbackSellingPre = + loadDexAssetState(*app, *targetLeg.fallback, targetLeg.selling) + .balance; + auto fallbackBuyingPre = + loadDexAssetState(*app, *targetLeg.fallback, targetLeg.buying) + .balance; + auto payerPre = loadDexAssetState(*app, payer, a).balance; + auto destinationPre = loadDexAssetState(*app, destination, a).balance; + + auto op = makePathDexOp(opKind, destination.getPublicKey(), a, b, c); + op.sourceAccount.activate() = toMuxedAccount(payer.getPublicKey()); + + auto tx = transactionFromOperations(*app, root->getSecretKey(), + root->nextSequenceNumber(), {op}); + tx->addSignature(payer.getSecretKey()); + + auto r = closeLedger(*app, {tx}); + checkTx(0, r, txSUCCESS); + + auto const& tr = r.results[0].result.result.results()[0].tr(); + REQUIRE(claimedOfferCount(tr, opKind) > 0); + + REQUIRE(!offerExists(*app, *targetLeg.best, targetLeg.bestOffer)); + REQUIRE( + offerExists(*app, *targetLeg.fallback, targetLeg.fallbackOffer)); + + auto bestSellingPost = + loadDexAssetState(*app, *targetLeg.best, targetLeg.selling).balance; + auto bestBuyingPost = + loadDexAssetState(*app, *targetLeg.best, targetLeg.buying).balance; + auto fallbackSellingPost = + loadDexAssetState(*app, *targetLeg.fallback, targetLeg.selling) + .balance; + auto fallbackBuyingPost = + loadDexAssetState(*app, *targetLeg.fallback, targetLeg.buying) + .balance; + auto payerPost = loadDexAssetState(*app, payer, a).balance; + auto destinationPost = loadDexAssetState(*app, destination, a).balance; + + // Exactly one leg uses the fallback 2:1 offer, other two legs use 1:1. + // For both strict-receive (dest=200 A) and strict-send (source=400 A): + // - fallback seller sells 200 of its selling asset and receives 400 of + // its buying asset, + // - payer sends 400 A, + // - destination receives 200 A. + constexpr int64_t expectedFallbackSold = 200; + constexpr int64_t expectedFallbackBought = 400; + constexpr int64_t expectedPayerSent = 400; + constexpr int64_t expectedDestinationReceived = 200; + + REQUIRE(bestSellingPost == bestSellingPre); + REQUIRE(bestBuyingPost == bestBuyingPre); + REQUIRE(fallbackSellingPre - fallbackSellingPost == + expectedFallbackSold); + REQUIRE(fallbackBuyingPost - fallbackBuyingPre == + expectedFallbackBought); + REQUIRE(payerPre - payerPost == expectedPayerSent); + REQUIRE(destinationPost - destinationPre == + expectedDestinationReceived); + + for (auto const& frozenState : frozenStates) + { + auto post = + loadDexAssetState(*app, *targetLeg.best, frozenState.asset); + REQUIRE(post.balance == frozenState.pre.balance); + REQUIRE(post.liabilities.selling == 0); + REQUIRE(post.liabilities.buying == 0); + } + } +} + +#endif // ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION +} // namespace +} // namespace stellar diff --git a/src/transactions/test/InvokeHostFunctionTests.cpp b/src/transactions/test/InvokeHostFunctionTests.cpp index e64db9604e..999217e59b 100644 --- a/src/transactions/test/InvokeHostFunctionTests.cpp +++ b/src/transactions/test/InvokeHostFunctionTests.cpp @@ -2526,6 +2526,16 @@ TEST_CASE("settings upgrade", "[tx][soroban][upgrades]") continue; } +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION + // Delta settings are upgrade payloads only and have no stored + // ledger entries to load. + if (type == CONFIG_SETTING_FROZEN_LEDGER_KEYS_DELTA || + type == CONFIG_SETTING_FREEZE_BYPASS_TXS_DELTA) + { + continue; + } +#endif + // Because we added more cost types in v21 and later, the initial // contractDataEntrySizeBytes setting of 2000 is too low to write // all settings at once. This isn't an issue in practice because 1. @@ -5687,6 +5697,16 @@ TEST_CASE("settings upgrade command line utils", "[tx][soroban][upgrades]") continue; } +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION + // Delta settings are upgrade payloads only and have no stored + // ledger entries to load. + if (type == CONFIG_SETTING_FROZEN_LEDGER_KEYS_DELTA || + type == CONFIG_SETTING_FREEZE_BYPASS_TXS_DELTA) + { + continue; + } +#endif + LedgerTxn ltx(app->getLedgerTxnRoot()); auto entry = ltx.load(configSettingKey(type)); @@ -5955,6 +5975,16 @@ TEST_CASE("settings upgrade command line utils", "[tx][soroban][upgrades]") continue; } +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION + // Delta settings are upgrade payloads only and have no stored + // ledger entries to load. + if (type == CONFIG_SETTING_FROZEN_LEDGER_KEYS_DELTA || + type == CONFIG_SETTING_FREEZE_BYPASS_TXS_DELTA) + { + continue; + } +#endif + LedgerTxn ltx(app->getLedgerTxnRoot()); auto entry = ltx.load(configSettingKey(type)); diff --git a/test-tx-meta-baseline-next/FrozenLedgerKeysTests.json b/test-tx-meta-baseline-next/FrozenLedgerKeysTests.json new file mode 100644 index 0000000000..943089b22f --- /dev/null +++ b/test-tx-meta-baseline-next/FrozenLedgerKeysTests.json @@ -0,0 +1,2066 @@ + +{ + "!cfg protocol version" : 26, + "!rng seed" : 12345, + "!test all versions" : true, + "!versions to test" : + [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26 + ], + "deauth removes offers on frozen account" : + [ + "DacWHBn53mA=", + "BHt32BfGHdg=", + "thRTR69es+0=", + "RQrU/aegI2c=", + "liUp8uVOyp0=", + "iRy/J6geXLQ=", + "GDoBb0yXRAQ=" + ], + "freeze bypass tx hash allows frozen key access at validation time" : + [ + "cpVo6Aho2ao=", + "IBmLxZVWm4Y=", + "6CmtErdDHKk=", + "cpVo6Aho2ao=", + "IBmLxZVWm4Y=", + "6CmtErdDHKk=", + "cpVo6Aho2ao=", + "IBmLxZVWm4Y=", + "6CmtErdDHKk=", + "cpVo6Aho2ao=", + "IBmLxZVWm4Y=", + "6CmtErdDHKk=", + "cpVo6Aho2ao=", + "IBmLxZVWm4Y=", + "6CmtErdDHKk=", + "cpVo6Aho2ao=", + "IBmLxZVWm4Y=", + "6CmtErdDHKk=" + ], + "frozen ledger keys DEX offer operations" : + [ + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=", + "dROvjn1oZgA=", + "wgVEL6+CtcI=", + "4Y7TSWNjzJ8=", + "z35KLOsU0zI=", + "6LEMxT4kV2k=", + "kpWFkyAJ7r0=" + ], + "frozen ledger keys DEX offer operations|second offer active [create-passive-sell][credit-credit][both-frozen]" : + [ + "5T6l8LjCUkY=", + "ccRHbVieTsw=", + "SIk6EzSNNIU=", + "YTXYxQkECjo=", + "D03a2r64awc=", + "u0m/JV4s3Q0=", + "HoyNmTSXTcI=", + "camVDOU0C7I=", + "lVgryQtUCw0=", + "b4bdwQpMOHE=", + "FnMTkn9Jn2E=", + "MW1Dtly8A6o=", + "lvrZR5qo+vs=", + "7HURPDSCy34=" + ], + "frozen ledger keys DEX offer operations|second offer active [create-passive-sell][credit-credit][buying-frozen]" : + [ + "5T6l8LjCUkY=", + "ccRHbVieTsw=", + "SIk6EzSNNIU=", + "YTXYxQkECjo=", + "D03a2r64awc=", + "u0m/JV4s3Q0=", + "HoyNmTSXTcI=", + "camVDOU0C7I=", + "lVgryQtUCw0=", + "b4bdwQpMOHE=", + "FnMTkn9Jn2E=", + "MW1Dtly8A6o=", + "lvrZR5qo+vs=", + "7HURPDSCy34=" + ], + "frozen ledger keys DEX offer operations|second offer active [create-passive-sell][credit-credit][selling-frozen]" : + [ + "5T6l8LjCUkY=", + "ccRHbVieTsw=", + "SIk6EzSNNIU=", + "YTXYxQkECjo=", + "D03a2r64awc=", + "u0m/JV4s3Q0=", + "HoyNmTSXTcI=", + "camVDOU0C7I=", + "lVgryQtUCw0=", + "b4bdwQpMOHE=", + "FnMTkn9Jn2E=", + "MW1Dtly8A6o=", + "lvrZR5qo+vs=", + "7HURPDSCy34=" + ], + "frozen ledger keys DEX offer operations|second offer active [create-passive-sell][credit-native][both-frozen]" : + [ + "5T6l8LjCUkY=", + "6VjQbqbbuAk=", + "jDnZQ4ZgABk=", + "6KQbVLRdk3w=", + "7/YQuwhh6TI=", + "iM5ZOHIqzLg=", + "1eRHliGXeqs=", + "iTycvSyljs0=", + "XkrrDvK4zSs=", + "FrdcvWcrVJ0=" + ], + "frozen ledger keys DEX offer operations|second offer active [create-passive-sell][credit-native][buying-frozen]" : + [ + "5T6l8LjCUkY=", + "6VjQbqbbuAk=", + "jDnZQ4ZgABk=", + "6KQbVLRdk3w=", + "7/YQuwhh6TI=", + "iM5ZOHIqzLg=", + "1eRHliGXeqs=", + "iTycvSyljs0=", + "XkrrDvK4zSs=", + "FrdcvWcrVJ0=" + ], + "frozen ledger keys DEX offer operations|second offer active [create-passive-sell][credit-native][selling-frozen]" : + [ + "5T6l8LjCUkY=", + "6VjQbqbbuAk=", + "jDnZQ4ZgABk=", + "6KQbVLRdk3w=", + "7/YQuwhh6TI=", + "iM5ZOHIqzLg=", + "1eRHliGXeqs=", + "iTycvSyljs0=", + "XkrrDvK4zSs=", + "FrdcvWcrVJ0=" + ], + "frozen ledger keys DEX offer operations|second offer active [create-passive-sell][native-credit][both-frozen]" : + [ + "5T6l8LjCUkY=", + "254mVlqDxAE=", + "jDnZQ4ZgABk=", + "dXFJT+pLeTg=", + "7/YQuwhh6TI=", + "bMdsOUzGrZM=", + "1eRHliGXeqs=", + "XSuqNLC2K3o=", + "ccLd/umPUiE=", + "s1oXN6i+Kyo=" + ], + "frozen ledger keys DEX offer operations|second offer active [create-passive-sell][native-credit][buying-frozen]" : + [ + "5T6l8LjCUkY=", + "254mVlqDxAE=", + "jDnZQ4ZgABk=", + "dXFJT+pLeTg=", + "7/YQuwhh6TI=", + "bMdsOUzGrZM=", + "1eRHliGXeqs=", + "XSuqNLC2K3o=", + "ccLd/umPUiE=", + "s1oXN6i+Kyo=" + ], + "frozen ledger keys DEX offer operations|second offer active [create-passive-sell][native-credit][selling-frozen]" : + [ + "5T6l8LjCUkY=", + "254mVlqDxAE=", + "jDnZQ4ZgABk=", + "dXFJT+pLeTg=", + "7/YQuwhh6TI=", + "bMdsOUzGrZM=", + "1eRHliGXeqs=", + "XSuqNLC2K3o=", + "ccLd/umPUiE=", + "s1oXN6i+Kyo=" + ], + "frozen ledger keys DEX offer operations|second offer active [manage-buy][credit-credit][both-frozen]" : + [ + "5T6l8LjCUkY=", + "ccRHbVieTsw=", + "SIk6EzSNNIU=", + "YTXYxQkECjo=", + "D03a2r64awc=", + "u0m/JV4s3Q0=", + "HoyNmTSXTcI=", + "camVDOU0C7I=", + "lVgryQtUCw0=", + "b4bdwQpMOHE=", + "FnMTkn9Jn2E=", + "MW1Dtly8A6o=", + "lvrZR5qo+vs=", + "7HURPDSCy34=" + ], + "frozen ledger keys DEX offer operations|second offer active [manage-buy][credit-credit][buying-frozen]" : + [ + "5T6l8LjCUkY=", + "ccRHbVieTsw=", + "SIk6EzSNNIU=", + "YTXYxQkECjo=", + "D03a2r64awc=", + "u0m/JV4s3Q0=", + "HoyNmTSXTcI=", + "camVDOU0C7I=", + "lVgryQtUCw0=", + "b4bdwQpMOHE=", + "FnMTkn9Jn2E=", + "MW1Dtly8A6o=", + "lvrZR5qo+vs=", + "7HURPDSCy34=" + ], + "frozen ledger keys DEX offer operations|second offer active [manage-buy][credit-credit][selling-frozen]" : + [ + "5T6l8LjCUkY=", + "ccRHbVieTsw=", + "SIk6EzSNNIU=", + "YTXYxQkECjo=", + "D03a2r64awc=", + "u0m/JV4s3Q0=", + "HoyNmTSXTcI=", + "camVDOU0C7I=", + "lVgryQtUCw0=", + "b4bdwQpMOHE=", + "FnMTkn9Jn2E=", + "MW1Dtly8A6o=", + "lvrZR5qo+vs=", + "7HURPDSCy34=" + ], + "frozen ledger keys DEX offer operations|second offer active [manage-buy][credit-native][both-frozen]" : + [ + "5T6l8LjCUkY=", + "6VjQbqbbuAk=", + "jDnZQ4ZgABk=", + "6KQbVLRdk3w=", + "7/YQuwhh6TI=", + "iM5ZOHIqzLg=", + "1eRHliGXeqs=", + "iTycvSyljs0=", + "XkrrDvK4zSs=", + "FrdcvWcrVJ0=" + ], + "frozen ledger keys DEX offer operations|second offer active [manage-buy][credit-native][buying-frozen]" : + [ + "5T6l8LjCUkY=", + "6VjQbqbbuAk=", + "jDnZQ4ZgABk=", + "6KQbVLRdk3w=", + "7/YQuwhh6TI=", + "iM5ZOHIqzLg=", + "1eRHliGXeqs=", + "iTycvSyljs0=", + "XkrrDvK4zSs=", + "FrdcvWcrVJ0=" + ], + "frozen ledger keys DEX offer operations|second offer active [manage-buy][credit-native][selling-frozen]" : + [ + "5T6l8LjCUkY=", + "6VjQbqbbuAk=", + "jDnZQ4ZgABk=", + "6KQbVLRdk3w=", + "7/YQuwhh6TI=", + "iM5ZOHIqzLg=", + "1eRHliGXeqs=", + "iTycvSyljs0=", + "XkrrDvK4zSs=", + "FrdcvWcrVJ0=" + ], + "frozen ledger keys DEX offer operations|second offer active [manage-buy][native-credit][both-frozen]" : + [ + "5T6l8LjCUkY=", + "254mVlqDxAE=", + "jDnZQ4ZgABk=", + "dXFJT+pLeTg=", + "7/YQuwhh6TI=", + "bMdsOUzGrZM=", + "1eRHliGXeqs=", + "XSuqNLC2K3o=", + "ccLd/umPUiE=", + "s1oXN6i+Kyo=" + ], + "frozen ledger keys DEX offer operations|second offer active [manage-buy][native-credit][buying-frozen]" : + [ + "5T6l8LjCUkY=", + "254mVlqDxAE=", + "jDnZQ4ZgABk=", + "dXFJT+pLeTg=", + "7/YQuwhh6TI=", + "bMdsOUzGrZM=", + "1eRHliGXeqs=", + "XSuqNLC2K3o=", + "ccLd/umPUiE=", + "s1oXN6i+Kyo=" + ], + "frozen ledger keys DEX offer operations|second offer active [manage-buy][native-credit][selling-frozen]" : + [ + "5T6l8LjCUkY=", + "254mVlqDxAE=", + "jDnZQ4ZgABk=", + "dXFJT+pLeTg=", + "7/YQuwhh6TI=", + "bMdsOUzGrZM=", + "1eRHliGXeqs=", + "XSuqNLC2K3o=", + "ccLd/umPUiE=", + "s1oXN6i+Kyo=" + ], + "frozen ledger keys DEX offer operations|second offer active [manage-sell][credit-credit][both-frozen]" : + [ + "5T6l8LjCUkY=", + "ccRHbVieTsw=", + "SIk6EzSNNIU=", + "YTXYxQkECjo=", + "D03a2r64awc=", + "u0m/JV4s3Q0=", + "HoyNmTSXTcI=", + "camVDOU0C7I=", + "lVgryQtUCw0=", + "b4bdwQpMOHE=", + "FnMTkn9Jn2E=", + "MW1Dtly8A6o=", + "lvrZR5qo+vs=", + "7HURPDSCy34=" + ], + "frozen ledger keys DEX offer operations|second offer active [manage-sell][credit-credit][buying-frozen]" : + [ + "5T6l8LjCUkY=", + "ccRHbVieTsw=", + "SIk6EzSNNIU=", + "YTXYxQkECjo=", + "D03a2r64awc=", + "u0m/JV4s3Q0=", + "HoyNmTSXTcI=", + "camVDOU0C7I=", + "lVgryQtUCw0=", + "b4bdwQpMOHE=", + "FnMTkn9Jn2E=", + "MW1Dtly8A6o=", + "lvrZR5qo+vs=", + "7HURPDSCy34=" + ], + "frozen ledger keys DEX offer operations|second offer active [manage-sell][credit-credit][selling-frozen]" : + [ + "5T6l8LjCUkY=", + "ccRHbVieTsw=", + "SIk6EzSNNIU=", + "YTXYxQkECjo=", + "D03a2r64awc=", + "u0m/JV4s3Q0=", + "HoyNmTSXTcI=", + "camVDOU0C7I=", + "lVgryQtUCw0=", + "b4bdwQpMOHE=", + "FnMTkn9Jn2E=", + "MW1Dtly8A6o=", + "lvrZR5qo+vs=", + "7HURPDSCy34=" + ], + "frozen ledger keys DEX offer operations|second offer active [manage-sell][credit-native][both-frozen]" : + [ + "5T6l8LjCUkY=", + "6VjQbqbbuAk=", + "jDnZQ4ZgABk=", + "6KQbVLRdk3w=", + "7/YQuwhh6TI=", + "iM5ZOHIqzLg=", + "1eRHliGXeqs=", + "iTycvSyljs0=", + "XkrrDvK4zSs=", + "FrdcvWcrVJ0=" + ], + "frozen ledger keys DEX offer operations|second offer active [manage-sell][credit-native][buying-frozen]" : + [ + "5T6l8LjCUkY=", + "6VjQbqbbuAk=", + "jDnZQ4ZgABk=", + "6KQbVLRdk3w=", + "7/YQuwhh6TI=", + "iM5ZOHIqzLg=", + "1eRHliGXeqs=", + "iTycvSyljs0=", + "XkrrDvK4zSs=", + "FrdcvWcrVJ0=" + ], + "frozen ledger keys DEX offer operations|second offer active [manage-sell][credit-native][selling-frozen]" : + [ + "5T6l8LjCUkY=", + "6VjQbqbbuAk=", + "jDnZQ4ZgABk=", + "6KQbVLRdk3w=", + "7/YQuwhh6TI=", + "iM5ZOHIqzLg=", + "1eRHliGXeqs=", + "iTycvSyljs0=", + "XkrrDvK4zSs=", + "FrdcvWcrVJ0=" + ], + "frozen ledger keys DEX offer operations|second offer active [manage-sell][native-credit][both-frozen]" : + [ + "5T6l8LjCUkY=", + "254mVlqDxAE=", + "jDnZQ4ZgABk=", + "dXFJT+pLeTg=", + "7/YQuwhh6TI=", + "bMdsOUzGrZM=", + "1eRHliGXeqs=", + "XSuqNLC2K3o=", + "ccLd/umPUiE=", + "s1oXN6i+Kyo=" + ], + "frozen ledger keys DEX offer operations|second offer active [manage-sell][native-credit][buying-frozen]" : + [ + "5T6l8LjCUkY=", + "254mVlqDxAE=", + "jDnZQ4ZgABk=", + "dXFJT+pLeTg=", + "7/YQuwhh6TI=", + "bMdsOUzGrZM=", + "1eRHliGXeqs=", + "XSuqNLC2K3o=", + "ccLd/umPUiE=", + "s1oXN6i+Kyo=" + ], + "frozen ledger keys DEX offer operations|second offer active [manage-sell][native-credit][selling-frozen]" : + [ + "5T6l8LjCUkY=", + "254mVlqDxAE=", + "jDnZQ4ZgABk=", + "dXFJT+pLeTg=", + "7/YQuwhh6TI=", + "bMdsOUzGrZM=", + "1eRHliGXeqs=", + "XSuqNLC2K3o=", + "ccLd/umPUiE=", + "s1oXN6i+Kyo=" + ], + "frozen ledger keys DEX offer operations|second offer frozen [create-passive-sell][credit-credit][both-frozen]" : + [ + "5T6l8LjCUkY=", + "ccRHbVieTsw=", + "SIk6EzSNNIU=", + "YTXYxQkECjo=", + "D03a2r64awc=", + "u0m/JV4s3Q0=", + "HoyNmTSXTcI=", + "camVDOU0C7I=", + "lVgryQtUCw0=", + "b4bdwQpMOHE=", + "FnMTkn9Jn2E=", + "MW1Dtly8A6o=", + "lvrZR5qo+vs=", + "aRfpkC8fg1w=" + ], + "frozen ledger keys DEX offer operations|second offer frozen [create-passive-sell][credit-credit][buying-frozen]" : + [ + "5T6l8LjCUkY=", + "ccRHbVieTsw=", + "SIk6EzSNNIU=", + "YTXYxQkECjo=", + "D03a2r64awc=", + "u0m/JV4s3Q0=", + "HoyNmTSXTcI=", + "camVDOU0C7I=", + "lVgryQtUCw0=", + "b4bdwQpMOHE=", + "FnMTkn9Jn2E=", + "MW1Dtly8A6o=", + "lvrZR5qo+vs=", + "aRfpkC8fg1w=" + ], + "frozen ledger keys DEX offer operations|second offer frozen [create-passive-sell][credit-credit][selling-frozen]" : + [ + "5T6l8LjCUkY=", + "ccRHbVieTsw=", + "SIk6EzSNNIU=", + "YTXYxQkECjo=", + "D03a2r64awc=", + "u0m/JV4s3Q0=", + "HoyNmTSXTcI=", + "camVDOU0C7I=", + "lVgryQtUCw0=", + "b4bdwQpMOHE=", + "FnMTkn9Jn2E=", + "MW1Dtly8A6o=", + "lvrZR5qo+vs=", + "aRfpkC8fg1w=" + ], + "frozen ledger keys DEX offer operations|second offer frozen [create-passive-sell][credit-native][both-frozen]" : + [ + "5T6l8LjCUkY=", + "6VjQbqbbuAk=", + "jDnZQ4ZgABk=", + "6KQbVLRdk3w=", + "7/YQuwhh6TI=", + "iM5ZOHIqzLg=", + "1eRHliGXeqs=", + "iTycvSyljs0=", + "XkrrDvK4zSs=", + "kgx5PwmlbjA=" + ], + "frozen ledger keys DEX offer operations|second offer frozen [create-passive-sell][credit-native][buying-frozen]" : + [ + "5T6l8LjCUkY=", + "6VjQbqbbuAk=", + "jDnZQ4ZgABk=", + "6KQbVLRdk3w=", + "7/YQuwhh6TI=", + "iM5ZOHIqzLg=", + "1eRHliGXeqs=", + "iTycvSyljs0=", + "XkrrDvK4zSs=", + "kgx5PwmlbjA=" + ], + "frozen ledger keys DEX offer operations|second offer frozen [create-passive-sell][credit-native][selling-frozen]" : + [ + "5T6l8LjCUkY=", + "6VjQbqbbuAk=", + "jDnZQ4ZgABk=", + "6KQbVLRdk3w=", + "7/YQuwhh6TI=", + "iM5ZOHIqzLg=", + "1eRHliGXeqs=", + "iTycvSyljs0=", + "XkrrDvK4zSs=", + "kgx5PwmlbjA=" + ], + "frozen ledger keys DEX offer operations|second offer frozen [create-passive-sell][native-credit][both-frozen]" : + [ + "5T6l8LjCUkY=", + "254mVlqDxAE=", + "jDnZQ4ZgABk=", + "dXFJT+pLeTg=", + "7/YQuwhh6TI=", + "bMdsOUzGrZM=", + "1eRHliGXeqs=", + "XSuqNLC2K3o=", + "ccLd/umPUiE=", + "IOM0KKSLRP0=" + ], + "frozen ledger keys DEX offer operations|second offer frozen [create-passive-sell][native-credit][buying-frozen]" : + [ + "5T6l8LjCUkY=", + "254mVlqDxAE=", + "jDnZQ4ZgABk=", + "dXFJT+pLeTg=", + "7/YQuwhh6TI=", + "bMdsOUzGrZM=", + "1eRHliGXeqs=", + "XSuqNLC2K3o=", + "ccLd/umPUiE=", + "IOM0KKSLRP0=" + ], + "frozen ledger keys DEX offer operations|second offer frozen [create-passive-sell][native-credit][selling-frozen]" : + [ + "5T6l8LjCUkY=", + "254mVlqDxAE=", + "jDnZQ4ZgABk=", + "dXFJT+pLeTg=", + "7/YQuwhh6TI=", + "bMdsOUzGrZM=", + "1eRHliGXeqs=", + "XSuqNLC2K3o=", + "ccLd/umPUiE=", + "IOM0KKSLRP0=" + ], + "frozen ledger keys DEX offer operations|second offer frozen [manage-buy][credit-credit][both-frozen]" : + [ + "5T6l8LjCUkY=", + "ccRHbVieTsw=", + "SIk6EzSNNIU=", + "YTXYxQkECjo=", + "D03a2r64awc=", + "u0m/JV4s3Q0=", + "HoyNmTSXTcI=", + "camVDOU0C7I=", + "lVgryQtUCw0=", + "b4bdwQpMOHE=", + "FnMTkn9Jn2E=", + "MW1Dtly8A6o=", + "lvrZR5qo+vs=", + "aRfpkC8fg1w=" + ], + "frozen ledger keys DEX offer operations|second offer frozen [manage-buy][credit-credit][buying-frozen]" : + [ + "5T6l8LjCUkY=", + "ccRHbVieTsw=", + "SIk6EzSNNIU=", + "YTXYxQkECjo=", + "D03a2r64awc=", + "u0m/JV4s3Q0=", + "HoyNmTSXTcI=", + "camVDOU0C7I=", + "lVgryQtUCw0=", + "b4bdwQpMOHE=", + "FnMTkn9Jn2E=", + "MW1Dtly8A6o=", + "lvrZR5qo+vs=", + "aRfpkC8fg1w=" + ], + "frozen ledger keys DEX offer operations|second offer frozen [manage-buy][credit-credit][selling-frozen]" : + [ + "5T6l8LjCUkY=", + "ccRHbVieTsw=", + "SIk6EzSNNIU=", + "YTXYxQkECjo=", + "D03a2r64awc=", + "u0m/JV4s3Q0=", + "HoyNmTSXTcI=", + "camVDOU0C7I=", + "lVgryQtUCw0=", + "b4bdwQpMOHE=", + "FnMTkn9Jn2E=", + "MW1Dtly8A6o=", + "lvrZR5qo+vs=", + "aRfpkC8fg1w=" + ], + "frozen ledger keys DEX offer operations|second offer frozen [manage-buy][credit-native][both-frozen]" : + [ + "5T6l8LjCUkY=", + "6VjQbqbbuAk=", + "jDnZQ4ZgABk=", + "6KQbVLRdk3w=", + "7/YQuwhh6TI=", + "iM5ZOHIqzLg=", + "1eRHliGXeqs=", + "iTycvSyljs0=", + "XkrrDvK4zSs=", + "kgx5PwmlbjA=" + ], + "frozen ledger keys DEX offer operations|second offer frozen [manage-buy][credit-native][buying-frozen]" : + [ + "5T6l8LjCUkY=", + "6VjQbqbbuAk=", + "jDnZQ4ZgABk=", + "6KQbVLRdk3w=", + "7/YQuwhh6TI=", + "iM5ZOHIqzLg=", + "1eRHliGXeqs=", + "iTycvSyljs0=", + "XkrrDvK4zSs=", + "kgx5PwmlbjA=" + ], + "frozen ledger keys DEX offer operations|second offer frozen [manage-buy][credit-native][selling-frozen]" : + [ + "5T6l8LjCUkY=", + "6VjQbqbbuAk=", + "jDnZQ4ZgABk=", + "6KQbVLRdk3w=", + "7/YQuwhh6TI=", + "iM5ZOHIqzLg=", + "1eRHliGXeqs=", + "iTycvSyljs0=", + "XkrrDvK4zSs=", + "kgx5PwmlbjA=" + ], + "frozen ledger keys DEX offer operations|second offer frozen [manage-buy][native-credit][both-frozen]" : + [ + "5T6l8LjCUkY=", + "254mVlqDxAE=", + "jDnZQ4ZgABk=", + "dXFJT+pLeTg=", + "7/YQuwhh6TI=", + "bMdsOUzGrZM=", + "1eRHliGXeqs=", + "XSuqNLC2K3o=", + "ccLd/umPUiE=", + "IOM0KKSLRP0=" + ], + "frozen ledger keys DEX offer operations|second offer frozen [manage-buy][native-credit][buying-frozen]" : + [ + "5T6l8LjCUkY=", + "254mVlqDxAE=", + "jDnZQ4ZgABk=", + "dXFJT+pLeTg=", + "7/YQuwhh6TI=", + "bMdsOUzGrZM=", + "1eRHliGXeqs=", + "XSuqNLC2K3o=", + "ccLd/umPUiE=", + "IOM0KKSLRP0=" + ], + "frozen ledger keys DEX offer operations|second offer frozen [manage-buy][native-credit][selling-frozen]" : + [ + "5T6l8LjCUkY=", + "254mVlqDxAE=", + "jDnZQ4ZgABk=", + "dXFJT+pLeTg=", + "7/YQuwhh6TI=", + "bMdsOUzGrZM=", + "1eRHliGXeqs=", + "XSuqNLC2K3o=", + "ccLd/umPUiE=", + "IOM0KKSLRP0=" + ], + "frozen ledger keys DEX offer operations|second offer frozen [manage-sell][credit-credit][both-frozen]" : + [ + "5T6l8LjCUkY=", + "ccRHbVieTsw=", + "SIk6EzSNNIU=", + "YTXYxQkECjo=", + "D03a2r64awc=", + "u0m/JV4s3Q0=", + "HoyNmTSXTcI=", + "camVDOU0C7I=", + "lVgryQtUCw0=", + "b4bdwQpMOHE=", + "FnMTkn9Jn2E=", + "MW1Dtly8A6o=", + "lvrZR5qo+vs=", + "aRfpkC8fg1w=" + ], + "frozen ledger keys DEX offer operations|second offer frozen [manage-sell][credit-credit][buying-frozen]" : + [ + "5T6l8LjCUkY=", + "ccRHbVieTsw=", + "SIk6EzSNNIU=", + "YTXYxQkECjo=", + "D03a2r64awc=", + "u0m/JV4s3Q0=", + "HoyNmTSXTcI=", + "camVDOU0C7I=", + "lVgryQtUCw0=", + "b4bdwQpMOHE=", + "FnMTkn9Jn2E=", + "MW1Dtly8A6o=", + "lvrZR5qo+vs=", + "aRfpkC8fg1w=" + ], + "frozen ledger keys DEX offer operations|second offer frozen [manage-sell][credit-credit][selling-frozen]" : + [ + "5T6l8LjCUkY=", + "ccRHbVieTsw=", + "SIk6EzSNNIU=", + "YTXYxQkECjo=", + "D03a2r64awc=", + "u0m/JV4s3Q0=", + "HoyNmTSXTcI=", + "camVDOU0C7I=", + "lVgryQtUCw0=", + "b4bdwQpMOHE=", + "FnMTkn9Jn2E=", + "MW1Dtly8A6o=", + "lvrZR5qo+vs=", + "aRfpkC8fg1w=" + ], + "frozen ledger keys DEX offer operations|second offer frozen [manage-sell][credit-native][both-frozen]" : + [ + "5T6l8LjCUkY=", + "6VjQbqbbuAk=", + "jDnZQ4ZgABk=", + "6KQbVLRdk3w=", + "7/YQuwhh6TI=", + "iM5ZOHIqzLg=", + "1eRHliGXeqs=", + "iTycvSyljs0=", + "XkrrDvK4zSs=", + "kgx5PwmlbjA=" + ], + "frozen ledger keys DEX offer operations|second offer frozen [manage-sell][credit-native][buying-frozen]" : + [ + "5T6l8LjCUkY=", + "6VjQbqbbuAk=", + "jDnZQ4ZgABk=", + "6KQbVLRdk3w=", + "7/YQuwhh6TI=", + "iM5ZOHIqzLg=", + "1eRHliGXeqs=", + "iTycvSyljs0=", + "XkrrDvK4zSs=", + "kgx5PwmlbjA=" + ], + "frozen ledger keys DEX offer operations|second offer frozen [manage-sell][credit-native][selling-frozen]" : + [ + "5T6l8LjCUkY=", + "6VjQbqbbuAk=", + "jDnZQ4ZgABk=", + "6KQbVLRdk3w=", + "7/YQuwhh6TI=", + "iM5ZOHIqzLg=", + "1eRHliGXeqs=", + "iTycvSyljs0=", + "XkrrDvK4zSs=", + "kgx5PwmlbjA=" + ], + "frozen ledger keys DEX offer operations|second offer frozen [manage-sell][native-credit][both-frozen]" : + [ + "5T6l8LjCUkY=", + "254mVlqDxAE=", + "jDnZQ4ZgABk=", + "dXFJT+pLeTg=", + "7/YQuwhh6TI=", + "bMdsOUzGrZM=", + "1eRHliGXeqs=", + "XSuqNLC2K3o=", + "ccLd/umPUiE=", + "IOM0KKSLRP0=" + ], + "frozen ledger keys DEX offer operations|second offer frozen [manage-sell][native-credit][buying-frozen]" : + [ + "5T6l8LjCUkY=", + "254mVlqDxAE=", + "jDnZQ4ZgABk=", + "dXFJT+pLeTg=", + "7/YQuwhh6TI=", + "bMdsOUzGrZM=", + "1eRHliGXeqs=", + "XSuqNLC2K3o=", + "ccLd/umPUiE=", + "IOM0KKSLRP0=" + ], + "frozen ledger keys DEX offer operations|second offer frozen [manage-sell][native-credit][selling-frozen]" : + [ + "5T6l8LjCUkY=", + "254mVlqDxAE=", + "jDnZQ4ZgABk=", + "dXFJT+pLeTg=", + "7/YQuwhh6TI=", + "bMdsOUzGrZM=", + "1eRHliGXeqs=", + "XSuqNLC2K3o=", + "ccLd/umPUiE=", + "IOM0KKSLRP0=" + ], + "frozen ledger keys DEX path payments" : + [ + "aY2C4p/9qcw=", + "zJ9sGQUvfGQ=", + "7fYvXIbcZyE=", + "i6aZ1T5L9P8=", + "U4Pbx19vJ8M=", + "HiIoDPEX+eE=", + "uDXezWa8xNQ=", + "T/GEMUgvPtQ=", + "+kr9gYlStNc=", + "/reDqinGgXY=", + "OXwiBQJqOns=", + "5BuQgUu787I=", + "38Z6pSP9oI4=", + "Zz623bsxVkA=", + "U6QHjN+rP8g=", + "tX5RfCrgFH0=", + "Aqw277PxgNk=", + "BrcCIiO0L+s=", + "6xsrkSKswk0=", + "JEIuT9C8xmE=", + "eqlPRZunMmE=", + "P5PONQu76y0=", + "lZIRo+LomGw=", + "bNJtJaqUne0=", + "AXy4uiwhM+I=", + "/w9rxRGo2Qw=", + "eO7T3jT6jBM=", + "t8zuSIlPQiQ=", + "9QCBdUrXc6k=", + "GrNiQ/qnODo=", + "aY2C4p/9qcw=", + "zJ9sGQUvfGQ=", + "7fYvXIbcZyE=", + "i6aZ1T5L9P8=", + "U4Pbx19vJ8M=", + "HiIoDPEX+eE=", + "uDXezWa8xNQ=", + "T/GEMUgvPtQ=", + "+kr9gYlStNc=", + "/reDqinGgXY=", + "OXwiBQJqOns=", + "5BuQgUu787I=", + "38Z6pSP9oI4=", + "Zz623bsxVkA=", + "U6QHjN+rP8g=", + "tX5RfCrgFH0=", + "Aqw277PxgNk=", + "BrcCIiO0L+s=", + "6xsrkSKswk0=", + "JEIuT9C8xmE=", + "eqlPRZunMmE=", + "P5PONQu76y0=", + "lZIRo+LomGw=", + "bNJtJaqUne0=", + "AXy4uiwhM+I=", + "/w9rxRGo2Qw=", + "eO7T3jT6jBM=", + "t8zuSIlPQiQ=", + "9QCBdUrXc6k=", + "GrNiQ/qnODo=", + "aY2C4p/9qcw=", + "zJ9sGQUvfGQ=", + "7fYvXIbcZyE=", + "i6aZ1T5L9P8=", + "U4Pbx19vJ8M=", + "HiIoDPEX+eE=", + "uDXezWa8xNQ=", + "T/GEMUgvPtQ=", + "+kr9gYlStNc=", + "/reDqinGgXY=", + "OXwiBQJqOns=", + "5BuQgUu787I=", + "38Z6pSP9oI4=", + "Zz623bsxVkA=", + "U6QHjN+rP8g=", + "tX5RfCrgFH0=", + "Aqw277PxgNk=", + "BrcCIiO0L+s=", + "6xsrkSKswk0=", + "JEIuT9C8xmE=", + "eqlPRZunMmE=", + "P5PONQu76y0=", + "lZIRo+LomGw=", + "bNJtJaqUne0=", + "AXy4uiwhM+I=", + "/w9rxRGo2Qw=", + "eO7T3jT6jBM=", + "t8zuSIlPQiQ=", + "9QCBdUrXc6k=", + "GrNiQ/qnODo=", + "aY2C4p/9qcw=", + "zJ9sGQUvfGQ=", + "7fYvXIbcZyE=", + "i6aZ1T5L9P8=", + "U4Pbx19vJ8M=", + "HiIoDPEX+eE=", + "uDXezWa8xNQ=", + "T/GEMUgvPtQ=", + "+kr9gYlStNc=", + "/reDqinGgXY=", + "OXwiBQJqOns=", + "5BuQgUu787I=", + "38Z6pSP9oI4=", + "Zz623bsxVkA=", + "U6QHjN+rP8g=", + "tX5RfCrgFH0=", + "Aqw277PxgNk=", + "BrcCIiO0L+s=", + "6xsrkSKswk0=", + "JEIuT9C8xmE=", + "eqlPRZunMmE=", + "P5PONQu76y0=", + "lZIRo+LomGw=", + "bNJtJaqUne0=", + "AXy4uiwhM+I=", + "/w9rxRGo2Qw=", + "eO7T3jT6jBM=", + "t8zuSIlPQiQ=", + "9QCBdUrXc6k=", + "GrNiQ/qnODo=", + "aY2C4p/9qcw=", + "zJ9sGQUvfGQ=", + "7fYvXIbcZyE=", + "i6aZ1T5L9P8=", + "U4Pbx19vJ8M=", + "HiIoDPEX+eE=", + "uDXezWa8xNQ=", + "T/GEMUgvPtQ=", + "+kr9gYlStNc=", + "/reDqinGgXY=", + "OXwiBQJqOns=", + "5BuQgUu787I=", + "38Z6pSP9oI4=", + "Zz623bsxVkA=", + "U6QHjN+rP8g=", + "tX5RfCrgFH0=", + "Aqw277PxgNk=", + "BrcCIiO0L+s=", + "6xsrkSKswk0=", + "JEIuT9C8xmE=", + "eqlPRZunMmE=", + "P5PONQu76y0=", + "lZIRo+LomGw=", + "bNJtJaqUne0=", + "AXy4uiwhM+I=", + "/w9rxRGo2Qw=", + "eO7T3jT6jBM=", + "t8zuSIlPQiQ=", + "9QCBdUrXc6k=", + "GrNiQ/qnODo=", + "aY2C4p/9qcw=", + "zJ9sGQUvfGQ=", + "7fYvXIbcZyE=", + "i6aZ1T5L9P8=", + "U4Pbx19vJ8M=", + "HiIoDPEX+eE=", + "uDXezWa8xNQ=", + "T/GEMUgvPtQ=", + "+kr9gYlStNc=", + "/reDqinGgXY=", + "OXwiBQJqOns=", + "5BuQgUu787I=", + "38Z6pSP9oI4=", + "Zz623bsxVkA=", + "U6QHjN+rP8g=", + "tX5RfCrgFH0=", + "Aqw277PxgNk=", + "BrcCIiO0L+s=", + "6xsrkSKswk0=", + "JEIuT9C8xmE=", + "eqlPRZunMmE=", + "P5PONQu76y0=", + "lZIRo+LomGw=", + "bNJtJaqUne0=", + "AXy4uiwhM+I=", + "/w9rxRGo2Qw=", + "eO7T3jT6jBM=", + "t8zuSIlPQiQ=", + "9QCBdUrXc6k=", + "GrNiQ/qnODo=", + "aY2C4p/9qcw=", + "zJ9sGQUvfGQ=", + "7fYvXIbcZyE=", + "i6aZ1T5L9P8=", + "U4Pbx19vJ8M=", + "HiIoDPEX+eE=", + "uDXezWa8xNQ=", + "T/GEMUgvPtQ=", + "+kr9gYlStNc=", + "/reDqinGgXY=", + "OXwiBQJqOns=", + "5BuQgUu787I=", + "38Z6pSP9oI4=", + "Zz623bsxVkA=", + "U6QHjN+rP8g=", + "tX5RfCrgFH0=", + "Aqw277PxgNk=", + "BrcCIiO0L+s=", + "6xsrkSKswk0=", + "JEIuT9C8xmE=", + "eqlPRZunMmE=", + "P5PONQu76y0=", + "lZIRo+LomGw=", + "bNJtJaqUne0=", + "AXy4uiwhM+I=", + "/w9rxRGo2Qw=", + "eO7T3jT6jBM=", + "t8zuSIlPQiQ=", + "9QCBdUrXc6k=", + "GrNiQ/qnODo=", + "aY2C4p/9qcw=", + "zJ9sGQUvfGQ=", + "7fYvXIbcZyE=", + "i6aZ1T5L9P8=", + "U4Pbx19vJ8M=", + "HiIoDPEX+eE=", + "uDXezWa8xNQ=", + "T/GEMUgvPtQ=", + "+kr9gYlStNc=", + "/reDqinGgXY=", + "OXwiBQJqOns=", + "5BuQgUu787I=", + "38Z6pSP9oI4=", + "Zz623bsxVkA=", + "U6QHjN+rP8g=", + "tX5RfCrgFH0=", + "Aqw277PxgNk=", + "BrcCIiO0L+s=", + "6xsrkSKswk0=", + "JEIuT9C8xmE=", + "eqlPRZunMmE=", + "P5PONQu76y0=", + "lZIRo+LomGw=", + "bNJtJaqUne0=", + "AXy4uiwhM+I=", + "/w9rxRGo2Qw=", + "eO7T3jT6jBM=", + "t8zuSIlPQiQ=", + "9QCBdUrXc6k=", + "GrNiQ/qnODo=", + "aY2C4p/9qcw=", + "zJ9sGQUvfGQ=", + "7fYvXIbcZyE=", + "i6aZ1T5L9P8=", + "U4Pbx19vJ8M=", + "HiIoDPEX+eE=", + "uDXezWa8xNQ=", + "T/GEMUgvPtQ=", + "+kr9gYlStNc=", + "/reDqinGgXY=", + "OXwiBQJqOns=", + "5BuQgUu787I=", + "38Z6pSP9oI4=", + "Zz623bsxVkA=", + "U6QHjN+rP8g=", + "tX5RfCrgFH0=", + "Aqw277PxgNk=", + "BrcCIiO0L+s=", + "6xsrkSKswk0=", + "JEIuT9C8xmE=", + "eqlPRZunMmE=", + "P5PONQu76y0=", + "lZIRo+LomGw=", + "bNJtJaqUne0=", + "AXy4uiwhM+I=", + "/w9rxRGo2Qw=", + "eO7T3jT6jBM=", + "t8zuSIlPQiQ=", + "9QCBdUrXc6k=", + "GrNiQ/qnODo=", + "aY2C4p/9qcw=", + "zJ9sGQUvfGQ=", + "7fYvXIbcZyE=", + "i6aZ1T5L9P8=", + "U4Pbx19vJ8M=", + "HiIoDPEX+eE=", + "uDXezWa8xNQ=", + "T/GEMUgvPtQ=", + "+kr9gYlStNc=", + "/reDqinGgXY=", + "OXwiBQJqOns=", + "5BuQgUu787I=", + "38Z6pSP9oI4=", + "Zz623bsxVkA=", + "U6QHjN+rP8g=", + "tX5RfCrgFH0=", + "Aqw277PxgNk=", + "BrcCIiO0L+s=", + "6xsrkSKswk0=", + "JEIuT9C8xmE=", + "eqlPRZunMmE=", + "P5PONQu76y0=", + "lZIRo+LomGw=", + "bNJtJaqUne0=", + "AXy4uiwhM+I=", + "/w9rxRGo2Qw=", + "eO7T3jT6jBM=", + "t8zuSIlPQiQ=", + "9QCBdUrXc6k=", + "GrNiQ/qnODo=", + "aY2C4p/9qcw=", + "zJ9sGQUvfGQ=", + "7fYvXIbcZyE=", + "i6aZ1T5L9P8=", + "U4Pbx19vJ8M=", + "HiIoDPEX+eE=", + "uDXezWa8xNQ=", + "T/GEMUgvPtQ=", + "+kr9gYlStNc=", + "/reDqinGgXY=", + "OXwiBQJqOns=", + "5BuQgUu787I=", + "38Z6pSP9oI4=", + "Zz623bsxVkA=", + "U6QHjN+rP8g=", + "tX5RfCrgFH0=", + "Aqw277PxgNk=", + "BrcCIiO0L+s=", + "6xsrkSKswk0=", + "JEIuT9C8xmE=", + "eqlPRZunMmE=", + "P5PONQu76y0=", + "lZIRo+LomGw=", + "bNJtJaqUne0=", + "AXy4uiwhM+I=", + "/w9rxRGo2Qw=", + "eO7T3jT6jBM=", + "t8zuSIlPQiQ=", + "9QCBdUrXc6k=", + "GrNiQ/qnODo=", + "aY2C4p/9qcw=", + "zJ9sGQUvfGQ=", + "7fYvXIbcZyE=", + "i6aZ1T5L9P8=", + "U4Pbx19vJ8M=", + "HiIoDPEX+eE=", + "uDXezWa8xNQ=", + "T/GEMUgvPtQ=", + "+kr9gYlStNc=", + "/reDqinGgXY=", + "OXwiBQJqOns=", + "5BuQgUu787I=", + "38Z6pSP9oI4=", + "Zz623bsxVkA=", + "U6QHjN+rP8g=", + "tX5RfCrgFH0=", + "Aqw277PxgNk=", + "BrcCIiO0L+s=", + "6xsrkSKswk0=", + "JEIuT9C8xmE=", + "eqlPRZunMmE=", + "P5PONQu76y0=", + "lZIRo+LomGw=", + "bNJtJaqUne0=", + "AXy4uiwhM+I=", + "/w9rxRGo2Qw=", + "eO7T3jT6jBM=", + "t8zuSIlPQiQ=", + "9QCBdUrXc6k=", + "GrNiQ/qnODo=", + "aY2C4p/9qcw=", + "zJ9sGQUvfGQ=", + "7fYvXIbcZyE=", + "i6aZ1T5L9P8=", + "U4Pbx19vJ8M=", + "HiIoDPEX+eE=", + "uDXezWa8xNQ=", + "T/GEMUgvPtQ=", + "+kr9gYlStNc=", + "/reDqinGgXY=", + "OXwiBQJqOns=", + "5BuQgUu787I=", + "38Z6pSP9oI4=", + "Zz623bsxVkA=", + "U6QHjN+rP8g=", + "tX5RfCrgFH0=", + "Aqw277PxgNk=", + "BrcCIiO0L+s=", + "6xsrkSKswk0=", + "JEIuT9C8xmE=", + "eqlPRZunMmE=", + "P5PONQu76y0=", + "lZIRo+LomGw=", + "bNJtJaqUne0=", + "AXy4uiwhM+I=", + "/w9rxRGo2Qw=", + "eO7T3jT6jBM=", + "t8zuSIlPQiQ=", + "9QCBdUrXc6k=", + "GrNiQ/qnODo=", + "aY2C4p/9qcw=", + "zJ9sGQUvfGQ=", + "7fYvXIbcZyE=", + "i6aZ1T5L9P8=", + "U4Pbx19vJ8M=", + "HiIoDPEX+eE=", + "uDXezWa8xNQ=", + "T/GEMUgvPtQ=", + "+kr9gYlStNc=", + "/reDqinGgXY=", + "OXwiBQJqOns=", + "5BuQgUu787I=", + "38Z6pSP9oI4=", + "Zz623bsxVkA=", + "U6QHjN+rP8g=", + "tX5RfCrgFH0=", + "Aqw277PxgNk=", + "BrcCIiO0L+s=", + "6xsrkSKswk0=", + "JEIuT9C8xmE=", + "eqlPRZunMmE=", + "P5PONQu76y0=", + "lZIRo+LomGw=", + "bNJtJaqUne0=", + "AXy4uiwhM+I=", + "/w9rxRGo2Qw=", + "eO7T3jT6jBM=", + "t8zuSIlPQiQ=", + "9QCBdUrXc6k=", + "GrNiQ/qnODo=", + "aY2C4p/9qcw=", + "zJ9sGQUvfGQ=", + "7fYvXIbcZyE=", + "i6aZ1T5L9P8=", + "U4Pbx19vJ8M=", + "HiIoDPEX+eE=", + "uDXezWa8xNQ=", + "T/GEMUgvPtQ=", + "+kr9gYlStNc=", + "/reDqinGgXY=", + "OXwiBQJqOns=", + "5BuQgUu787I=", + "38Z6pSP9oI4=", + "Zz623bsxVkA=", + "U6QHjN+rP8g=", + "tX5RfCrgFH0=", + "Aqw277PxgNk=", + "BrcCIiO0L+s=", + "6xsrkSKswk0=", + "JEIuT9C8xmE=", + "eqlPRZunMmE=", + "P5PONQu76y0=", + "lZIRo+LomGw=", + "bNJtJaqUne0=", + "AXy4uiwhM+I=", + "/w9rxRGo2Qw=", + "eO7T3jT6jBM=", + "t8zuSIlPQiQ=", + "9QCBdUrXc6k=", + "GrNiQ/qnODo=", + "aY2C4p/9qcw=", + "zJ9sGQUvfGQ=", + "7fYvXIbcZyE=", + "i6aZ1T5L9P8=", + "U4Pbx19vJ8M=", + "HiIoDPEX+eE=", + "uDXezWa8xNQ=", + "T/GEMUgvPtQ=", + "+kr9gYlStNc=", + "/reDqinGgXY=", + "OXwiBQJqOns=", + "5BuQgUu787I=", + "38Z6pSP9oI4=", + "Zz623bsxVkA=", + "U6QHjN+rP8g=", + "tX5RfCrgFH0=", + "Aqw277PxgNk=", + "BrcCIiO0L+s=", + "6xsrkSKswk0=", + "JEIuT9C8xmE=", + "eqlPRZunMmE=", + "P5PONQu76y0=", + "lZIRo+LomGw=", + "bNJtJaqUne0=", + "AXy4uiwhM+I=", + "/w9rxRGo2Qw=", + "eO7T3jT6jBM=", + "t8zuSIlPQiQ=", + "9QCBdUrXc6k=", + "GrNiQ/qnODo=", + "aY2C4p/9qcw=", + "zJ9sGQUvfGQ=", + "7fYvXIbcZyE=", + "i6aZ1T5L9P8=", + "U4Pbx19vJ8M=", + "HiIoDPEX+eE=", + "uDXezWa8xNQ=", + "T/GEMUgvPtQ=", + "+kr9gYlStNc=", + "/reDqinGgXY=", + "OXwiBQJqOns=", + "5BuQgUu787I=", + "38Z6pSP9oI4=", + "Zz623bsxVkA=", + "U6QHjN+rP8g=", + "tX5RfCrgFH0=", + "Aqw277PxgNk=", + "BrcCIiO0L+s=", + "6xsrkSKswk0=", + "JEIuT9C8xmE=", + "eqlPRZunMmE=", + "P5PONQu76y0=", + "lZIRo+LomGw=", + "bNJtJaqUne0=", + "AXy4uiwhM+I=", + "/w9rxRGo2Qw=", + "eO7T3jT6jBM=", + "t8zuSIlPQiQ=", + "9QCBdUrXc6k=", + "GrNiQ/qnODo=", + "aY2C4p/9qcw=", + "zJ9sGQUvfGQ=", + "7fYvXIbcZyE=", + "i6aZ1T5L9P8=", + "U4Pbx19vJ8M=", + "HiIoDPEX+eE=", + "uDXezWa8xNQ=", + "T/GEMUgvPtQ=", + "+kr9gYlStNc=", + "/reDqinGgXY=", + "OXwiBQJqOns=", + "5BuQgUu787I=", + "38Z6pSP9oI4=", + "Zz623bsxVkA=", + "U6QHjN+rP8g=", + "tX5RfCrgFH0=", + "Aqw277PxgNk=", + "BrcCIiO0L+s=", + "6xsrkSKswk0=", + "JEIuT9C8xmE=", + "eqlPRZunMmE=", + "P5PONQu76y0=", + "lZIRo+LomGw=", + "bNJtJaqUne0=", + "AXy4uiwhM+I=", + "/w9rxRGo2Qw=", + "eO7T3jT6jBM=", + "t8zuSIlPQiQ=", + "9QCBdUrXc6k=", + "GrNiQ/qnODo=" + ], + "frozen ledger keys apply time validation" : + [ + "tMoQ2Vse5tc=", + "xORLh0b3QZY=", + "zmU28zD7PUE=", + "GpZOJFWuGak=", + "Qx1IqUzjv2w=", + "VPGZIGfI6Rg=", + "bXNUiwR2HR0=", + "PAmiGhwY1GA=", + "NHP1/DNlYAw=", + "y2oaGajRq7Y=", + "tMoQ2Vse5tc=", + "xORLh0b3QZY=", + "zmU28zD7PUE=", + "GpZOJFWuGak=", + "Qx1IqUzjv2w=", + "VPGZIGfI6Rg=", + "bXNUiwR2HR0=", + "PAmiGhwY1GA=", + "NHP1/DNlYAw=", + "y2oaGajRq7Y=", + "tMoQ2Vse5tc=", + "xORLh0b3QZY=", + "zmU28zD7PUE=", + "GpZOJFWuGak=", + "Qx1IqUzjv2w=", + "VPGZIGfI6Rg=", + "bXNUiwR2HR0=", + "PAmiGhwY1GA=", + "NHP1/DNlYAw=", + "y2oaGajRq7Y=", + "tMoQ2Vse5tc=", + "xORLh0b3QZY=", + "zmU28zD7PUE=", + "GpZOJFWuGak=", + "Qx1IqUzjv2w=", + "VPGZIGfI6Rg=", + "bXNUiwR2HR0=", + "PAmiGhwY1GA=", + "NHP1/DNlYAw=", + "y2oaGajRq7Y=", + "tMoQ2Vse5tc=", + "xORLh0b3QZY=", + "zmU28zD7PUE=", + "GpZOJFWuGak=", + "Qx1IqUzjv2w=", + "VPGZIGfI6Rg=", + "bXNUiwR2HR0=", + "PAmiGhwY1GA=", + "NHP1/DNlYAw=", + "y2oaGajRq7Y=" + ], + "frozen ledger keys apply time validation|claim claimable balance trustline frozen" : [ "KWQKyjBIFco=" ], + "frozen ledger keys apply time validation|liquidity pool deposit assetA trustline frozen" : [ "68zQEnUoWak=", "94Lxi/EzWuw=" ], + "frozen ledger keys apply time validation|liquidity pool deposit assetB trustline frozen" : [ "68zQEnUoWak=", "94Lxi/EzWuw=" ], + "frozen ledger keys apply time validation|liquidity pool withdraw assetA trustline frozen" : [ "68zQEnUoWak=", "94Lxi/EzWuw=" ], + "frozen ledger keys apply time validation|liquidity pool withdraw assetB trustline frozen" : [ "68zQEnUoWak=", "94Lxi/EzWuw=" ], + "frozen ledger keys in Soroban footprint" : [ "E/Hp+63+Q2Y=" ], + "operation destination frozen" : + [ + "tMoQ2Vse5tc=", + "xORLh0b3QZY=", + "zmU28zD7PUE=", + "GpZOJFWuGak=", + "Qx1IqUzjv2w=", + "VPGZIGfI6Rg=", + "bXNUiwR2HR0=", + "PAmiGhwY1GA=", + "NHP1/DNlYAw=", + "y2oaGajRq7Y=", + "KmiMr73908A=", + "tMoQ2Vse5tc=", + "xORLh0b3QZY=", + "zmU28zD7PUE=", + "GpZOJFWuGak=", + "Qx1IqUzjv2w=", + "VPGZIGfI6Rg=", + "bXNUiwR2HR0=", + "PAmiGhwY1GA=", + "NHP1/DNlYAw=", + "y2oaGajRq7Y=", + "KmiMr73908A=", + "tMoQ2Vse5tc=", + "xORLh0b3QZY=", + "zmU28zD7PUE=", + "GpZOJFWuGak=", + "Qx1IqUzjv2w=", + "VPGZIGfI6Rg=", + "bXNUiwR2HR0=", + "PAmiGhwY1GA=", + "NHP1/DNlYAw=", + "y2oaGajRq7Y=", + "KmiMr73908A=", + "tMoQ2Vse5tc=", + "xORLh0b3QZY=", + "zmU28zD7PUE=", + "GpZOJFWuGak=", + "Qx1IqUzjv2w=", + "VPGZIGfI6Rg=", + "bXNUiwR2HR0=", + "PAmiGhwY1GA=", + "NHP1/DNlYAw=", + "y2oaGajRq7Y=", + "KmiMr73908A=", + "tMoQ2Vse5tc=", + "xORLh0b3QZY=", + "zmU28zD7PUE=", + "GpZOJFWuGak=", + "Qx1IqUzjv2w=", + "VPGZIGfI6Rg=", + "bXNUiwR2HR0=", + "PAmiGhwY1GA=", + "NHP1/DNlYAw=", + "y2oaGajRq7Y=", + "KmiMr73908A=", + "tMoQ2Vse5tc=", + "xORLh0b3QZY=", + "zmU28zD7PUE=", + "GpZOJFWuGak=", + "Qx1IqUzjv2w=", + "VPGZIGfI6Rg=", + "bXNUiwR2HR0=", + "PAmiGhwY1GA=", + "NHP1/DNlYAw=", + "y2oaGajRq7Y=", + "KmiMr73908A=", + "tMoQ2Vse5tc=", + "xORLh0b3QZY=", + "zmU28zD7PUE=", + "GpZOJFWuGak=", + "Qx1IqUzjv2w=", + "VPGZIGfI6Rg=", + "bXNUiwR2HR0=", + "PAmiGhwY1GA=", + "NHP1/DNlYAw=", + "y2oaGajRq7Y=", + "KmiMr73908A=", + "tMoQ2Vse5tc=", + "xORLh0b3QZY=", + "zmU28zD7PUE=", + "GpZOJFWuGak=", + "Qx1IqUzjv2w=", + "VPGZIGfI6Rg=", + "bXNUiwR2HR0=", + "PAmiGhwY1GA=", + "NHP1/DNlYAw=", + "y2oaGajRq7Y=", + "KmiMr73908A=", + "tMoQ2Vse5tc=", + "xORLh0b3QZY=", + "zmU28zD7PUE=", + "GpZOJFWuGak=", + "Qx1IqUzjv2w=", + "VPGZIGfI6Rg=", + "bXNUiwR2HR0=", + "PAmiGhwY1GA=", + "NHP1/DNlYAw=", + "y2oaGajRq7Y=", + "KmiMr73908A=", + "tMoQ2Vse5tc=", + "xORLh0b3QZY=", + "zmU28zD7PUE=", + "GpZOJFWuGak=", + "Qx1IqUzjv2w=", + "VPGZIGfI6Rg=", + "bXNUiwR2HR0=", + "PAmiGhwY1GA=", + "NHP1/DNlYAw=", + "y2oaGajRq7Y=", + "KmiMr73908A=", + "tMoQ2Vse5tc=", + "xORLh0b3QZY=", + "zmU28zD7PUE=", + "GpZOJFWuGak=", + "Qx1IqUzjv2w=", + "VPGZIGfI6Rg=", + "bXNUiwR2HR0=", + "PAmiGhwY1GA=", + "NHP1/DNlYAw=", + "y2oaGajRq7Y=", + "KmiMr73908A=", + "tMoQ2Vse5tc=", + "xORLh0b3QZY=", + "zmU28zD7PUE=", + "GpZOJFWuGak=", + "Qx1IqUzjv2w=", + "VPGZIGfI6Rg=", + "bXNUiwR2HR0=", + "PAmiGhwY1GA=", + "NHP1/DNlYAw=", + "y2oaGajRq7Y=", + "KmiMr73908A=", + "tMoQ2Vse5tc=", + "xORLh0b3QZY=", + "zmU28zD7PUE=", + "GpZOJFWuGak=", + "Qx1IqUzjv2w=", + "VPGZIGfI6Rg=", + "bXNUiwR2HR0=", + "PAmiGhwY1GA=", + "NHP1/DNlYAw=", + "y2oaGajRq7Y=", + "KmiMr73908A=" + ], + "operation destination frozen|ClawbackOp from trustline frozen" : [ "dl3b5XRie08=", "6Umcp50dy1w=", "NCE6KjdBf2I=", "6eJJgJRJRVk=" ], + "operation destination frozen|SetTrustLineFlagsOp trustor trustline frozen" : [ "FFNGCyg5dJ4=" ], + "source account frozen" : + [ + "cpVo6Aho2ao=", + "IBmLxZVWm4Y=", + "cpVo6Aho2ao=", + "IBmLxZVWm4Y=", + "cpVo6Aho2ao=", + "IBmLxZVWm4Y=", + "cpVo6Aho2ao=", + "IBmLxZVWm4Y=", + "cpVo6Aho2ao=", + "IBmLxZVWm4Y=", + "cpVo6Aho2ao=", + "IBmLxZVWm4Y=", + "cpVo6Aho2ao=", + "IBmLxZVWm4Y=", + "cpVo6Aho2ao=", + "IBmLxZVWm4Y=" + ], + "source account frozen|fee bump source account frozen" : [ "6CmtErdDHKk=" ], + "source account frozen|one of multiple ops source account frozen" : [ "OnSCl340l+U=", "OBYw8xSVvlY=" ], + "source account frozen|op source account frozen" : [ "Z4OpilPA/jk=" ], + "source account frozen|op source frozen via muxed account ID" : [ "R01UcLdO4lI=" ], + "source trustline frozen" : + [ + "tMoQ2Vse5tc=", + "xORLh0b3QZY=", + "zmU28zD7PUE=", + "GpZOJFWuGak=", + "Qx1IqUzjv2w=", + "VPGZIGfI6Rg=", + "bXNUiwR2HR0=", + "PAmiGhwY1GA=", + "NHP1/DNlYAw=", + "y2oaGajRq7Y=", + "KmiMr73908A=", + "tMoQ2Vse5tc=", + "xORLh0b3QZY=", + "zmU28zD7PUE=", + "GpZOJFWuGak=", + "Qx1IqUzjv2w=", + "VPGZIGfI6Rg=", + "bXNUiwR2HR0=", + "PAmiGhwY1GA=", + "NHP1/DNlYAw=", + "y2oaGajRq7Y=", + "KmiMr73908A=", + "tMoQ2Vse5tc=", + "xORLh0b3QZY=", + "zmU28zD7PUE=", + "GpZOJFWuGak=", + "Qx1IqUzjv2w=", + "VPGZIGfI6Rg=", + "bXNUiwR2HR0=", + "PAmiGhwY1GA=", + "NHP1/DNlYAw=", + "y2oaGajRq7Y=", + "KmiMr73908A=", + "tMoQ2Vse5tc=", + "xORLh0b3QZY=", + "zmU28zD7PUE=", + "GpZOJFWuGak=", + "Qx1IqUzjv2w=", + "VPGZIGfI6Rg=", + "bXNUiwR2HR0=", + "PAmiGhwY1GA=", + "NHP1/DNlYAw=", + "y2oaGajRq7Y=", + "KmiMr73908A=", + "tMoQ2Vse5tc=", + "xORLh0b3QZY=", + "zmU28zD7PUE=", + "GpZOJFWuGak=", + "Qx1IqUzjv2w=", + "VPGZIGfI6Rg=", + "bXNUiwR2HR0=", + "PAmiGhwY1GA=", + "NHP1/DNlYAw=", + "y2oaGajRq7Y=", + "KmiMr73908A=", + "tMoQ2Vse5tc=", + "xORLh0b3QZY=", + "zmU28zD7PUE=", + "GpZOJFWuGak=", + "Qx1IqUzjv2w=", + "VPGZIGfI6Rg=", + "bXNUiwR2HR0=", + "PAmiGhwY1GA=", + "NHP1/DNlYAw=", + "y2oaGajRq7Y=", + "KmiMr73908A=", + "tMoQ2Vse5tc=", + "xORLh0b3QZY=", + "zmU28zD7PUE=", + "GpZOJFWuGak=", + "Qx1IqUzjv2w=", + "VPGZIGfI6Rg=", + "bXNUiwR2HR0=", + "PAmiGhwY1GA=", + "NHP1/DNlYAw=", + "y2oaGajRq7Y=", + "KmiMr73908A=", + "tMoQ2Vse5tc=", + "xORLh0b3QZY=", + "zmU28zD7PUE=", + "GpZOJFWuGak=", + "Qx1IqUzjv2w=", + "VPGZIGfI6Rg=", + "bXNUiwR2HR0=", + "PAmiGhwY1GA=", + "NHP1/DNlYAw=", + "y2oaGajRq7Y=", + "KmiMr73908A=", + "tMoQ2Vse5tc=", + "xORLh0b3QZY=", + "zmU28zD7PUE=", + "GpZOJFWuGak=", + "Qx1IqUzjv2w=", + "VPGZIGfI6Rg=", + "bXNUiwR2HR0=", + "PAmiGhwY1GA=", + "NHP1/DNlYAw=", + "y2oaGajRq7Y=", + "KmiMr73908A=", + "tMoQ2Vse5tc=", + "xORLh0b3QZY=", + "zmU28zD7PUE=", + "GpZOJFWuGak=", + "Qx1IqUzjv2w=", + "VPGZIGfI6Rg=", + "bXNUiwR2HR0=", + "PAmiGhwY1GA=", + "NHP1/DNlYAw=", + "y2oaGajRq7Y=", + "KmiMr73908A=", + "tMoQ2Vse5tc=", + "xORLh0b3QZY=", + "zmU28zD7PUE=", + "GpZOJFWuGak=", + "Qx1IqUzjv2w=", + "VPGZIGfI6Rg=", + "bXNUiwR2HR0=", + "PAmiGhwY1GA=", + "NHP1/DNlYAw=", + "y2oaGajRq7Y=", + "KmiMr73908A=" + ], + "sponsorship can be removed with frozen sponsor" : [ "DNbanX3bsGI=", "A6H9fxI4C/k=", "eUPWbi2D4Bc=" ] +}