Stellar Atlas
Protocol
CAP-0077

Freeze Ledger Entries via Network Configuration

FinalGitHub

Specification

CAP: 0077
Title: Freeze Ledger Entries via Network Configuration
Working Group:
    Owner: Dmytro Kozhevin <@dmkozh>
    Authors: Dmytro Kozhevin <@dmkozh>
    Consulted: 
Status: Final
Created: 2025-11-25
Discussion: https://github.com/orgs/stellar/discussions/1811
Protocol version: 26

Simple Summary

Provide a way to make ledger entries inaccessible based on the network configuration upgrade performed with a validator vote.

Working Group

As specified in the Preamble.

Motivation

One of the first remediation steps for the data corruption incident that has occurred in protocol 23 has been the temporary 'freeze' of the corrupted ledger entries at the overlay layer in order to prevent further data corruption until the protocol has been fixed. Any transaction that has accessed any one of the corrupted keys (detectable via footprint) was rejected by the validators without being added to the mempool.

This was a bespoke change that has been released in a separate Core build. The 'freeze' has only become fully active when every tier 1 validator has updated to the new build, and even then it was not 100% effective (e.g. a validator could roll back the build). Also notably this kind of change can only be easily done for the Soroban entries - it would be much trickier to do quickly in case if any classic accounts or trustlines have been corrupted for whatever reason.

While the Stellar Core team puts a lot of effort to reduce the probability of the similar data corruption issues from ever occurring again, there is always a non-zero risk that something goes wrong. There also may be a possibility of using a similar mechanism for remediation of other issues beyond the protocol bugs, such as freezing the data entries that are known to be vulnerable in some way, e.g. are known to be hacked.

If the mechanism for freezing the entries is implemented as a part of the protocol instead of an overlay-level filter, it will both be easier to enable (via consensus instead of relying on every validator picking up the settings), and also more transparent, as all the changes will be visible on-chain.

Goals Alignment

This CAP is aligned with the goal of maintaining the Stellar network reliability via providing tools for quick issue remediation.

Abstract

This CAP introduces two new types of configuration settings: one for storing the full list of the frozen ledger keys and another one for performing incremental updates to that list via the standard settings upgrade mechanism (CAP-46-06).

Only a subset of ledger entry types can be frozen, specifically:

  • Soroban-only entries (contract data, contract code)
  • Account entries
  • Trustline entires, except pool share trustlines

Soroban transactions that have a frozen entry in the footprint will be considered invalid, which would cause them to never be included into ledger. For the most of the 'classic' transactions it's possible to tell which account or trustline entries are going to be accessed and thus they will also be considered invalid and never included into ledger.

For the transactions that use the opaque ids instead of the asset specification (claimable balance and liquidity pool related), the validation happens at apply time instead and results in transaction failure. For the transactions that interact with DEX there is no way to predict if a frozen trustline or account is going to be accessed and thus the offers that would modify a frozen entry are ignored and removed instead.

There are also two exceptions for the access to the frozen entries: offers may be removed and update the frozen liabilities, and it's an entry that is sponsored by a frozen account entry may be removed and update the sponsorship counter.

Besides a capability to fully freeze the ledger entries, a capability to 'unfreeze' specific transactions that may access the frozen entries is introduced as well. That capability allows performing targeted and thoroughly reviewed corruption or vulnerability recovery actions.

Specification

XDR changes

This patch of XDR changes is based on the XDR files in commit 0a621ec7811db000a60efae5b35f78dee3aa2533 of stellar-xdr.

