Skip to content

Resolve TODOs and Improve EIP-7805 (FOCIL) Spec #4351

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

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

jihoonsong
Copy link
Contributor

@jihoonsong jihoonsong commented Jun 1, 2025

This PR resolves TODOs and improves the honest validator section of EIP-7805 spec. It contains minimal test changes and rest of tests will be added by the subsequent PR.

@jihoonsong jihoonsong marked this pull request as draft June 1, 2025 10:35
@jihoonsong jihoonsong force-pushed the focil-tests branch 2 times, most recently from 93df232 to 3824af8 Compare June 1, 2025 15:15
@jihoonsong jihoonsong force-pushed the focil-tests branch 4 times, most recently from f6951fa to 281edd1 Compare June 19, 2025 23:01
@jihoonsong jihoonsong changed the title Add EIP-7805 (FOCIL) Tests Resolve TODOs and Improve EIP-7805 (FOCIL) Spec Jun 19, 2025
@jihoonsong jihoonsong marked this pull request as ready for review June 19, 2025 23:31
@ethereum ethereum deleted a comment from ethereum-code-reviewer bot Jun 20, 2025
@GrapeBaBa
Copy link

There is a constant that is inconsistent in the SEPC and EIP documents.
In the EIP it used MAX_TRANSACTIONS_PER_INCLUSION_LIST, how in the spec it used MAX_TRANSACTIONS_PER_PAYLOAD.

@jihoonsong
Copy link
Contributor Author

As of now, the latest discussion on this PR can be found here: https://hackmd.io/@jihoonsong/S1c2dhMrle

Please review it and provide some feedback. Thank you.

@jtraglia jtraglia changed the base branch from dev to master July 9, 2025 19:50
@jtraglia jtraglia added the eip7805 FOCIL label Jul 9, 2025
@jihoonsong
Copy link
Contributor Author

This PR has been followed by lots of discussions and changes. To preserve history, those changes are contained in the second commit. For easier review, here is the change log:

  • Add InclusionListStore to specify IL equivocation handling logic and IL transactions retrieval while abstracting it from the perspective of the Store.
    • This comes with get_inclusion_list_store, on_inclusion_list, store_inclusion_list and get_inclusion_list_transactions.
    • Compared to the first version of EIP-7805 spec, on_inclusion_list is modified to not repeat the p2p networking validation rule, consistent with other fork choice handlers.
  • Add validate_inclusion_lists to the fork choice module for IL validation in on_block. validate_inclusion_lists validates the IL constraints using the ExecutionEngine interface.
  • Add notify_inclusion_lists to ExecutionEngine, which is used for IL validation.
    • The name notify_inclusion_lists aligns with existing similar functions, which are notify_new_payload and notify_forkchoice_updated.
  • Revert changes to state_transition and use validate_inclusion_lists, an abstract ExecutionEngine call, for IL validation.
  • Add get_inclusion_list to ExecutionEngine.
    • An inclusion list committee member uses this ExecutionEngine interface to perform their inclusion list committee duty.
  • Modify notify_forkchoice_updated to add inclusion_list_transactions to PayloadAttributes.
    • The proposer uses this ExecutionEngine interface to produce an execution payload that satisfies the IL constraints.
  • Specify that the block for slot N satisfies the ILs for slot N-1.
    • The block for slot N is built during slot N-1 (without timing games) and satisfies the ILs for the same slot.
  • Reformat Honest Validator to match the existing spec writing convention.
    • It now uses the ExecutionEngine interface instead of the actual Engine APIs.
    • Some sections were reformatted to match those of previous forks.
  • Modify get_inclusion_list_committee's return type to support hash_tree_root calculation.

Copy link
Member

@jtraglia jtraglia left a comment

Choose a reason for hiding this comment

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

@jihoonsong this looks great, nice work! Just a couple nits + notes for later.

Comment on lines +189 to +192
key = (inclusion_list.slot, inclusion_list.inclusion_list_committee_root)

inclusion_lists = store.inclusion_lists.setdefault(key, set())
equivocators = store.equivocators.setdefault(key, set())
Copy link
Member

Choose a reason for hiding this comment

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

The setdefault stuff here is ugly 😅 I'll look into fixing that in a follow up PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

I believe we should just remove setdefault stuff. This is because the store must already be initialized outside of this function call, thus setdefault becomes redundant

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's not about store. It's just about Python's Dictionary.

I also don't like this part and that's why I asked @jtraglia if we can use DefaultDict. Let me think about how to improve this.

Comment on lines +233 to +240
inclusion_list_transactions = {
transaction
for inclusion_list in inclusion_lists
if inclusion_list.validator_index not in equivocators
for transaction in inclusion_list.transactions
}

return list(inclusion_list_transactions)
Copy link
Member

Choose a reason for hiding this comment

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

This is implicitly deduping the list of transactions, right? I think we should make this a little more explicit, because I almost missed this myself. Maybe something like this:

Suggested change
inclusion_list_transactions = {
transaction
for inclusion_list in inclusion_lists
if inclusion_list.validator_index not in equivocators
for transaction in inclusion_list.transactions
}
return list(inclusion_list_transactions)
inclusion_list_transactions = [
transaction
for inclusion_list in inclusion_lists
if inclusion_list.validator_index not in equivocators
for transaction in inclusion_list.transactions
]
# Deduplicate, order does not need to be preserved
return list(set(inclusion_list_transactions))

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well, the deduplication comes from the fact that inclusion_lists field of InclusionListStore has the value of set. Would adding a comment regarding this inside get_inclusion_list_transactions enhance readability?


## New inclusion list committee duty
*Note*: The only change to `prepare_execution_payload` is to call
`get_inclusion_list_store()` and `get_inclusion_list_transactions()` to set the
Copy link
Member

Choose a reason for hiding this comment

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

We don't usually include the () to indicate a function in the specs. Personally, I do the same though 😢

Suggested change
`get_inclusion_list_store()` and `get_inclusion_list_transactions()` to set the
`get_inclusion_list_store` and `get_inclusion_list_transactions` to set the

create and broadcast the `signed_inclusion_list` to the global `inclusion_list`
subnet by `INCLUSION_LIST_SUBMISSION_DEADLINE` seconds into the slot after
processing the block for the current slot and confirming it as the head. If no
block is received by `INCLUSION_LIST_SUBMISSION_DEADLINE - 1` seconds into the
Copy link
Member

Choose a reason for hiding this comment

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

I think there might need to be a config variable for this too. Let's deal with this later though.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think I added it at L54. Do you mean preset?

Copy link
Contributor

@mkalinin mkalinin left a comment

Choose a reason for hiding this comment

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

Left a few comments, a general question: what if we introduce inclusion_list.md and move InclusionListStore and on_inclusion_list to that new document? This will make FC spec cleaner. cc @jtraglia

#### New `notify_inclusion_lists`

```python
def notify_inclusion_lists(
Copy link
Contributor

Choose a reason for hiding this comment

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

A slight preference on the naming: is_inclusion_list_satisfied or something like that. Similarly to is_block_hash_valid and is_valid_versioned_hashes

Comment on lines +189 to +192
key = (inclusion_list.slot, inclusion_list.inclusion_list_committee_root)

inclusion_lists = store.inclusion_lists.setdefault(key, set())
equivocators = store.equivocators.setdefault(key, set())
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe we should just remove setdefault stuff. This is because the store must already be initialized outside of this function call, thus setdefault becomes redundant

continue

if stored_inclusion_list != inclusion_list:
equivocators.add(inclusion_list.validator_index)
Copy link
Contributor

Choose a reason for hiding this comment

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

We should stop processing equivocations at some point in time. Potentially, at the beginning of the next slot. I believe this check can be introduced in a separate PR

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes I still remember this discussion on Discord. As it would change both the EIP and the CL spec, I think we need more eyes on this. I guess we can revisit this after we're in right momentum, i.e., SFIing EIP-7805. For now, I think we could focus on improvements and tests. But I'll keep this in mind.

### New `store_inclusion_list`

```python
def store_inclusion_list(
Copy link
Contributor

Choose a reason for hiding this comment

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

This function processes the list, but not necessarily stores it. Slight preference in favour of process_inclusion_list name


```python
def get_sync_committee_message(
state: BeaconState,
block_root: Root,
validator_index: ValidatorIndex,
privkey: int,
store: Store,
store: Store, # [New in EIP7805]
Copy link
Contributor

Choose a reason for hiding this comment

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

A better way to spec this out would be stating which block_root should passed to the get_sync_committee_message call without modification of the function

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds great!

Some validators are selected to submit signed inclusion list. Validators should
call `get_inclusion_committee_assignment` at the beginning of an epoch to be
prepared to submit their inclusion list during the next epoch.
*Note*: The proposer should call `prepare_execution_payload` at
Copy link
Contributor

Choose a reason for hiding this comment

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

It doesn’t sound right, a proposer may call prepare_execution_payload at 11.5s into a slot or even later in the case of re-org. I think the spec should say that proposer must not start the build process before the PROPOSER_INCLUSION_LIST_CUT_OFF and that a proposer must use a snapshot of ILs made at PROPOSER_INCLUSION_LIST_CUT_OFF in the prepare_execution_payload call

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it's up to proposers. They still have the freedom to start payload building process before PROPOSER_INCLUSION_LIST_CUT_OFF and we don't want to take it away. But you're right, this is misleading. A proposer can choose either start payload building earlier and update it with ILs after the deadline, or start payload building after the deadline. Let me change it.

Copy link
Contributor

Choose a reason for hiding this comment

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

Is this safe for a proposer to start building earlier? It can lead to the situation where proposer’s ILs are more relaxed because they started to build earlier and haven’t received some ILs yet. I think proposer "should not” start the build process before the cut off — this would probably be the right wording

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Isn't it safe as long as a proposer update the building process after the deadline? What I meant is a proposer can start payload building earlier AND update it with ILs after the deadline.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, I see what you mean but in this case proposer will have to ensure that no new IL arrived after it has started to build earlier. Otherwise, that early built payload can’t be safely used. I agree, this should be up to proposer’s implementation. So, the ultimate requirement is: built payload should satisfy IL constraints observed by a proposer at the cut off

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
eip7805 FOCIL
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants