Skip to content

Implement EIP-7805 (FOCIL)#8003

Open
Marchhill wants to merge 223 commits into
masterfrom
feature/eip-7805
Open

Implement EIP-7805 (FOCIL)#8003
Marchhill wants to merge 223 commits into
masterfrom
feature/eip-7805

Conversation

@Marchhill

@Marchhill Marchhill commented Jan 3, 2025

Copy link
Copy Markdown
Contributor

Changes

  • Implement EIP-7805
  • engine api changes
    • implement engine_getInclusionList
    • add inclusion lists to PayloadAttributes and newPayload parameters
  • validate that valid IL txs were included up to gas limit (see here)
  • use IL in block building

Types of changes

What types of changes does your code introduce?

  • Bugfix (a non-breaking change that fixes an issue)
  • New feature (a non-breaking change that adds functionality)
  • Breaking change (a change that causes existing functionality not to work as expected)
  • Optimization
  • Refactoring
  • Documentation update
  • Build-related changes
  • Other: Description

Testing

Requires testing

  • Yes
  • No

If yes, did you write tests?

  • Yes
  • No

Documentation

Requires documentation update

  • Yes
  • No

Requires explanation in Release Notes

  • Yes
  • No

@Marchhill

Copy link
Copy Markdown
Contributor Author

@claude review

@claude

claude Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Claude finished @Marchhill's task in 4m 10s —— View job


EIP-7805 (FOCIL) PR Review — Round 16

Tasks:

  • Load repo rules and context
  • Check recent commits since Round 15
  • Read key changed files (LukaszRozmej Round 16 findings)
  • Verify previous findings status
  • Write summary

Progress since Round 15

Two commits have landed since the last Claude review (fcfbf783):

Commit Summary Issues
d3be4934 Address Lukasz review (round 5): 5 of 6 Round-16 Low findings ✅ See table below
564b2b8d Revert input-side MAX_BYTES_PER_INCLUSION_LIST check ✅ Correct reversal — hive FOCIL spec test asserts engine_forkchoiceUpdatedV5 must accept oversized ILs

LukaszRozmej Round 16 findings

Finding Status
IL re-validation skipped on duplicate engine_newPayloadV6 (Low) ✅ Fixed in d3be4934!HasInclusionList(block) guard added to all three short-circuits: IsOnMainChainBehindOrEqualHead, _latestBlocks cache lookup, and AlreadyKnown && WasProcessed
EncodeTransactionsUpToLimit early-exit uses wrong size bound (Low) ✅ Fixed in d3be4934 — now uses Eip7805Constants.MinTransactionSizeBytesLower (32 B) instead of MinTransactionSizeBytesUpper (100 B)
Reservoir over-allocates for blob-heavy mempools (Low/Polish) Open — Math.Min(mempool.Length, capacity) was already added in a prior round; further improvement (pre-counting non-blob txs) noted as "Probably not worth the complexity" by LukaszRozmej
No input-side MaxBytesPerInclusionList check (Low) ✅ Added in d3be4934 then correctly reverted in 564b2b8d — the hive FOCIL spec test confirms the byte cap applies only to engine_getInclusionListV1 output, not to FCU/newPayload input
InclusionListTxSource.Decoder lazy init not thread-safe (Low) ✅ Fixed in d3be4934??= replaced with Lazy<T> (default ExecutionAndPublication mode, once-only construction under races)
BlockValidator.ValidateInclusionList mutates processedBlock (Low) ✅ Fixed in d3be4934IsSatisfied now has a two-arg overload; BlockValidator passes suggestedBlock.InclusionListTransactions directly without touching processedBlock

Verdict: mergeable. All Critical, High, and Medium findings across all review rounds are resolved. The sole remaining item — reservoir over-allocation in InclusionListBuilder for blob-heavy mempools — is a pure-perf Polish observation that LukaszRozmej acknowledged as likely not worth the added complexity.

ArrayPoolList<Transaction> for the reservoir — Count tracks actual non-blob
fill, so the prior alloc + Array.Resize + copy pair when the mempool yielded
fewer than capacity non-blob txs is gone. Buffer is rented from the shared
ArrayPool and disposed via the using in GetInclusionList.