diff --git a/Stellar-contract-config-setting.x b/Stellar-contract-config-setting.x
index f1b8a3a..80c0d7f 100644
--- a/Stellar-contract-config-setting.x
+++ b/Stellar-contract-config-setting.x
@@ -1,6 +1,9 @@
 %#include "xdr/Stellar-types.h"
 
 namespace stellar {
+
+typedef opaque EncodedLedgerKey<>;
+
 // General “Soroban execution lane” settings
 struct ConfigSettingContractExecutionLanesV0
 {
@@ -343,6 +346,24 @@ struct ConfigSettingSCPTiming {
     uint32 ballotTimeoutIncrementMilliseconds;
 };
 
+struct FrozenLedgerKeys {
+    EncodedLedgerKey keys<>;
+};
+
+struct FrozenLedgerKeysDelta {
+    EncodedLedgerKey keysToFreeze<>;
+    EncodedLedgerKey keysToUnfreeze<>;
+};
+
+struct FreezeBypassTxs {
+    Hash txHashes<>;
+};
+
+struct FreezeBypassTxsDelta {
+    Hash addTxs<>;
+    Hash removeTxs<>;
+};
+
 // limits the ContractCostParams size to 20kB
 const CONTRACT_COST_COUNT_LIMIT = 1024;
 
@@ -367,7 +388,11 @@ enum ConfigSettingID
     CONFIG_SETTING_EVICTION_ITERATOR = 13,
     CONFIG_SETTING_CONTRACT_PARALLEL_COMPUTE_V0 = 14,
     CONFIG_SETTING_CONTRACT_LEDGER_COST_EXT_V0 = 15,
-    CONFIG_SETTING_SCP_TIMING = 16
+    CONFIG_SETTING_SCP_TIMING = 16,
+    CONFIG_SETTING_FROZEN_LEDGER_KEYS = 17,
+    CONFIG_SETTING_FROZEN_LEDGER_KEYS_DELTA = 18,
+    CONFIG_SETTING_FREEZE_BYPASS_TXS = 19,
+    CONFIG_SETTING_FREEZE_BYPASS_TXS_DELTA = 20
 };
 
 union ConfigSettingEntry switch (ConfigSettingID configSettingID)
@@ -406,5 +431,13 @@ case CONFIG_SETTING_CONTRACT_LEDGER_COST_EXT_V0:
     ConfigSettingContractLedgerCostExtV0 contractLedgerCostExt;
 case CONFIG_SETTING_SCP_TIMING:
     ConfigSettingSCPTiming contractSCPTiming;
+case CONFIG_SETTING_FROZEN_LEDGER_KEYS:
+    FrozenLedgerKeys frozenLedgerKeys;
+case CONFIG_SETTING_FROZEN_LEDGER_KEYS_DELTA:
+    FrozenLedgerKeysDelta frozenLedgerKeysDelta;
+case CONFIG_SETTING_FREEZE_BYPASS_TXS:
+    FreezeBypassTxs freezeBypassTxs;
+case CONFIG_SETTING_FREEZE_BYPASS_TXS_DELTA:
+    FreezeBypassTxsDelta freezeBypassTxsDelta;
 };
 }
diff --git a/Stellar-transaction.x b/Stellar-transaction.x
index 9a14d6e..c22f5b4 100644
--- a/Stellar-transaction.x
+++ b/Stellar-transaction.x
@@ -1597,7 +1597,8 @@ enum ClaimClaimableBalanceResultCode
     CLAIM_CLAIMABLE_BALANCE_CANNOT_CLAIM = -2,
     CLAIM_CLAIMABLE_BALANCE_LINE_FULL = -3,
     CLAIM_CLAIMABLE_BALANCE_NO_TRUST = -4,
-    CLAIM_CLAIMABLE_BALANCE_NOT_AUTHORIZED = -5
+    CLAIM_CLAIMABLE_BALANCE_NOT_AUTHORIZED = -5,
+    CLAIM_CLAIMABLE_BALANCE_TRUSTLINE_FROZEN = -6
 };
 
 union ClaimClaimableBalanceResult switch (ClaimClaimableBalanceResultCode code)
@@ -1609,6 +1610,7 @@ case CLAIM_CLAIMABLE_BALANCE_CANNOT_CLAIM:
 case CLAIM_CLAIMABLE_BALANCE_LINE_FULL:
 case CLAIM_CLAIMABLE_BALANCE_NO_TRUST:
 case CLAIM_CLAIMABLE_BALANCE_NOT_AUTHORIZED:
+case CLAIM_CLAIMABLE_BALANCE_TRUSTLINE_FROZEN:
     void;
 };
 
