Skip to content

perf(l1): replace FIFO mempool eviction with min-heap keyed by tip#6607

Open
ilitteri wants to merge 4 commits into
mainfrom
feat/mempool-heap-eviction
Open

perf(l1): replace FIFO mempool eviction with min-heap keyed by tip#6607
ilitteri wants to merge 4 commits into
mainfrom
feat/mempool-heap-eviction

Conversation

@ilitteri
Copy link
Copy Markdown
Collaborator

@ilitteri ilitteri commented May 12, 2026

Motivation

When the mempool is full (transaction_pool.len() >= max_mempool_size), ethrex currently drops the oldest transaction (VecDeque<H256> FIFO). That is wrong under fee pressure: low-fee transactions cling on while high-fee newcomers get rejected. The block builder should reward the highest-tip senders, not the earliest arrivals.

Description

Replace the FIFO txs_order: VecDeque<H256> in MempoolInner with a min-heap txs_by_tip: BinaryHeap<Reverse<(u64, H256)>> keyed by Transaction::gas_tip_cap() saturated to u64 (astronomically-large tips rank highest and are evicted last by the min-heap).

  • evict_lowest_tip_transaction replaces remove_oldest_transaction and pops from the min-heap until transaction_pool is under capacity.
  • Underpriced newcomer rejection: add_transaction peeks the live heap minimum via a new live_min_tip helper (skipping tombstones) before evicting. If the incoming tx's tip is <= the heap minimum, the incoming tx is rejected with the new MempoolError::PoolFullAndUnderpriced { incoming_tip, min_pool_tip } — admitting a cheaper tx by evicting a more expensive one would invert tip-based eviction. add_transaction's return type changes from Result<(), StoreError> to Result<(), MempoolError> so the new variant can surface.
  • Lazy deletion: normal removals (remove_transaction_with_lock) do not touch the heap. The eviction loop checks transaction_pool.contains_key on each popped entry and skips tombstones.
  • Heap rebuild from the top of add_transaction when the heap exceeds MEMPOOL_PRUNE_THRESHOLD_NUM / MEMPOOL_PRUNE_THRESHOLD_DEN (3/2 = 1.5x) max_mempool_size. Both numerator and denominator are named constants — no magic numbers.

Behavioral change

  • Eviction order under pressure changes from oldest to lowest-tip.
  • Incoming under-priced txs get rejected outright when the pool is at capacity (new error variant). Previously the pool was always made room for any incoming tx by evicting the oldest.
  • All other observable behavior — insertion, lookup, blob handling, filtering, replacement — is identical.

Replace the `VecDeque<H256>` FIFO order queue in `MempoolInner` with a
`BinaryHeap<Reverse<(u64, H256)>>` min-heap keyed by `Transaction::gas_tip_cap()`
saturated to `u64`. When the pool is full we now evict the *lowest-tip*
transaction instead of the oldest, so under fee pressure high-fee newcomers
are admitted at the expense of low-fee residents.

Lazy deletion: normal removals leave heap entries as tombstones; the eviction
loop skips popped hashes that are no longer in `transaction_pool`. The
top-of-`add_transaction` prune rebuilds the heap from the live pool once the
heap grows past `MEMPOOL_PRUNE_THRESHOLD_NUM / MEMPOOL_PRUNE_THRESHOLD_DEN`
(1.5x) `max_mempool_size`, dropping accumulated tombstones in O(n).
Copilot AI review requested due to automatic review settings May 12, 2026 02:24
@ilitteri ilitteri requested a review from a team as a code owner May 12, 2026 02:24
@github-actions github-actions Bot added L1 Ethereum client performance Block execution throughput and performance in general labels May 12, 2026
@github-actions
Copy link
Copy Markdown

🤖 Kimi Code Review

The PR correctly transitions mempool eviction from FIFO to a tip-based priority scheme using a lazy-deletion min-heap. The logic is sound and the test coverage is comprehensive.

