-
Notifications
You must be signed in to change notification settings - Fork 353
CAP 81 draft #1869
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
CAP 81 draft #1869
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR introduces CAP-0081, which proposes a simplified eviction mechanism for Soroban entries in Protocol 26. The CAP changes eviction order from bucket-file-position to TTL-order, enabling in-memory eviction scans and improving performance.
Changes:
- Adds CAP-0081 document proposing TTL-ordered eviction with separate limits for temporary and persistent entries
- Introduces new config setting CONFIG_SETTING_MAX_TEMP_ENTRIES_TO_EVICT and renames maxEntriesToArchive to maxPersistentEntriesToArchive
- Updates README.md to include CAP-0081 in the draft proposals list
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| core/cap-0081.md | New CAP document defining TTL-ordered eviction mechanism for Protocol 26 |
| core/README.md | Added CAP-0081 entry to the draft proposals table |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| This CAP modifies the eviction mechanism to: | ||
|
|
||
| 1. **Order eviction by TTL**: Temporary and persistent entries are evicted separately, each with their own ordering. Within each entry type, entries are ordered by `(liveUntilLedgerSeq, LedgerKey)` — the entry with the lowest `liveUntilLedgerSeq` is evicted first. For entries with the same TTL, `LedgerKey` ordering (using the existing `LedgerEntryIdCmp` comparator) provides a deterministic tiebreaker. Temporary and persistent entries do not compete with each other for eviction order. |
Copilot
AI
Jan 30, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reference to "LedgerEntryIdCmp" comparator is not defined or documented elsewhere in the CAP system. While LedgerKey ordering is used in other CAPs (e.g., CAP-0057 line 446 refers to "pre-existing compare function"), the specific name "LedgerEntryIdCmp" doesn't appear in any other CAP. Consider either defining how LedgerKey ordering works in this CAP, or using more generic language like "standard LedgerKey ordering" to match the convention in other CAPs.
| 1. **Order eviction by TTL**: Temporary and persistent entries are evicted separately, each with their own ordering. Within each entry type, entries are ordered by `(liveUntilLedgerSeq, LedgerKey)` — the entry with the lowest `liveUntilLedgerSeq` is evicted first. For entries with the same TTL, `LedgerKey` ordering (using the existing `LedgerEntryIdCmp` comparator) provides a deterministic tiebreaker. Temporary and persistent entries do not compete with each other for eviction order. | |
| 1. **Order eviction by TTL**: Temporary and persistent entries are evicted separately, each with their own ordering. Within each entry type, entries are ordered by `(liveUntilLedgerSeq, LedgerKey)` — the entry with the lowest `liveUntilLedgerSeq` is evicted first. For entries with the same TTL, `LedgerKey` ordering (using the standard LedgerKey ordering used elsewhere in the protocol) provides a deterministic tiebreaker. Temporary and persistent entries do not compete with each other for eviction order. |
| + uint32 maxTempEntriesToEvict; | ||
| }; | ||
| ``` | ||
|
|
Copilot
AI
Jan 30, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new network config setting maxTempEntriesToEvict is introduced without specifying an initial/default value. According to CAP-0046-09, "Every CAP introducing the new setting must also define its default value to be used during the protocol version upgrade." Consider adding a section in the Semantics that specifies the initial value for maxTempEntriesToEvict and possibly acceptable value ranges, similar to how CAP-0070 defines initial values for CONFIG_SETTING_SCP_TIMING.
| #### Initial value and valid range for `CONFIG_SETTING_MAX_TEMP_ENTRIES_TO_EVICT` | |
| On upgrade to protocol version 26, the network **MUST** initialize | |
| `CONFIG_SETTING_MAX_TEMP_ENTRIES_TO_EVICT.maxTempEntriesToEvict` to `1000`. | |
| The valid range for `maxTempEntriesToEvict` is any non-zero `uint32` value, | |
| i.e. `1` through `2^32 - 1`. A value of `0` is invalid and **MUST** be | |
| rejected by implementations. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is indeed missing from the CAP, we should initialize with the same value as persistent setting though.
| This list can be prepared and maintained outside of the ledgerClose path. While a TTL can be increased such that an entry in the list must be "skipped," | ||
| TTLs can never decrease such that a new entry "jumps the line" and invalidates the previously constructed list. | ||
|
|
||
| For initial rollout, the naive solution is likely good enough. No newline at end of file |
Copilot
AI
Jan 30, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The CAP is missing common sections that appear in other protocol CAPs, such as "Design Rationale", "Security Concerns", "Backwards Incompatibilities", "Resource Utilization", and "Test Cases". While the Implementation section provides some discussion, consider adding these sections to better document the proposal's implications. For example, discussing backwards compatibility with existing eviction infrastructure, security implications of the new ordering, and resource utilization changes would strengthen the proposal.
| For initial rollout, the naive solution is likely good enough. | |
| For initial rollout, the naive solution is likely good enough. | |
| ### Design Rationale | |
| The primary goal of TTL-ordered eviction is to provide a deterministic and predictable eviction policy that is directly derived from protocol-visible state. | |
| Ordering eviction strictly by the ledger sequence at which an entry becomes ineligible for retention: | |
| - Ensures that all validators observe the same eviction order, as it depends only on consensus data (TTL and key). | |
| - Makes it easier for contract and application developers to reason about when their state may be removed. | |
| - Avoids hidden heuristics such as implicit LRU or cache pressure–driven strategies, which are harder to model and test. | |
| Alternative designs (e.g., eviction based on access frequency, random eviction, or per-contract quotas) were considered but rejected for this CAP because | |
| they either introduce non-determinism, require additional non-consensus state, or complicate the mental model for users of the system. By coupling eviction | |
| directly to TTL, this proposal reuses existing mechanisms (TTL bumps, configuration of maximum TTLs, and network-level limits) rather than inventing new | |
| control knobs for state lifetime. | |
| ### Security Concerns | |
| TTL-ordered eviction can be abused if an attacker is able to cheaply create many entries with short TTLs, potentially causing high churn near the eviction | |
| frontier. This is mitigated by existing fees and limits on entry creation and TTL bump operations; this CAP does not relax those mechanisms. Validators | |
| should ensure that cost models for creating and bumping entries reflect the work imposed on the eviction index. | |
| The eviction ordering must be fully deterministic across all nodes. Implementations MUST define a total order over entries with identical `liveUntilLedgerSeq` | |
| (for example, by ordering on the canonical ledger key after TTL) to avoid consensus divergence. Implementations should be careful that any optimizations (such | |
| as maintaining only a window of soon-to-evict entries) preserve this global total order when deciding which entries to evict. | |
| Finally, nodes must avoid exposing any side channels via eviction timing that could be used to infer private information about contract usage. Because the | |
| order is derived solely from consensus-visible TTL and keys, and not from access frequency or other local-only data, this design reduces such risks relative | |
| to heuristic eviction policies. | |
| ### Backwards Incompatibilities | |
| Existing deployments may rely—implicitly or explicitly—on the current eviction behavior, which may be more opportunistic or implementation-defined. Adopting | |
| TTL-ordered eviction standardizes eviction semantics and may change the order and timing in which entries are removed compared to previous versions. | |
| From a protocol perspective, this CAP does not change the meaning of TTL values or the definition of when an entry has expired. However, operators and | |
| application developers should be aware that: | |
| - Entries with earlier expiration will be preferentially evicted over longer-lived entries, even if the latter are rarely used. | |
| - Any assumptions about eviction being correlated with access patterns or local cache pressure will no longer hold. | |
| Implementations should provide a migration path that constructs or backfills the eviction index from existing ledger state at upgrade time. During this | |
| migration, care must be taken to ensure that all eligible entries are inserted into the index in a way that preserves the intended order. | |
| ### Resource Utilization | |
| As described above, maintaining a global `std::set<EvictionKey>` index for all eligible entries adds approximately 48 MB of memory at current network state | |
| sizes, since `EvictionKey::entry` is a pointer into already-allocated cached ledger entries. This overhead scales approximately linearly with the number of | |
| live entries, with an additional logarithmic CPU cost for insertions, deletions, and updates. | |
| The primary runtime costs are: | |
| - O(log n) operations on TTL bumps, entry creation, and eviction. | |
| - Occasional rebalancing of the ordered set data structure. | |
| In practice, these costs are expected to be dominated by disk I/O and bucket operations during ledger close. If resource usage becomes a concern, operators | |
| MAY enable optimizations discussed in the previous section, such as: | |
| - Maintaining a sliding window of soon-to-evict entries rather than a full index. | |
| - Updating the eviction index in parallel with bucket writes. | |
| These optimizations must be designed to preserve the same observable eviction order defined by this CAP. | |
| ### Test Cases | |
| Implementations of this CAP should include, at a minimum, the following categories of test coverage: | |
| - **Deterministic ordering tests**: Verify that for a fixed set of entries with specified TTLs and keys, all nodes evict the same sequence of entries. | |
| - **TTL bump interaction tests**: Ensure that increasing the TTL of an entry already in the eviction index correctly updates its position and does not | |
| violate the global ordering, and that decreasing TTL is either prohibited or handled in a way that cannot cause entries to "jump the line". | |
| - **Compatibility tests**: Start from a pre-upgrade ledger snapshot, build the eviction index, and verify that all entries become eligible and are evicted | |
| at the correct ledger sequences. | |
| - **Resource stress tests**: Exercise scenarios with large numbers of entries and frequent TTL bumps to measure CPU and memory overhead and validate that | |
| performance remains within acceptable bounds. | |
| - **Failure and recovery tests**: Simulate node crashes and restarts around eviction and index-update points, confirming that the eviction index can be | |
| reconstructed consistently from durable state. | |
| Together, these tests provide confidence that the new eviction policy is correctly implemented, deterministic, and robust under load and failure conditions. |
| uint32_t lastModifiedLedgerSeq; | ||
| }; | ||
|
|
||
| std::set<EvictionKey> evictionIndex; // Sorted by (liveUntilLedgerSeq, LedgerKey) |
Copilot
AI
Jan 30, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The EvictionKey struct includes lastModifiedLedgerSeq but this field is never mentioned in the algorithm description or sorting criteria. The eviction ordering is specified as being by (liveUntilLedgerSeq, LedgerKey), so it's unclear what role lastModifiedLedgerSeq plays. Either this field should be removed from the struct if it's not needed, or the documentation should explain its purpose.
| uint32_t lastModifiedLedgerSeq; | |
| }; | |
| std::set<EvictionKey> evictionIndex; // Sorted by (liveUntilLedgerSeq, LedgerKey) | |
| // Ledger sequence when this entry was last modified. This is tracked for | |
| // diagnostics/metrics and does not participate in the eviction ordering. | |
| uint32_t lastModifiedLedgerSeq; | |
| }; | |
| // Ordered by (liveUntilLedgerSeq, LedgerKey), both derived from `entry`. | |
| // The comparator ignores `lastModifiedLedgerSeq`. | |
| std::set<EvictionKey> evictionIndex; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good find, this seems to be a typo
This CAP outlines a simplified eviction scan logic for Protocol 26. Basically, we just keep a set of live Soroban entries ordered by TTL and key. At the end of each ledger, we check if the front of our set is expired and evict up to maxEntriesToEvict of expired state. Should maintaining this set be too inefficient, we outline future non-protocol breaking optimizations.
Additionally, this separates eviction limits, one for temp entries and one for persistent entries. Temp and persistent entries are fundamentally quite different, so this give us better control over eviction behavior, with little added complexity (we can just maintain two separate sets for each type).