@@ -1778,7 +1780,9 @@ enum LiquidityPoolDepositResultCode
     LIQUIDITY_POOL_DEPOSIT_LINE_FULL = -5,      // pool share trust line doesn't
                                                 // have sufficient limit
     LIQUIDITY_POOL_DEPOSIT_BAD_PRICE = -6,      // deposit price outside bounds
-    LIQUIDITY_POOL_DEPOSIT_POOL_FULL = -7       // pool reserves are full
+    LIQUIDITY_POOL_DEPOSIT_POOL_FULL = -7,      // pool reserves are full
+    LIQUIDITY_POOL_DEPOSIT_TRUSTLINE_FROZEN = -8  // trustline for one of the 
+                                                  // assets is frozen
 };
 
 union LiquidityPoolDepositResult switch (LiquidityPoolDepositResultCode code)
@@ -1792,6 +1796,7 @@ case LIQUIDITY_POOL_DEPOSIT_UNDERFUNDED:
 case LIQUIDITY_POOL_DEPOSIT_LINE_FULL:
 case LIQUIDITY_POOL_DEPOSIT_BAD_PRICE:
 case LIQUIDITY_POOL_DEPOSIT_POOL_FULL:
+case LIQUIDITY_POOL_DEPOSIT_TRUSTLINE_FROZEN:
     void;
 };
 
@@ -1810,7 +1815,9 @@ enum LiquidityPoolWithdrawResultCode
                                                // pool share
     LIQUIDITY_POOL_WITHDRAW_LINE_FULL = -4,    // would go above limit for one
                                                // of the assets
-    LIQUIDITY_POOL_WITHDRAW_UNDER_MINIMUM = -5 // didn't withdraw enough
+    LIQUIDITY_POOL_WITHDRAW_UNDER_MINIMUM = -5, // didn't withdraw enough
+    LIQUIDITY_POOL_WITHDRAW_TRUSTLINE_FROZEN = -6  // trustline for one of the 
+                                                   // assets is frozen
 };
 
 union LiquidityPoolWithdrawResult switch (LiquidityPoolWithdrawResultCode code)
@@ -1822,6 +1829,7 @@ case LIQUIDITY_POOL_WITHDRAW_NO_TRUST:
 case LIQUIDITY_POOL_WITHDRAW_UNDERFUNDED:
 case LIQUIDITY_POOL_WITHDRAW_LINE_FULL:
 case LIQUIDITY_POOL_WITHDRAW_UNDER_MINIMUM:
+case LIQUIDITY_POOL_WITHDRAW_TRUSTLINE_FROZEN:
     void;
 };
 
@@ -1999,7 +2007,8 @@ enum TransactionResultCode
     txBAD_SPONSORSHIP = -14,        // sponsorship not confirmed
     txBAD_MIN_SEQ_AGE_OR_GAP = -15, // minSeqAge or minSeqLedgerGap conditions not met
     txMALFORMED = -16,              // precondition is invalid
-    txSOROBAN_INVALID = -17         // soroban-specific preconditions were not met
+    txSOROBAN_INVALID = -17,        // soroban-specific preconditions were not met
+    txFROZEN_KEY_ACCESSED = -18     // a 'frozen' ledger key is accessed by any operation
 };
 
 // InnerTransactionResult must be binary compatible with TransactionResult
@@ -2031,6 +2040,7 @@ struct InnerTransactionResult
     case txBAD_MIN_SEQ_AGE_OR_GAP:
     case txMALFORMED:
     case txSOROBAN_INVALID:
+    case txFROZEN_KEY_ACCESSED:
         void;
     }
     result;
@@ -2078,6 +2088,7 @@ struct TransactionResult
     case txBAD_MIN_SEQ_AGE_OR_GAP:
     case txMALFORMED:
     case txSOROBAN_INVALID:
+    case txFROZEN_KEY_ACCESSED:
         void;
     }
     result;

Semantics

Configuration settings

Settings for freezing the ledger keys

Two new configuration settings types are introduced to support ledger key freezing: CONFIG_SETTING_FROZEN_LEDGER_KEYS (frozenLedgerKeys setting) and CONFIG_SETTING_FROZEN_LEDGER_KEYS_DELTA (frozenLedgerKeysDelta setting). On protocol upgrade only CONFIG_SETTING_FROZEN_LEDGER_KEYS entry is created and initialized with an empty array.