Minor Defensive Programming Suggestions:

  1. Integer overflow in capacity calculation (mempool.rs:62-63):

    mempool_prune_threshold: max_mempool_size * MEMPOOL_PRUNE_THRESHOLD_NUM
        / MEMPOOL_PRUNE_THRESHOLD_DEN,

    While unlikely with practical mempool limits, this could overflow usize with pathological configurations. Consider using saturating_mul or checked_mul to prevent panic:

    mempool_prune_threshold: max_mempool_size
        .saturating_mul(MEMPOOL_PRUNE_THRESHOLD_NUM)
        .saturating_div(MEMPOOL_PRUNE_THRESHOLD_DEN),
  2. Heap capacity in rebuild_tip_heap (mempool.rs:135):

    let mut heap = BinaryHeap::with_capacity(self.max_mempool_size * 2);

    Similarly, max_mempool_size * 2 could overflow. Use saturating_mul or checked_mul with a fallback to max_mempool_size.

Code Quality Observations:

  1. Effective tip calculation (mempool.rs:143-147): The tip_key function uses gas_tip_cap() (max priority fee). This is the correct heuristic for mempool eviction since the actual effective tip depends on the unknown future base fee. The saturation to u64::MAX for oversized values is handled correctly.

  2. Lock contention: The add_transaction function holds the write lock during heap rebuild (O(n)) and potential multiple evictions. Given this is on the transaction submission path rather than the block execution hot path, this is acceptable, but document this behavior if the mempool is expected to handle high throughput.

Verification Questions:

  1. Nonce ordering consistency: Confirm that txs_by_sender_nonce (the BTreeMap used for nonce tracking) remains consistent when evict_lowest_tip_transaction removes transactions. The code calls remove_transaction_with_lock, which presumably cleans up all sender/nonce mappings—ensure this includes removing from txs_by_sender_nonce to prevent nonce gaps from lingering.

The lazy deletion pattern is well-implemented and the tests verify tombstone handling, heap rebuild thresholds, and correct eviction priority. No critical issues identified.


Automated review by Kimi (Moonshot AI) · kimi-k2.5 · custom prompt

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 12, 2026

Lines of code report

Total lines added: 256
Total lines removed: 0
Total lines changed: 256

Detailed view
+-------------------------------------+-------+------+
| File                                | Lines | Diff |
+-------------------------------------+-------+------+
| ethrex/crates/blockchain/error.rs   | 156   | +7   |
+-------------------------------------+-------+------+
| ethrex/crates/blockchain/mempool.rs | 713   | +249 |
+-------------------------------------+-------+------+

Copy link
Copy Markdown
Contributor

Copilot AI left a 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 changes the mempool’s behavior under capacity pressure by evicting the lowest-tip transaction instead of the oldest, improving fee-based prioritization during congestion.

Changes:

  • Replaces FIFO eviction tracking (VecDeque) with a min-heap (BinaryHeap<Reverse<(u64, H256)>>) keyed by Transaction::gas_tip_cap() projected into u64.
  • Implements lazy deletion for heap entries and adds a periodic heap rebuild when tombstones accumulate past a named threshold.
  • Adds unit tests covering lowest-tip eviction, newcomer retention, lazy deletion behavior, and heap rebuild pruning.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/blockchain/mempool.rs Outdated
inner.txs_order.retain(|tx| txpool.contains_key(tx));
inner.transaction_pool = txpool;
// Rebuild the eviction heap if tombstones have accumulated past the
// configured threshold (heap-size > MEMPOOL_PRUNE_THRESHOLD_FACTOR * max_mempool_size).
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread crates/blockchain/mempool.rs Outdated
Comment on lines +50 to +51
/// Min-heap (via `Reverse`) of `(effective tip, hash)` used to pick the
/// lowest-tip transaction to evict when the mempool is full. Entries are
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 12, 2026

Greptile Summary