Addresses Lukasz's #4 round-5 comment.
@Marchhill Marchhill requested a review from LukaszRozmej June 12, 2026 14:42
Marchhill and others added 8 commits June 19, 2026 15:40
A tx whose GasLimit is below its intrinsic gas can never execute, so the
block legitimately omitting it must not trip INCLUSION_LIST_UNSATISFIED.

Caught by EEST `test_block_with_intrinsic_gas_too_low_pending_il_tx_is_valid`
(see ethereum/execution-specs tests-focil@v0.0.1 after the eip_7805 / Bogota
patch that bumps fixtures to engine_newPayloadV6).
Locks in the behaviour added in 3e7ef38 — a tx whose GasLimit is
below the intrinsic gas cost must be treated as not-appendable so the
block omitting it does not trip INCLUSION_LIST_UNSATISFIED.

Mirrors EEST `test_block_with_intrinsic_gas_too_low_pending_il_tx_is_valid`.
# Conflicts:
#	src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs
#	src/Nethermind/Nethermind.Merge.Plugin/MergeErrorCodes.cs
#	src/Nethermind/Nethermind.Merge.Plugin/SszRest/Handlers/SszRestPaths.cs
Post-merge build needs the namespace for EngineApiVersions, which moved
under Nethermind.Core. Local build was green only because the file had
been edited but never staged into the merge commit.
* fix: build failure due to missing 'using Nethermind.Core'

* Update src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Bogota.cs

---------

Co-authored-by: Lukasz Rozmej <lukasz.rozmej@gmail.com>
# Conflicts:
#	src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs
Adds a BogotaEngineBlockchainTests Pyspec fixture sourced from the
tests-focil@v0.1.0 EELS release archive (separate from the BAL archive),
plus the plumbing needed for FOCIL-shaped engine fixtures:

- SpecNameParser recognises "Bogota" so JsonToEthereumTest.LoadSpec
  doesn't throw NotSupportedException when parsing focil fixture network.
- TestEngineNewPayloadsJson grows InclusionListTransactions and Status
  fields; the V6 path appends IL as the 5th positional arg to
  engine_newPayloadV6, and AssertPayloadStatus honours the fixture's
  explicit Status (covers INCLUSION_LIST_UNSATISFIED).
- Constants.cs gains FOCIL_ARCHIVE_VERSION / NAME for the separate
  release archive.

13 of the 24 collected tests pass against the existing IL pipeline; the
remaining 11 (the "unsatisfied_*" scenarios) expose a postState question
— EELS expects the block to be on-chain when engine returns
INCLUSION_LIST_UNSATISFIED, but our flow rolls the block back. Tracked
as a follow-up rather than blocking this CI bump.
@Marchhill Marchhill marked this pull request as draft June 24, 2026 15:05
EIP-7805 treats an IL-unsatisfied block as a *valid, executable* block
that just didn't honour the inclusion list — the CL gets the
INCLUSION_LIST_UNSATISFIED status and decides canonical inclusion via
fork-choice. Throwing InclusionListUnsatisfiedException after execution
rolled the block back even though its txs had already mutated state,
which diverged from EELS fixtures whose postState reflects the
committed block.

- Add Block.IsInclusionListSatisfied (default true).
- BlockProcessor.ProcessOne sets the flag instead of throwing; the
  block is committed normally and receipts are stored.
- BlockchainProcessor.ProcessBlocks checks the flag and emits
  ProcessingResult.InclusionListUnsatisfied via BlockRemoved (so
  NewPayloadHandler still returns the same engine status to the CL).
- InclusionListUnsatisfiedException removed (no callers left).
- Test harness: send FCU after INCLUSION_LIST_UNSATISFIED too, so the
  chain head advances to the committed block and the EELS fixture's
  lastblockhash + postState assertions match.

Verified end-to-end: all 24 BogotaEngineBlockchainTests pass against
tests-focil@v0.1.0 (was 13/24).
@Marchhill

Copy link
Copy Markdown
Contributor Author

@claude review

@claude

claude Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Claude finished @Marchhill's task in 7m 7s —— View job


EIP-7805 (FOCIL) PR Review — Round 17