frozenLedgerKeys setting contains a vector of the LedgerKey XDR structs encoded as bytes (in order to avoid a circular dependency in XDR definitions). The ledger keys stored in this setting are considered frozen by the protocol, with detailed semantics described in the following section.

frozenLedgerKeys can not be upgraded by the regular setting upgrade mechanism that involves overriding the full contents of the configuration entry. Instead, the upgrade is performed via a frozenLedgerKeysDelta setting that contains vectors of LedgerKeys to freeze and unfreeze, encoded as XDR bytes.

frozenLedgerKeysDelta upgrade is considered invalid if any of the ledger keys can't be decoded as LedgerKey XDR, if any of the keys doesn't belong to the following allowed types: ACCOUNT, TRUSTLINE, CONTRACT_DATA, CONTRACT_CODE, or if any one of the keys is either a pool share trustline, or an issuer trustline.

When an upgrade to that contains frozenLedgerKeysDelta setting is applied to the ledger, all the keys in keysToFreeze are encoded and added to frozenLedgerKeys and all the keys in keysToUnfreeze are removed from frozenLedgerKeys. The upgrade process gracefully handles the addition of duplicate keys and removal of non-existent keys, these are simply ignored during the upgrade application process.

Settings for bypassing the freeze by individual transactions

Two new configuration settings types are introduced to allow specific individual transactions to access frozen keys: CONFIG_SETTING_FREEZE_BYPASS_TXS (freezeBypassTxs setting) and CONFIG_SETTING_FREEZE_BYPASS_TXS_DELTA (freezeBypassTxsDelta setting). The upgrade semantics are the same as for the frozen keys settings, but instead of LedgerKey XDR, the entries are Hash XDR (transaction hashes). On protocol upgrade only CONFIG_SETTING_FREEZE_BYPASS_TXS entry is created and initialized with an empty array. The upgrade is performed via CONFIG_SETTING_FREEZE_BYPASS_TXS_DELTA setting that contains vectors of transaction hashes to add or remove to the list of transactions that are allowed to bypass the freeze.

The transactions with content hashes in freezeBypassTxs are allowed to access the frozen ledger keys at validation time without being rejected by the protocol. freezeBypassTxs setting is ignored at apply time, i.e. it's not possible to bypass the freeze for the transactions that access frozen keys via the operations with opaque source/destination (claimable balance and liquidity pool operations) or via DEX.

Frozen key validation

In general, the transaction that would access a frozen key should be rejected by the network as soon as possible, i.e. at the validation stage. If it's possible to tell if a transaction or one of its operation accesses a frozen key, it is rejected as invalid with txFROZEN_KEY_ACCESSED error. Additional details may be provided via the diagnostic events.

If a transaction accesses a frozen key, the protocol will check if the transaction hash is in the freezeBypassTxs setting, and if it is, then the transaction will be considered valid.

This section contains details for all the supported validation scenarios.

Soroban transactions

Soroban transactions, i.e. those transactions that have SorobanTransactionData with resources are considered invalid if any of the keys in either readOnly, or readWrite footprint are present in the frozenLedgerKeys setting. Currently, the Soroban transactions are those that contain a single operation among InvokeHostFunctionOp, ExtendFootprintTTLOp and RestoreFootprintOp.

Source accounts

If a source account of any transaction, fee bump transaction, or operation is in frozenLedgerKeys, then the transaction is considered to be invalid.

Source trustlines

A number of operations modifies the trustline of the source account. The trustline is defined by the source account and the respective Asset(s) specified in the operation. If a trustline is frozen, the transaction is considered to be invalid.

The list of operations that explicitly specify source trustline(s):

  • PaymentOp
  • PathPaymentStrictReceiveOp (sendAsset)
  • PathPaymentStrictSendOp (sendAsset)
  • ManageSellOfferOp (trustlines for selling and buying assets)
  • ManageBuyOfferOp (trustlines for selling and buying assets)
  • CreatePassiveSellOfferOp (trustlines for selling and buying assets)
  • ChangeTrustOp (alphaNum4/alphaNum12 trustlines)
  • CreateClaimableBalanceOp
Destination accounts/trustlines

A number of operations specifies the destination trustline or account, typically via a MuxedAccount/AccountID identifier and the corresponding Asset asset. These unambiguously identify either a trustline, or an account entry (in case of the NATIVE asset). If such account or trustline is in the frozenLedgerKeys, then the transactions is considered to be invalid.

The list of the operations that explicitly specify the payment destination:

  • PaymentOp
  • PathPaymentStrictReceiveOp
  • PathPaymentStrictSendOp
  • AllowTrustOp (trustor + asset define a trustline)
  • RevokeSponsorshipOp (destination key is directly defined via LedgerKey, or an account entry is specified for REVOKE_SPONSORSHIP_SIGNER)
  • ClawbackOp (from + asset define a trustline)
  • SetTrustLineFlagsOp (trustor + asset define a trustline)
  • AccountMergeOp (merge destination account)
  • CreateAccountOp (destination account)

Apply time validation

Some operations don't contain the information necessary to identify the trustlines or accounts being modified. For these operations the frozen keys are handled at apply time. Note, that freezeBypassTxs setting is ignored for the apply-time checks, i.e. the few operations validated at apply time can not be unfrozen.

Operations with opaque source/destination

The claimable balance and liquidity pool operations use opaque identifiers instead of the asset codes. If one of these operations modifies a frozen trustline or an account (for the XLM balance modification), then the operation will fail with the respective error.

  • ClaimClaimableBalanceOp - fails with CLAIM_CLAIMABLE_BALANCE_TRUSTLINE_FROZEN error if balance is withdrawn to a frozen trustline or account
  • LiquidityPoolDepositOp - fails with LIQUIDITY_POOL_DEPOSIT_TRUSTLINE_FROZEN error if either one of the two pool asset trustlines used for deposit is frozen
  • LiquidityPoolWithdrawOp - fails with LIQUIDITY_POOL_WITHDRAW_TRUSTLINE_FROZEN error if either one of the two pool asset trustlines used for withdrawal is frozen
DEX operations

A DEX offer may be traded when a DEX operation is applied (these operations are PathPaymentStrictReceiveOp, ManageSellOfferOp, CreatePassiveSellOfferOp, ManageBuyOfferOp, PathPaymentStrictSendOp). When DEX tries to cross an offer that would cause a frozen trustline or account balance change, the offer will be removed instead without moving any assets, and the offer matching will then proceed as usual. Note, that the transactions that have an explicit frozen source or destination defined in the transaction would have already been filtered out at this point, so only the trustline/account owning the offer found by DEX are verified at this stage.

The offer removal also results in adjusting the liabilities of the trustline or an account, even though it is frozen. This is one of the few changes allowed to perform for the frozen entries.

Authorization revocation

When an asset issuer revokes authorization on a trustline (via AllowTrustOp or SetTrustLineFlagsOp), all offers owned by the trustline holder that involve the deauthorized asset are removed and their liabilities are released. If the trustline being deauthorized is not frozen, but the account owning the trustline is frozen, the offer removal will still modify the frozen account's liabilities and sub-entry count. This is the same class of modification as the DEX frozen offer removal (only liabilities and sub-entry count are modified, the actual balance stays the same).

Deletion of sponsored entry

If an account is frozen, an arbitrary number of sponsored entries might implicitly depend on it. The protocol allows doing that as per the standard sponsorship removal procedure, i.e. the numSponsoring field of a frozen account may be still modified when an entry or a signer sponsored by a frozen account is removed.

Design Rationale

CONFIG_SETTING_FROZEN_LEDGER_KEYS and CONFIG_SETTING_FREEZE_BYPASS_TXS config upgrades

The bespoke upgrade procedure is introduced in order to work around the limit for the maximum contract data entry size. The entry size limit currently is 128 KB, so if the regular upgrade process was used, then only about 1500 keys may be stored in the setting depending on the key sizes. This might be too limiting and thus the mechanism for incremental updates is introduced.

The validation of the incremental upgrades is also relaxed (duplicates/missing entries are allowed) in order to make the upgrade process faster and more reliable, as the assumption is that if this mechanism is ever used, it needs to be used rather fast. Under that requirement it's better to miss updates to a few keys instead of missing the whole upgrade.

Unsupported key types

The CAP aims at supporting freezing the minimum viable set of ledger key types in order to minimize the maintenance burden and complexity. Soroban keys are a straightforward choice as they contain arbitrary data. Account and trustline entries are the only remaining entries that contain user value. Claimable balances and liquidity pools technically contain user value as well, but they might be frozen by a trustline proxy (e.g. a claimable balance can be frozen by freezing the trustlines of the claimers). It's also important to note that the data corruption or hack risk is much lower for the claimable balances and liquidity pools, as these are not interfacing with Soroban, are not actively developed, and can't be used standalone to transfer value. For the trustline keys we also exclude:

  • Pool share trustlines, as they are only used for liquidity pools and thus can be frozen by freezing the respective liquidity pool's asset trustlines.
  • Issuer trustlines, as these cannot exist as real ledger entries, but can introduce some unnecessary maintenance complexity in the apply paths.

DEX handling

A simple approach to handling the DEX operations would be to fail the operation if an offer that would update a frozen account/trustline is crossed. However, since DEX greedily picks the best offer to cross, this approach might effectively disable DEX for an asset pair for an unknown period of time and block all the offers that are more expensive than the affected offer.

In order to avoid DEX disruption the approach described in this CAP is proposed: the offer is automatically removed and liabilities are released. This comes with the minimal DEX disruption with a tradeoff of modifying a frozen entry. However, the modification is minimal (only the liabilities are modified, the actual balance stays the same) and thus is unlikely to make data corruption worse. In the worst case, if an entry is in an arbitrarily broken state and update of liabilities would violate the protocol invariants for the liabilities, the transaction will fail with an internal error, which is equivalent to what would unconditionally happen in the 'simple' approach.

Sponsorship handling

Similarly to the DEX handling, the proposal here is a tradeoff that prioritizes disruption minimization over complete access blocking. The sponsored entries have pretty much nothing to do with a frozen account, and decreasing the numSponsored value is a very small and likely safe modification that shouldn't be able to make data corruption issues worse.

Using contents hash for the bypass transaction list

The bypass list uses transaction content hashes instead of the full envelope hashes, i.e. it omits hashing the signatures. Since every signature has to belong to a source account of either the transaction, or one of the operations, there is no way to have a bypass transaction that accesses unexpected frozen keys via attaching an extra signature from an unrelated account. This allows specifying the bypass transactions without knowing the exact signatures beforehand.

It is tempting to look at the inner transaction contents hash to allow wrapping the bypass transactions into arbitrary fee bumps. However, unlike in the signature case, fee bump transaction envelope specifies an additional source account which has to be verified against the frozen keys list. Thus, if only the inner transaction hash was used, then it would be possible to bypass the freeze for the unexpected ledger keys by wrapping the bypass transaction into a fee bump with a frozen source account.

Protocol Upgrade Transition

Backwards Incompatibilities

This CAP does not introduce any backward incompatibilities.

Resource Utilization

If the frozen key list is too large, then it might slow down the transaction validation and execution of some operations, but it's unlikely that this would be ever noticeable in practice if a hash map is used for lookup. While the list is empty/small, there should be no noticeable performance impact at all.

Security Concerns

Every Stellar validator has an inherent ability to censor the traffic, that doesn't require a consensus of validators to agree on that, and that is also pretty hard to observe. The 'censorship' mechanism introduced in this CAP is easily observable and requires consensus of validators and thus it is not providing any new risks or attack angles.

On the other hand, the observability may be problematic if the freezing mechanism has to be used on a short notice (for example, before someone could abuse a data corruption bug). A malicious party may be able to observe the upgrade and try to abuse it before the upgrade goes through. However, that requires constant monitoring of all the Soroban state changes combined with an ability to react to the upgrade quickly. This impact of this issue can be reduced greatly by reducing the timeline between the upload of the config upgrade entry and the vote.

Test Cases

TBD

Implementation

TBD

Preamble

Status
Final
Protocol version
26
Authors
Dmytro Kozhevin
Created
2025-11-25

Discussion

0 linked threads