This PR replaces the FIFO VecDeque<H256> eviction order in MempoolInner with a min-heap (BinaryHeap<Reverse<(u64, H256)>>) keyed by each transaction's priority fee cap, so that the lowest-tip entry is evicted when the pool is full rather than the oldest. Lazy deletion is used for normal removals, with a periodic full rebuild when tombstones accumulate past 1.5× max_mempool_size.

  • The core eviction mechanic (evict_lowest_tip_transaction) correctly pops from the min-heap and skips tombstones, and rebuild_tip_heap cleans up stale entries from transaction_pool.
  • A critical comparison is absent: the incoming transaction's tip is never checked against the current heap minimum before eviction, so a low-tip newcomer will unconditionally displace a higher-tip existing entry \u2014 the opposite of the intended behavior under fee pressure.
  • Four new unit tests cover the happy-path scenarios (eviction, lazy deletion, heap rebuild), but all use a fresh random sender per transaction, leaving the replacement flow and same-sender nonce interactions untested.

Confidence Score: 3/5

The eviction logic has a correctness gap that can actively degrade pool quality under fee pressure — the scenario this PR was written to fix.

The tip-comparison check before eviction is missing: a sender submitting a zero-tip transaction into a full pool of high-tip transactions will evict the current minimum-tip holder and get admitted, leaving the pool in a worse state than before.

crates/blockchain/mempool.rs — specifically the add_transaction / evict_lowest_tip_transaction interaction around lines 195–197.

Important Files Changed

Filename Overview
crates/blockchain/mempool.rs Replaces FIFO eviction with a lazy min-heap keyed by tip; contains a correctness gap where a low-tip newcomer can unconditionally evict a higher-tip existing transaction because the incoming tip is never compared against the heap minimum before eviction proceeds.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[add_transaction called] --> B{heap_size > prune_threshold?}
    B -- Yes --> C[rebuild_tip_heap from transaction_pool]
    C --> D
    B -- No --> D{pool_size >= max_mempool_size?}
    D -- Yes --> E[evict_lowest_tip_transaction]
    E --> F{heap.pop?}
    F -- None --> G[warn: pool full break]
    F -- Some hash --> H{hash in transaction_pool?}
    H -- No tombstone --> F
    H -- Yes --> I[remove_transaction_with_lock]
    I --> J{pool_size still >= max?}
    J -- Yes --> F
    J -- No --> K
    G --> K
    D -- No --> K[push tip/hash onto heap, insert tx into pool]
    K --> L[notify tx_added waiters]
Loading
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
crates/blockchain/mempool.rs:195-197
**Missing tip comparison before eviction**

`evict_lowest_tip_transaction()` unconditionally evicts the current minimum-tip entry from the pool without first checking whether the newcomer's tip is actually higher. If a sender submits a transaction with `tip=1` while the pool is full of `tip=100` transactions, the tip-100 entry is removed and the tip-1 newcomer is admitted — the opposite of the stated goal. The newcomer's tip must be compared against the live heap minimum before any eviction takes place; if the newcomer would be the new minimum, it should be rejected (or the eviction skipped) rather than displacing a better-priced transaction.

### Issue 2 of 2
crates/blockchain/mempool.rs:624-637
**`make_tx` generates a random sender per call, masking same-sender nonce conflicts**

Because each call to `make_tx` creates a fresh `Address::random()` sender, every transaction is from a different address and the `txs_by_sender_nonce` map never sees a collision. The tests therefore do not cover the replacement flow (`find_tx_to_replace` → remove old → add new), where the old entry becomes a heap tombstone while the sender-nonce mapping still points to the new hash. A test that reuses the same sender with an increasing nonce (or a tip-bump replacement) would catch any interaction between `txs_by_sender_nonce` and the lazy-deletion heap.

Reviews (1): Last reviewed commit: "perf(l1): replace FIFO mempool eviction ..." | Re-trigger Greptile