Tasks:

  • Load repo rules and context
  • Read new commits since Round 16
  • Check Round 16 findings are resolved
  • Review key changed files
  • Post inline comments
  • Write summary

Progress since Round 16

Round 16 reviewed up to 564b2b8d. Two FOCIL-specific commits have landed since then:

Commit Summary Issues
57bed131 test: wire EEST tests-focil@v0.1.0 fixtures into CI ✅ See below
0dd60777 FOCIL: signal IL-unsatisfied via Block flag, don't throw to reject ✅ See below

57bed131 — EELS FOCIL fixture wiring

This adds BogotaEngineBlockchainTests (24 FOCIL fixtures from tests-focil@v0.1.0) to CI. The plumbing is clean:

  • AssertPayloadStatus now accepts a fixtureStatus string: when the fixture sets status explicitly (e.g. INCLUSION_LIST_UNSATISFIED), that wins over the legacy validationError → INVALID heuristic — backward-compatible for existing fixtures.
  • BlockchainTestBase sends a FCU after both VALID and INCLUSION_LIST_UNSATISFIED so the chain head advances to the committed block, matching EELS lastblockhash / postState assertions. The commit note explains 13/24 were passing before this; the follow-up (0dd60777) brings all 24 to passing.
  • SpecNameParser now maps "Bogota" to Bogota.Instance. ✅

No issues.


0dd60777 — Flag-based INCLUSION_LIST_UNSATISFIED signalling

This is a significant architectural correction: previously BlockProcessor.ProcessOne threw InclusionListUnsatisfiedException after execution, which rolled back the block from the chain even though the EVM had already committed its transactions. EELS fixtures assert a valid postState for INCLUSION_LIST_UNSATISFIED scenarios, so the exception-based flow was spec-incorrect.

The fix:

  • Adds Block.IsInclusionListSatisfied (default true, [JsonIgnore]).
  • BlockProcessor.ProcessOne sets the flag after ValidateProcessedBlock succeeds, on both the processed and suggested block objects — correct propagation through the processedBlocks array.
  • BlockchainProcessor.ProcessBlocks reads processedBlock.IsInclusionListSatisfied (which IS the block returned by ProcessOne, not a copy) and fires ProcessingResult.InclusionListUnsatisfied via BlockRemoved.
  • InclusionListUnsatisfiedException is fully removed (no callers left). ✅

The flow through NewPayloadHandler is unchanged: ProcessingResult.InclusionListUnsatisfied still maps to ValidationResult.InclusionListUnsatisfiedINCLUSION_LIST_UNSATISFIED returned to the CL.

Key invariants verified:

  • EthereumMerge includes DoNotUpdateHead, so the block is committed but the chain head is not moved by the engine-API path. ✅
  • IsSatisfied returning true for null IL (P2P blocks, non-Bogota) keeps the default IsInclusionListSatisfied = true — no regression on pre-Bogota blocks. ✅
  • NoValidation short-circuit in ValidateInclusionList keeps IsInclusionListSatisfied = true for simulations. ✅
  • Duplicate newPayload calls with IL correctly bypass all caches (Round 16 guards remain in place). ✅

Remaining issues

Low

NotifyInclusionListUnsatisfied log message uses "Invalid" — misleading per spec terminology

EIP-7805 treats an IL-unsatisfied block as valid and executable — the CL receives INCLUSION_LIST_UNSATISFIED and decides canonical inclusion via fork-choice. The current message "Invalid inclusion list for block" may confuse operators reading logs into thinking the block itself is invalid. A one-word suggestion:

if (_logger.IsTrace) _logger.Trace($"Inclusion list unsatisfied for block {block.ToString(Block.Format.Full)}");

Verdict: mergeable. All Critical, High, and Medium findings across all 17 review rounds are resolved. The sole remaining item is a cosmetic log-message word choice that does not affect correctness, consensus, or security.

[MethodImpl(MethodImplOptions.NoInlining)]
void NotifyInclusionListUnsatisfied(BlockRef blockRef, Block block)
{
if (_logger.IsTrace) _logger.Trace($"Invalid inclusion list for block {block.ToString(Block.Format.Full)}");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Low — log message says "Invalid" but the spec calls this a valid, executable block

EIP-7805 treats IL-unsatisfied blocks as structurally valid — the CL receives INCLUSION_LIST_UNSATISFIED and decides canonical inclusion via fork-choice. Logging "Invalid inclusion list" may confuse operators into thinking the block itself is invalid.

Suggested change
if (_logger.IsTrace) _logger.Trace($"Invalid inclusion list for block {block.ToString(Block.Format.Full)}");
if (_logger.IsTrace) _logger.Trace($"Inclusion list unsatisfied for block {block.ToString(Block.Format.Full)}");

@Marchhill Marchhill marked this pull request as ready for review June 24, 2026 23:20
@claude

claude Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Claude finished @Marchhill's task in 3m 12s —— View job


EIP-7805 (FOCIL) PR Review — Round 18

Tasks:

  • Load repo rules and context
  • Read new commits since Round 17
  • Verify Round 17 Low finding (log message wording)
  • Review new commits for regressions or new issues
  • Post inline comments
  • Write summary

Progress since Round 17

Round 17 reviewed up to 0dd60777. No new FOCIL-specific commits have landed since then.

Round 17 finding Status
NotifyInclusionListUnsatisfied log says "Invalid inclusion list for block" — misleading per spec (Low) ❌ Still open — BlockchainProcessor.cs:432 unchanged

New commits reviewed

Commit Summary Issues
a75fc096 Merge upstream/feature/eip-7805 ✅ Merge-only, no FOCIL changes

The 0dd60777 / 57bed131 commits were already reviewed in Round 17 and are correct:

  • IsInclusionListSatisfied flag propagates correctly through ProcessOne → both block and suggestedBlock are stamped → BlockchainProcessor.ProcessBlocks reads processedBlock.IsInclusionListSatisfied from the right object.
  • EthereumMerge includes DoNotUpdateHead, so an IL-unsatisfied block is committed to state without advancing the chain head — head movement is left to the CL's FCU call, matching spec intent.
  • EEST test harness: the FCU-after-INCLUSION_LIST_UNSATISFIED path correctly advances the chain head to the committed block, so lastblockhash / postState assertions from EELS fixtures pass (all 24 BogotaEngineBlockchainTests).

Remaining issues

Low (carried from Round 17)

BlockchainProcessor.NotifyInclusionListUnsatisfied log says "Invalid"

BlockchainProcessor.cs:432: _logger.Trace($"Invalid inclusion list for block ...") should read "Inclusion list unsatisfied for block ..." — the block is valid and committed per EIP-7805.

Low (new)

NewPayloadHandler.cs:268 uses the same misleading wording at IsInfo level

The IsInfo log "Invalid inclusion list. Result of {requestStr}." is more visible to operators than the Trace-level one above. Inline suggestion posted. → discussion_r3470895076


Verdict: mergeable. All Critical, High, and Medium findings across all 18 review rounds are resolved. The two remaining items are cosmetic log-message wording (Low) and do not affect correctness, consensus, or security.

Comment thread src/Nethermind/Nethermind.Merge.Plugin/Handlers/NewPayloadHandler.cs Outdated
Marchhill and others added 5 commits June 25, 2026 11:09
…ler.cs

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
- InclusionListTxSource.GetTransactions: long → ulong gasLimit to match
  the new ITxSource signature from upstream.
- InclusionListValidator: (long) → (ulong) cast on IntrinsicGasCalculator
  result; EthereumIntrinsicGas now exposes ulong via its explicit
  conversion.
- Test sites: WithNonce/AccountStruct ctors + GetTransactions args
  updated to ulong; UInt256 Nonce comparisons in tests switched to ulong
  literals (the older comparisons silently fail because of the type
  mismatch under the NUnit2021 analyzer).
- BlockchainProcessor.NotifyInclusionListUnsatisfied: log wording from
  "Invalid inclusion list" → "Inclusion list unsatisfied" (Round 18 Low
  review point) — the block is valid + committed; only the IL signal
  failed.
if (payloadStatus.Status == PayloadStatus.Valid)
// FCU after VALID — and also after INCLUSION_LIST_UNSATISFIED so the chain head
// advances to the committed block, matching the EELS fixture's lastblockhash/postState.
if (payloadStatus.Status == PayloadStatus.Valid || payloadStatus.Status == PayloadStatus.InclusionListUnsatisfied)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

is + or

public string? ValidationError { get; set; }
// EIP-7805: emitted by the test fixture out-of-band from `params` (rather than as a 5th
// positional arg) so we append it explicitly when V6 is the target.
public JsonElement? InclusionListTransactions { get; set; }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can be more typed?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Exactly why not proper type structure?


namespace Nethermind.Blockchain.Test.Validators;

public class InclusionListValidatorTests

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can it be TestCaseSource based?

WarmingState<Block>.FinallyAction);