Comment on lines 195 to 197
if inner.transaction_pool.len() >= inner.max_mempool_size {
inner.remove_oldest_transaction()?;
inner.evict_lowest_tip_transaction()?;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Missing tip comparison before eviction

evict_lowest_tip_transaction() unconditionally evicts the current minimum-tip entry from the pool without first checking whether the newcomer's tip is actually higher. If a sender submits a transaction with tip=1 while the pool is full of tip=100 transactions, the tip-100 entry is removed and the tip-1 newcomer is admitted — the opposite of the stated goal. The newcomer's tip must be compared against the live heap minimum before any eviction takes place; if the newcomer would be the new minimum, it should be rejected (or the eviction skipped) rather than displacing a better-priced transaction.

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/blockchain/mempool.rs
Line: 195-197

Comment:
**Missing tip comparison before eviction**

`evict_lowest_tip_transaction()` unconditionally evicts the current minimum-tip entry from the pool without first checking whether the newcomer's tip is actually higher. If a sender submits a transaction with `tip=1` while the pool is full of `tip=100` transactions, the tip-100 entry is removed and the tip-1 newcomer is admitted — the opposite of the stated goal. The newcomer's tip must be compared against the live heap minimum before any eviction takes place; if the newcomer would be the new minimum, it should be rejected (or the eviction skipped) rather than displacing a better-priced transaction.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +624 to +637
let inner = EIP1559Transaction {
nonce,
max_priority_fee_per_gas: tip,
// Keep `max_fee_per_gas >= max_priority_fee_per_gas` so the tx is
// structurally valid even though we bypass mempool admission here.
max_fee_per_gas: tip,
gas_limit: 21_000,
to: TxKind::Call(Address::from_low_u64_be(1)),
..Default::default()
};
let tx = Transaction::EIP1559Transaction(inner);
let sender = Address::random();
let hash = H256::random();
(hash, sender, MempoolTransaction::new(tx, sender))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 make_tx generates a random sender per call, masking same-sender nonce conflicts

Because each call to make_tx creates a fresh Address::random() sender, every transaction is from a different address and the txs_by_sender_nonce map never sees a collision. The tests therefore do not cover the replacement flow (find_tx_to_replace → remove old → add new), where the old entry becomes a heap tombstone while the sender-nonce mapping still points to the new hash. A test that reuses the same sender with an increasing nonce (or a tip-bump replacement) would catch any interaction between txs_by_sender_nonce and the lazy-deletion heap.

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/blockchain/mempool.rs
Line: 624-637

Comment:
**`make_tx` generates a random sender per call, masking same-sender nonce conflicts**

Because each call to `make_tx` creates a fresh `Address::random()` sender, every transaction is from a different address and the `txs_by_sender_nonce` map never sees a collision. The tests therefore do not cover the replacement flow (`find_tx_to_replace` → remove old → add new), where the old entry becomes a heap tombstone while the sender-nonce mapping still points to the new hash. A test that reuses the same sender with an increasing nonce (or a tip-bump replacement) would catch any interaction between `txs_by_sender_nonce` and the lazy-deletion heap.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@github-actions
Copy link
Copy Markdown

🤖 Claude Code Review

Now I have the full picture. Let me write the review.


Review: perf(l1): replace FIFO mempool eviction with min-heap keyed by tip

Summary

The change is well-motivated and correctly structured. The lazy-deletion design avoids the O(n) rebuild on every removal, the threshold-based cleanup bounds memory growth, and the four tests cover the critical paths. A few points warrant attention:


Correctness

1. tip_key uses gas_tip_cap() (= max_priority_fee_per_gas), not the effective included tip.

For EIP-1559 the miner actually receives min(max_priority_fee_per_gas, max_fee_per_gas − base_fee). When the base fee is high, a transaction with max_priority_fee = 100 gwei, max_fee = 101 gwei (effective tip = 1 gwei) will have heap key 100 and will be kept ahead of a transaction with max_priority_fee = 5 gwei, max_fee = 200 gwei (effective tip = 5 gwei, key = 5). The eviction therefore keeps the less valuable transaction.

This is a semantic limitation worth calling out, even if the mempool lacks the current base fee at insertion time. The PR description should note it explicitly, and a follow-up to pass base_fee into tip_key / evict_lowest_tip_transaction is worth tracking.

2. Warning-then-break allows transaction_pool to silently exceed max_mempool_size.

// evict_lowest_tip_transaction
let Some(Reverse((_tip, hash))) = self.txs_by_tip.pop() else {
    warn!("Mempool is full but there are no transactions to remove …");
    break;   // ← returns Ok(()); caller still pushes + inserts
};

After the break, add_transaction continues to push to the heap and insert into transaction_pool, so the pool goes over capacity. This was the same behavior in the FIFO code and is not a regression, but it's the kind of invariant violation that can be hard to debug later. Consider returning an explicit Err or at minimum documenting that this path intentionally allows overfill.

3. Hash ordering as eviction tiebreaker.

The heap key is (u64, H256). Equal-tip ties break on the hash, which is lexicographic over raw bytes. Transaction hashes are deterministic (keccak of the signed bytes), so a sender who controls transaction content could in principle craft hashes to influence which of their same-tip transactions is evicted last. This is a low-severity concern (the attacker controls only their own transactions), but it is worth a comment acknowledging the tiebreaker is hash-order.


Performance

4. rebuild_tip_heap does O(n log n) individual pushes when O(n) heapify is available.

fn rebuild_tip_heap(&mut self) {
    let mut heap = BinaryHeap::with_capacity(self.max_mempool_size * 2);
    for (hash, tx) in self.transaction_pool.iter() {
        heap.push(Reverse((tip_key(tx.transaction()), *hash)));  // O(log n) each
    }
    self.txs_by_tip = heap;
}

BinaryHeap::from(Vec<_>) uses Floyd's algorithm and is O(n):

fn rebuild_tip_heap(&mut self) {
    let entries: Vec<_> = self.transaction_pool
        .iter()
        .map(|(hash, tx)| Reverse((tip_key(tx.transaction()), *hash)))
        .collect();
    self.txs_by_tip = BinaryHeap::from(entries);
}

For a mempool with tens of thousands of entries this is a real difference, and the rebuild path is already on the hot path (it fires under fee pressure).


Ordering subtlety in add_transaction

5. New hash is pushed to the heap before the transaction is inserted into transaction_pool.

The sequence is: rebuild → evict → heap.push(hash)txs_by_sender_nonce.insert → ... → transaction_pool.insert. If anything between the heap push and the pool insert fails (e.g., a nonce-replacement path that returns early), the heap will contain a stale entry. This is handled correctly by lazy deletion, so it is not a correctness bug, but the ordering is surprising and a comment would help.


Tests

6. No test for equal-tip eviction tiebreaking.

The four tests are good, but none inserts two transactions with identical max_priority_fee_per_gas and then forces an eviction. Given that the tiebreaker is hash-order (Point 3 above), a test that documents the expected behavior would prevent accidental regressions.

7. make_tx sets max_fee_per_gas == max_priority_fee_per_gas.

max_fee_per_gas: tip,

This is structurally valid but means every test transaction has zero headroom over the base fee. Tests that exercise realistic EIP-1559 values (where max_fee >> max_priority_fee) would catch the tip_key effective-tip issue described in Point 1.


Positive observations

  • The lazy-deletion approach is implemented correctly. The old remove_oldest_transaction called remove_transaction_with_lock on stale queue entries without checking existence; the new evict_lowest_tip_transaction explicitly skips tombstones — a silent correctness improvement.
  • The named constants MEMPOOL_PRUNE_THRESHOLD_NUM / DEN replace the inline max + max/2 expression cleanly and are documented well.
  • mempool_prune_threshold initialisation arithmetic is correct (3 * max / 2 = 1.5 × max) and matches the threshold check.
  • tip_key's saturating cast for U256 tips is the right choice; documenting that oversized tips saturate to u64::MAX (and therefore sort last for eviction) is accurate and helpful.

Automated review by Claude (Anthropic) · sonnet · custom prompt

@github-actions
Copy link
Copy Markdown

🤖 Codex Code Review

  1. Privileged txs are now eligible for fee-based eviction. tip_key() in crates/blockchain/mempool.rs treats every tx the same, but payload ordering still puts TxType::Privileged ahead of all others in crates/blockchain/payload.rs, and validate_transaction explicitly bypasses normal admission logic for them in crates/blockchain/blockchain.rs. Under a full mempool, later user txs can evict pending privileged/L1-message txs before they are sequenced. I’d keep privileged txs out of this heap or give them a separate protected class.

  2. The full-pool path evicts before the newcomer participates in ordering. In crates/blockchain/mempool.rs the code removes an existing tx, and only after that pushes the new one into the heap at crates/blockchain/mempool.rs. So a newcomer with tip < current_min_tip still displaces one resident: [100, 200] + 1 => [1, 200]. That defeats the intended “keep the best-paying txs” policy and leaves a spam slot. Either compare against the current min first, or insert first and evict the global minimum (which may be the newcomer).

  3. The heap key is not the same metric the builder uses. crates/blockchain/mempool.rs keys by gas_tip_cap(), while mempool filtering / payload ordering use effective_gas_tip(base_fee) in crates/blockchain/mempool.rs and crates/blockchain/payload.rs, and blob txs also depend on blob fee in crates/blockchain/mempool.rs. For EIP-1559/EIP-4844 txs this can retain currently unpayable or lower-value txs and evict txs that would actually yield more block value. If the goal is economic eviction, this needs current base_fee/blob_fee input; otherwise the comments should stop calling it “effective tip”.

The lazy-deletion/rebuild tests are useful, but they miss new_tip < current_min, privileged-tx retention, and max_fee != max_priority cases. I couldn’t run cargo test here because cargo/rustup tried to write under /home/runner/.rustup/tmp and hit a read-only filesystem.


Automated review by OpenAI Codex · gpt-5.4 · custom prompt

ilitteri added 2 commits May 12, 2026 15:11
Phase 2 review (Copilot):
- The `txs_by_tip` doc described entries as "(effective tip, hash)" but
  the key is the raw `gas_tip_cap` (not base-fee-adjusted).
- The `tip_key` doc said large tips "order to the top of the heap"; the
  `BinaryHeap<Reverse<...>>` is a min-heap so the largest tips actually
  rank highest and are evicted last.
- The prune-threshold comment referenced a stale
  `MEMPOOL_PRUNE_THRESHOLD_FACTOR` identifier; the code uses the
  `NUM / DEN` integer ratio (3/2 = 1.5x).

Wording updates only; no behavior change.
Phase 2 review (greptile P1): when the pool was full,
`evict_lowest_tip_transaction` unconditionally evicted the current
minimum-tip entry without checking whether the incoming tx's tip was
actually higher. Submitting a tip=1 tx to a pool full of tip=100
entries would evict a tip=100 and admit the tip=1 — the opposite of
the stated goal of tip-based eviction.

`add_transaction` now peeks the live minimum tip (skipping tombstones
via a new `live_min_tip` helper) before evicting. If the incoming tip
is less than or equal to the heap minimum, the incoming tx is rejected
with the new `MempoolError::PoolFullAndUnderpriced { incoming_tip,
min_pool_tip }`. Equal tips are rejected too — admitting at-tie would
needlessly churn the pool without economic benefit.

`add_transaction` return type changed from `Result<(), StoreError>` to
`Result<(), MempoolError>` to surface the new variant; existing
`?`-using callers continue to work because `StoreError` already has a
`From` impl into `MempoolError`.

Three new tests:
- `underpriced_newcomer_is_rejected_when_pool_is_full`
- `equal_tip_newcomer_is_rejected_when_pool_is_full`
- `same_sender_replacement_clears_heap_tombstone` (addresses the
  greptile P2 test-coverage gap on heap tombstone replacement flow)
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 12, 2026

Benchmark Block Execution Results Comparison Against Main

Command Mean [s] Min [s] Max [s] Relative
base 66.416 ± 0.428 65.572 66.935 1.00 ± 0.01
head 66.225 ± 0.333 65.668 66.750 1.00

@github-actions
Copy link
Copy Markdown

⚠️ Known Issues — intentionally skipped tests

Source: docs/known_issues.md

Known Issues

Tests intentionally excluded from CI. Source of truth for the Known
Issues
section the L1 workflow appends to each ef-tests job summary
and posts as a sticky PR comment.

Hive — bal-devnet-6 Amsterdam consume-engine tests — 32 functions / 54 cases

Same root cause as the blockchain-runner skip list (see EF Tests —
Blockchain
below): snobal-devnet-6 fixtures expect bal-devnet-6 spec
semantics, but our impl runs ahead due to the bal-devnet-7-prep
set_delegation SELFDESTRUCT-style refund subtraction. These fixtures
are routed through hive's eels/consume-engine simulator and produce
the same failures. Excluded via KNOWN_EXCLUDED_TESTS (substring
match on test_<name>[fork_Amsterdam, anchoring to the Amsterdam fork
so legacy Prague/Osaka variants still run).

Affected EELS test functions (32)
  • test_auth_refund_block_gas_accounting
  • test_auth_refund_bypasses_one_fifth_cap
  • test_auth_state_gas_scales_with_cpsb
  • test_auth_with_calldata_and_access_list
  • test_auth_with_multiple_sstores
  • test_authorization_exact_state_gas_boundary
  • test_authorization_to_precompile_address
  • test_authorization_with_sstore
  • test_bal_7702_delegation_clear
  • test_bal_7702_delegation_create
  • test_bal_7702_delegation_update
  • test_bal_7702_double_auth_reset
  • test_bal_7702_double_auth_swap
  • test_bal_7702_null_address_delegation_no_code_change
  • test_bal_all_transaction_types
  • test_bal_selfdestruct_to_7702_delegation
  • test_bal_withdrawal_to_7702_delegation
  • test_duplicate_signer_authorizations
  • test_existing_account_auth_header_gas_used_uses_worst_case
  • test_existing_account_refund
  • test_existing_account_refund_enables_sstore
  • test_existing_auth_with_reverted_execution_preserves_intrinsic
  • test_many_authorizations_state_gas
  • test_mixed_auths_header_gas_used_uses_worst_case
  • test_mixed_new_and_existing_auths
  • test_mixed_valid_and_invalid_auths
  • test_multi_tx_block_auth_refund_and_sstore
  • test_multiple_refund_types_in_one_tx
  • test_simple_gas_accounting
  • test_sstore_state_gas_all_tx_types
  • test_transfer_with_all_tx_types
  • test_varying_calldata_costs

EF Tests — Stateless coverage narrowed to EIP-8025 optional-proofs

make -C tooling/ef_tests/blockchain test calls test-stateless-zkevm
instead of test-stateless. The zkevm@v0.3.3 fixtures are filled against
bal@v5.6.1, out of sync with current bal spec; the broad target trips ~549
fixtures. Re-broaden once the zkevm bundle is regenerated.

Why and resolution path

PR #6527 broadened
test-stateless to extract the entire for_amsterdam/ tree from the
zkevm bundle and run all of it under --features stateless; combined with
this branch's bal-devnet-6+ semantics (and bal-devnet-7-prep
set_delegation re-application) that scope produces ~549
GasUsedMismatch / ReceiptsRootMismatch /
BlockAccessListHashMismatch failures.

test-stateless-zkevm filters cargo to the eip8025_optional_proofs
suite, which still validates the stateless harness without the bal-version
mismatch.

Re-broaden by switching test: back to test-stateless in
tooling/ef_tests/blockchain/Makefile once the zkevm bundle is regenerated
against the current bal spec.

EF Tests — Blockchain bal-devnet-6 (Amsterdam fork) — 74 tests

snobal-devnet-6 fixtures expect bal-devnet-6 spec semantics, but our impl
runs ahead due to the bal-devnet-7-prep set_delegation SELFDESTRUCT-style
refund subtraction. Skipped in
tooling/ef_tests/blockchain/tests/all.rs::SKIPPED_BASE, anchored on
[fork_Amsterdam so legacy Prague / Osaka variants still run.

Bucket breakdown (74 total) and resolution path
EIP Bucket Count
EIP-7702 set_code_txs 24
EIP-7702 set_code_txs_2 15
EIP-7702 gas 1
EIP-8037 state_gas_set_code 17
EIP-8037 state_gas_pricing 1
EIP-8037 state_gas_sstore 1
EIP-7928 block_access_lists_eip7702 8
EIP-7928 block_access_lists 1
EIP-7778 gas_accounting 3
EIP-7708 transfer_logs 1
EIP-7976 refunds 1
EIP-1344 chainid (Amsterdam fork-transition fixture) 1
Total 74

Re-enable once we either:

  • (a) bump fixtures to a snobal-devnet-7 release that locks in the new
    accounting; or
  • (b) revert the bal-devnet-7-prep subtraction for bal-devnet-6
    compatibility.
Full test list (74)

EIP-7702 — for_amsterdam/prague/eip7702_set_code_tx/set_code_txs/

  • delegation_clearing
  • delegation_clearing_and_set
  • delegation_clearing_failing_tx
  • delegation_clearing_tx_to
  • eoa_tx_after_set_code
  • ext_code_on_chain_delegating_set_code
  • ext_code_on_self_delegating_set_code
  • ext_code_on_self_set_code
  • ext_code_on_set_code
  • many_delegations
  • nonce_overflow_after_first_authorization
  • nonce_validity
  • reset_code
  • self_code_on_set_code
  • self_sponsored_set_code
  • set_code_multiple_valid_authorization_tuples_same_signer_increasing_nonce
  • set_code_multiple_valid_authorization_tuples_same_signer_increasing_nonce_self_sponsored
  • set_code_to_log
  • set_code_to_non_empty_storage_non_zero_nonce
  • set_code_to_self_destruct
  • set_code_to_self_destructing_account_deployed_in_same_tx
  • set_code_to_sstore
  • set_code_to_sstore_then_sload
  • set_code_to_system_contract

EIP-7702 — for_amsterdam/prague/eip7702_set_code_tx/set_code_txs_2/

  • call_pointer_to_created_from_create_after_oog_call_again
  • call_to_precompile_in_pointer_context
  • contract_storage_to_pointer_with_storage
  • delegation_replacement_call_previous_contract
  • double_auth
  • pointer_measurements
  • pointer_normal
  • pointer_reentry
  • pointer_resets_an_empty_code_account_with_storage
  • pointer_reverts
  • pointer_to_pointer
  • pointer_to_precompile
  • pointer_to_static
  • pointer_to_static_reentry
  • static_to_pointer

EIP-7702 — for_amsterdam/prague/eip7702_set_code_tx/gas/

  • account_warming

EIP-8037 — for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_set_code/

  • auth_refund_block_gas_accounting
  • auth_refund_bypasses_one_fifth_cap
  • auth_with_calldata_and_access_list
  • auth_with_multiple_sstores
  • authorization_exact_state_gas_boundary
  • authorization_to_precompile_address
  • authorization_with_sstore
  • duplicate_signer_authorizations
  • existing_account_auth_header_gas_used_uses_worst_case
  • existing_account_refund
  • existing_account_refund_enables_sstore
  • existing_auth_with_reverted_execution_preserves_intrinsic
  • many_authorizations_state_gas
  • mixed_auths_header_gas_used_uses_worst_case
  • mixed_new_and_existing_auths
  • mixed_valid_and_invalid_auths
  • multi_tx_block_auth_refund_and_sstore

EIP-8037 — state_gas_pricing/

  • auth_state_gas_scales_with_cpsb

EIP-8037 — state_gas_sstore/

  • sstore_state_gas_all_tx_types

EIP-7928 — for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_eip7702/

  • bal_7702_delegation_clear
  • bal_7702_delegation_create
  • bal_7702_delegation_update
  • bal_7702_double_auth_reset
  • bal_7702_double_auth_swap
  • bal_7702_null_address_delegation_no_code_change
  • bal_selfdestruct_to_7702_delegation
  • bal_withdrawal_to_7702_delegation

EIP-7928 — block_access_lists/

  • bal_all_transaction_types

EIP-7778 — for_amsterdam/amsterdam/eip7778_block_gas_accounting_without_refunds/gas_accounting/

  • multiple_refund_types_in_one_tx
  • simple_gas_accounting
  • varying_calldata_costs

EIP-7708 — for_amsterdam/amsterdam/eip7708_eth_transfer_logs/transfer_logs/

  • transfer_with_all_tx_types

EIP-7976 — for_amsterdam/amsterdam/eip7976_increase_calldata_floor_cost/refunds/

  • gas_refunds_from_data_floor

EIP-1344 — for_amsterdam/istanbul/eip1344_chainid/chainid/

  • chainid (Amsterdam fork-transition fixture)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

L1 Ethereum client performance Block execution throughput and performance in general

Projects

Status: No status
Status: Todo

Development

Successfully merging this pull request may close these issues.

2 participants