// Prewarm inclusion list transaction senders too — they may be promoted into the block.
if (block.InclusionListTransactions is { Length: > 0 } inclusionList)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Potentially can be united with the top statement, where i above state.Payload.Transactions count means InclusionListTransactions

_ => null
};

string? validationMessage = e.ProcessingResult switch

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Does not account for InclusionListUnsatisfied seems like

// FOCIL is conditional: no gas left for a base-cost transfer → nothing is appendable.
if (block.GasUsed + Transaction.BaseTxGasCost > block.GasLimit) return true;

Span<bool> included = il.Length <= Eip7805Constants.MaxTransactionsPerInclusionList

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should we decline instead if tx count is too big?

/// <inheritdoc/>
public bool ValidateInclusionList(Block processedBlock, Block suggestedBlock, IWorldState worldState, ProcessingOptions options)
{
if (options.ContainsFlag(ProcessingOptions.NoValidation)) return true;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is not it's something that should happen in some external code?

public const int MaxBytesPerInclusionList = 8192;
// 32 bytes as conservative lower bound for transaction size
public const int MinTransactionSizeBytesLower = 32;
public const int MinTransactionSizeBytesUpper = 100;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What is MinTransactionSizeBytesUpper for?

return true;
}

private static bool CouldIncludeTx(Transaction tx, Block block, IReadOnlyStateProvider state, IReleaseSpec spec, ref Dictionary<AddressAsKey, AccountStruct>? senderCache)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Probably does not account for several txs from one account(I don't see nonce, balance counting)

IRlpDecoder<Transaction>? rlpDecoder = Rlp.GetDecoder<Transaction>();
if (rlpDecoder is null) return new TransactionDecodingResult($"{nameof(Transaction)} decoder is not registered");

return TryDecodeParallel(txData, rlpDecoder, skipErrors, out TransactionDecodingResult result)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can be simplified a bit if you just use txData.Length < ParallelDecodeThreshold for branching into parallel or seq, so you can omit try pattern

@flcl42

flcl42 commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

From codex

  1. src/Nethermind/Nethermind.Consensus/Transactions/InclusionListTxSource.cs:22 stores the inclusion list in singleton mutable state, while GetTransactions(...) ignores the current payloadAttributes. engine_forkchoiceUpdatedV5 writes that singleton before payload building, and asynchronous block improvement can build payload A after payload B has overwritten the list. Impact: a payload ID can be built with another request's inclusion list, or with a stale list. Fix: bind the decoded inclusion list to the specific payload/build context, or decode from the current payload attributes, and add an interleaved two-FCU regression test.

  2. Bogota SSZ Engine API routes are advertised but not wired. EngineRpcCapabilitiesProvider.cs:131 advertises SSZ paths for engine_newPayloadV6, engine_getInclusionListV1, and engine_forkchoiceUpdatedV5; SszRestPaths.cs:114 defines those paths; but SszMiddlewareConfigurer.cs:48 still registers only newPayload V1-V5 and forkchoice V1-V4, with no inclusion-list handler. SszCodec.cs:300 also cannot encode INCLUSION_LIST_UNSATISFIED. Impact: clients can select advertised SSZ endpoints that 404 or fail dispatch/encoding. Fix: implement and register the Bogota SSZ descriptors, route mapping, inclusion-list handler, and status encoding, or stop advertising these routes.

  3. engine_newPayloadV6 is not gated on Bogota/EIP-7805 activation. EngineRpcModule.Bogota.cs:32 delegates to ExecutionPayloadV4, whose fork validation only checks the Amsterdam/BAL fork, and IExecutionPayloadParams.cs:71 only requires the inclusion-list field when the spec already has inclusion lists enabled. Impact: a V6 Engine API method can be accepted at an Amsterdam-but-not-Bogota timestamp instead of returning unsupported fork. Fix: reject V6 unless the release spec has EIP-7805 enabled, ideally through a general method-version gate against the fork's expected Engine API version.

  4. Inclusion-list block production prepends IL transactions once, but validation is based on post-execution appendability. InclusionListBlockProducerTxSourceFactory.cs:12 emits IL transactions before the base tx source; BlockProductionTransactionsExecutor.cs:58 enumerates the stream once and does not retry skipped IL transactions after later transactions mutate nonce or balance. The branch's own InclusionListValidatorTests.cs:226 covers post-execution satisfaction semantics. Impact: Nethermind can build a payload that later fails local newPayloadV6 with INCLUSION_LIST_UNSATISFIED because an IL transaction became appendable only after normal payload transactions executed. Fix: make production IL-aware and revisit remaining IL transactions after state-changing accepted transactions, or after the normal selection pass, until no remaining IL transaction is appendable.

  5. InclusionListValidator.cs:61 uses unchecked appendability arithmetic: block.GasUsed + tx.GasLimit > block.GasLimit can wrap, and the balance check computes tx.Value + gasLimit * maxFee without a visible overflow guard. Impact: oversized or overflowing IL transactions can be treated as appendable, incorrectly causing INCLUSION_LIST_UNSATISFIED for an otherwise valid payload. Fix: compare against remaining gas via subtraction after guarding GasUsed <= GasLimit, and use existing overflow-aware UInt256 helpers or explicit pre-checks for gasLimit * maxFee + value.

  6. Engine API input does not enforce the EIP-7805 inclusion-list byte limit. Eip7805Constants.cs:7 defines MaxBytesPerInclusionList = 8192, but IExecutionPayloadParams.cs:71 only null-checks InclusionListTransactions, and EngineRpcModule.Bogota.cs:44 decodes FCUv5 inclusion-list transactions before any total-size validation. Impact: oversized protocol input can be accepted and can drive unnecessary decode/recovery/allocation work. Fix: reject null entries and total encoded inclusion-list bytes above MaxBytesPerInclusionList before decode/recovery for both newPayloadV6 and FCUv5 payload attributes.

  7. NewPayloadHandler.cs:418 avoids the normal already-known VALID shortcut when a known block has an inclusion list, but the requeue path does not force processing. BlockchainProcessor.cs:477 can skip such a block when it is not better than head, producing a processing error instead of a deterministic IL result. Impact: repeated engine_newPayloadV6 for an already-processed block can return a spurious invalid/processing error instead of VALID or INCLUSION_LIST_UNSATISFIED. Fix: for AlreadyKnown && HasInclusionList(block), validate the supplied IL directly against the known post-state or run a deterministic forced/read-only revalidation path.

@flcl42

flcl42 commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

@claude review the code and review comments(confirm their relevance)

@claude

claude Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Claude finished @flcl42's task in 4m 19s —— View job


EIP-7805 (FOCIL) — Code review + relevance check of the Codex findings

I read the current code for each of the 7 Codex points (and spot-checked flcl42's inline notes). Verdict on each below, with my own severity.


Codex findings — confirmed relevance

1. InclusionListTxSource is singleton mutable state; GetTransactions ignores payloadAttributes — VALID (Medium).
InclusionListTxSource.cs:25-29 returns Volatile.Read(ref _inclusionListTransactions) and ignores payloadAttributes. engine_forkchoiceUpdatedV5 (EngineRpcModule.Bogota.cs:50) overwrites that one field per FCU. Because the payload id encodes the IL digest (PayloadAttributes.ComputeInclusionListDigest), payloads A and B get distinct ids — but they share the single tx source. An async improvement cycle for payload A that runs after FCU(B) has overwritten the field will build A's payload with B's IL. The Volatile read/write makes it memory-safe but not correct under interleaving. Binding the decoded IL to the build context (or keying it by payload id) is the right fix. A two-interleaved-FCU regression test is warranted.

2. Bogota SSZ routes advertised but not wired — VALID (Medium, Bogota-devnet only).
Confirmed end-to-end:

  • EngineRpcCapabilitiesProvider.cs:131-133 advertises newPayloadV6 / getInclusionListV1 / forkchoiceUpdatedV5 SSZ paths (gated on IsEip7805Enabled).
  • SszRestPaths.cs:114-116 defines PostV6Payloads / PostV5Forkchoice / GetV1InclusionList.
  • SszMiddlewareConfigurer.cs:48-64 registers NewPayload V1–V5 and Forkchoice V1–V4 only — no V6 newPayload handler, no V5 forkchoice handler, no inclusion-list handler.
  • SszCodec.cs:300-307 EngineStatusToSsz has no case for INCLUSION_LIST_UNSATISFIED and throws on unknown status.

So a CL selecting the advertised SSZ endpoint for any Bogota method will 404 / fail to dispatch, and a Bogota block returning INCLUSION_LIST_UNSATISFIED over SSZ would throw. Since Bogota is parked at a far-future timestamp these aren't advertised on mainnet, but on a FOCIL devnet they are. Fix: wire the Bogota SSZ descriptors + status byte, or stop advertising them until SSZ support lands.

3. engine_newPayloadV6 not gated on Bogota activation — VALID but pre-existing pattern (Low).
ExecutionPayloadParams.ValidateInitialParams (IExecutionPayloadParams.cs:71) only requires the IL field when spec.InclusionListsEnabled, and ValidateEngineApiVersionParams never rejects a method version that is too new for the timestamp. So a V6 call at an Amsterdam-but-not-Bogota timestamp is accepted and behaves like V5 (IL ignored). This is a real spec-strictness gap, but it is the existing Nethermind behaviour for every newPayload version, not something FOCIL introduced — the block is still validated correctly for its timestamp. Worth a method-version-vs-fork gate as hardening, but lower priority than 1/2/4/7.

4. Production prepends IL once; validation is post-execution appendability — VALID (Medium).
InclusionListBlockProducerTxSourceFactory.cs:12 puts the IL source first, and BlockProductionTransactionsExecutor.ProcessTransactions (:66-83) enumerates the combined stream exactly once with no re-visit of skipped IL txs. An IL tx that is not yet appendable when first seen (e.g. nonce gap filled by a later mempool tx from the same sender) is dropped and never retried; post-execution it becomes appendable, so the builder's own engine_newPayloadV6 would return INCLUSION_LIST_UNSATISFIED for a block it just built. Liveness/self-consistency bug. Fix: make production IL-aware and re-test remaining IL txs after state-changing txs until none are appendable.

5. Unchecked arithmetic in CouldIncludeTx — VALID (Low).
InclusionListValidator.cs:61 block.GasUsed + tx.GasLimit > block.GasLimit can overflow long for an adversarial IL tx with a near-long.MaxValue gas limit (wraps negative → comparison false → not rejected by the gas guard). In most cases the subsequent Balance >= Value + GasLimit*MaxFeePerGas (UInt256) catches it, but a zero-fee / zero-value tx slips through and a valid block gets falsely flagged INCLUSION_LIST_UNSATISFIED. Prefer block.GasUsed > block.GasLimit - tx.GasLimit after guarding GasUsed <= GasLimit.

6. Enforce MaxBytesPerInclusionList on Engine API input — NOT RELEVANT (conflicts with spec test).
This was tried and deliberately reverted in commit 564b2b8d because the hive FOCIL spec test asserts engine_forkchoiceUpdatedV5 must accept oversized ILs — the 8192-byte cap is an output-side bound on engine_getInclusionListV1, not an input-side rejection for FCU/newPayload. Adding the check back would re-break that test. I'd reject this finding as written. (A null-entry guard alone could still be considered, but the byte-cap rejection should not be added.)

7. Already-known + IL requeue isn't forced → spurious error — VALID (Medium).
Confirmed the mechanism:

  • The Round-16 !HasInclusionList(block) guards (NewPayloadHandler.cs:194, 379, 418) correctly force an IL-bearing duplicate past the three Valid short-circuits into _processingQueue.Enqueue.
  • But it's enqueued with _defaultProcessingOptions = EthereumMerge, which is MarkAsProcessed | DoNotUpdateHead | IgnoreParentNotOnMainChainno ForceProcessing (ProcessingOptions.cs:78).
  • BlockchainProcessor.Process (:478-487) only processes when IsGenesis || IsBetterThanHead || ForceProcessing. A re-submitted already-processed block is not better than head → Process returns nullNotifyFailedOrSkippedProcessingResult.ProcessingError → CL gets INVALID instead of VALID/INCLUSION_LIST_UNSATISFIED.

So the Round-16 cache-bypass is incomplete: it forces re-validation but the requeue path can't actually re-run. Fix: add ForceProcessing for IL re-validation, or run a deterministic read-only IL re-check against the known post-state on the cache-hit path. No test covers same-hash-different-IL today.


flcl42 inline comments (2026-06-29) — quick relevance pass

  • Eip7805Constants.cs:11 "What is MinTransactionSizeBytesUpper for?" — Valid (Low): the constant is defined but never referenced anywhere; dead. Remove it or use it.
  • NewPayloadHandler.cs:484 "Does not account for InclusionListUnsatisfied" — Not a bug: the validationResult switch (:476) does map InclusionListUnsatisfied; the validationMessage switch deliberately returns null for it (no error text needed for a valid block). Fine as-is, maybe a clarifying comment.
  • PayloadAttributes.cs:152 "tx lengths can be the same → same payload id" — Resolved: ComputeInclusionListDigest now length-prefixes each entry and hashes its bytes (:145-154), so distinct ILs hash distinctly (modulo a Keccak collision). Stale concern.
  • InclusionListValidator.cs:56 "doesn't account for several txs from one account" — Not a correctness bug: IsSatisfied only needs to find one missing-but-appendable IL tx to fail; per-tx independent checks against post-state are sufficient. Worth a one-line comment to preempt the question.
  • The remainder (BlockchainTestBase.cs:373 typo, TestEngineNewPayloadsJson typing, InclusionListValidatorTests TestCaseSource, BlockCachePreWarmer.cs:407 unification, InclusionListDecoder.cs:38/43 span nits, RecoverSignatures.cs:36, TxsDecoder.cs:17 branch simplification) — all valid Low/Polish style/readability suggestions, none affect correctness.

Summary

# Finding Verdict Severity
1 IL tx-source not bound to payload context Confirmed Medium
2 Bogota SSZ routes advertised, not wired Confirmed Medium
3 V6 not gated on Bogota Confirmed, pre-existing pattern Low
4 Production not IL-aware (no retry) Confirmed Medium
5 Overflow in appendability arithmetic Confirmed Low
6 Enforce input byte-limit Reject — conflicts with hive spec test (564b2b8d)
7 Already-known + IL requeue not forced Confirmed Medium

Findings 1, 2, 4, 7 (Medium) are real and newly raised — not yet acknowledged with rationale in prior rounds. Recommend addressing (or explicitly documenting the rationale for) these before merge. Finding 6 should be dismissed with a reference to the 564b2b8d revert.

@LukaszRozmej LukaszRozmej left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Excessive comments
More TestCase/Source?
Update SSZ REST to newest format

Assert.That(validationError, Is.Not.Null, $"engine_newPayloadV{payloadVersion} RPC error: {errorCode} {errorMessage}");

private static void AssertPayloadStatus(PayloadStatusV1 payloadStatus, string? expectedValidationError, int payloadVersion)
private static void AssertPayloadStatus(PayloadStatusV1 payloadStatus, string? fixtureStatus, string? expectedValidationError, int payloadVersion)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

should fixtureStatus be default null? Also maybe call it explicitStatus?

public string? ValidationError { get; set; }
// EIP-7805: emitted by the test fixture out-of-band from `params` (rather than as a 5th
// positional arg) so we append it explicitly when V6 is the target.
public JsonElement? InclusionListTransactions { get; set; }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Exactly why not proper type structure?

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

Labels

bogota eip new feature taiko related to the taiko alethia rollup

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants