Implementations
The following table lists current implementation efforts. Note that implementations are not complete, and specifications may still change.
Execution client | Implementer | Assistance | Notes |
---|---|---|---|
Besu | open | ||
Erigon | @DarkLord017 | tbd | EPF permissionless |
Geth | @zsfelfoldi | EIP author | |
Nethermind | open | Log filter PR (incompatible design) | |
Nimbus | @vineetpant, @RazorClient | @advaita-saha | EPF proposal |
Reth | @18aaddy, @SkandaBhat | @mattsse | EPF proposal, Log filter PR |
M0 - Simplified on-chain log index
Execution client | Status |
---|---|
Erigon | π§ Branch created and added to Kurtosis |
Nimbus | π§ Branch created and added to Kurtosis |
Reth | π§ Branch created and added to Kurtosis |
Initially, a partial implementation of EIP-7745 is targeted with certain modifications.
Log
type
The Log
type is modified to use SSZ ByteList
instead of ProgressiveByteList
. Rationale: Not all SSZ libraries support EIP-7916 at this time.
MAX_TOPICS_PER_LOG = 4
MAX_LOG_DATA_SIZE = uint64(2**24)
class Log(Container):
address: ExecutionAddress
topics: List[Bytes32, MAX_TOPICS_PER_LOG]
data: ByteList[MAX_LOG_DATA_SIZE]
FilterRow
type
The FilterRow
type is modified to use SSZ ByteList
instead of ProgressiveByteList
. Rationale: Not all SSZ libraries support EIP-7916 at this time.
MAP_WIDTH = uint64(2**24)
MAPS_PER_EPOCH = uint64(2**10)
MAX_BASE_ROW_LENGTH = uint64(2**3)
type FilterRow = ByteList[MAX_BASE_ROW_LENGTH * log2(MAP_WIDTH) // 8 * MAPS_PER_EPOCH]
LogIndex
type
The LogIndex
type is extended with additional fields for debugging, to be filled when updating the log index. Rationale: Makes it easier to detect inconsistencies across implementations.
class LogIndex(Container):
epochs: Vector[LogIndexEpoch, MAX_EPOCH_HISTORY]
next_index: uint64
# Update before incrementing `log_index.next_index`, after
# `block_delimiter_entry` is added to `log_index.epochs[a].log_entries[b]`
latest_block_delimiter_index: uint64 # log_index.next_index
latest_block_delimiter_root: Root # block_delimiter_entry.hash_tree_root()
# Update before incrementing `log_index.next_index`, after
# `log_entry` is added to `log_index.epochs[a].log_entries[b]`
latest_log_entry_index: uint64 # log_index.next_index
latest_log_entry_root: Root # log_entry.hash_tree_root()
# Update before incrementing `log_index.next_index`, after
# `row.append` is called inside `add_log_value()`
latest_value_index: uint32 # log_index.next_index
latest_layer_index: uint32 # layer_index
latest_row_index: uint32 # row_index
latest_column_index: uint32 # column_index
latest_log_value: Bytes32 # log_value, as given to `add_log_value()`, after `sha2()` hash
latest_row_root: Root # row.hash_tree_root()
Persistence
Initially, no persistence to disk is expected, and the log index can be kept in memory. All clients are started before genesis and don't need to perform an initial sync. However, reorgs may occur, and the log index may have to be partially rewinded to switch to a different chain head.
Other EIPs
At this stage, no other Pureth EIPs should be bundled.
Network configuration
The various genesis config objects are extended with a new timestamp.
File | JSON path |
---|---|
genesis.json |
.config.eip7745Time |
chainspec.json |
.params.eip7745TransitionTimestamp |
besu.json |
.config.eip7745Time |
Each execution client typically reads only one of these files.
Activation
The log index is initialized from genesis and tracked inside the execution clients. If no timestamp is configured, or if the block timestamp indicates that the configured timestamp has not yet been reached, the log index is still maintained, but the block header stays unchanged.
For blocks with a timestamp >=
the configured timestamp, a LogIndexSummary
is derived from the corresponding LogIndex
, and the block header's logsBloom
field is replaced with ssz.serialize(log_index_summary)
, matching the same size as before: 2048 bits (256 bytes). The engine API is unchanged.
class LogIndexSummary(Container):
root: Root # 0x00 - log_index.hash_tree_root()
epochs_root: Root # 0x20 - log_index.epochs.hash_tree_root()
epoch_0_filter_maps_root: Root # 0x40 - log_index.epochs[0].filter_maps.hash_tree_root()
latest_block_delimiter_index: uint64 # 0x60 - log_index.latest_block_delimiter_index
latest_block_delimiter_root: Root # 0x68 - log_index.latest_block_delimiter_root
latest_log_entry_index: uint64 # 0x88 - log_index.latest_log_entry_index
latest_log_entry_root: Root # 0x90 - log_index.latest_log_entry_root
latest_value_index: uint32 # 0xb0 - log_index.latest_value_index
latest_layer_index: uint32 # 0xb4 - log_index.latest_layer_index
latest_row_index: uint32 # 0xb8 - log_index.latest_row_index
latest_column_index: uint32 # 0xbc - log_index.latest_column_index
latest_log_value: Bytes32 # 0xc0 - log_index.latest_log_value
latest_row_root: Root # 0xe0 - log_index.latest_row_root
Validation
When processing a block with a timestamp >=
the configured timestamp, its logsBloom
field has to be compared against the expected value. If it does not match, an error should be logged, indicating both the expected and actual values. The field can be decoded using LogIndexSummary.deserialize()
to detect differences more easily.
Block processing may be triggered by all of engine_newPayloadV4
, engine_forkchoiceUpdatedV3
, and forward syncing tasks, and validation is required regardless of the originating trigger.
Testing
Kurtosis can be used to locally simulate a network with the participating clients. It can be set up by following the installation instructions and is configured using a YAML schema.
Save the following config as ~/Downloads/network_params_pureth.yaml
. Note that this uses a fork of ethpandaops/ethereum-package to enable EIP-7745 testing.
participants_matrix:
el:
- el_type: erigon
el_image: ethpandaops/erigon:eip-7745-m0
- el_type: nimbus
el_image: ethpandaops/nimbus-eth1:eip-7745-m0
- el_type: reth
el_image: ethpandaops/reth:eip-7745-m0
cl:
- cl_type: nimbus
cl_image: ethpandaops/nimbus-eth2:eip-7745-m0-minimal
# global_log_level: debug
network_params:
fulu_fork_epoch: 1
eip7745_fork_epoch: 2
preset: minimal
additional_services:
- spamoor
spamoor_params:
spammers:
- scenario: erctx
config:
throughput: 10
To interact with the network, first start Orbstack, and then use the following commands. Docker has a known issue with Erigon, if the network fails to start up, exit Docker and start Orbstack, then try again.
Action | Command |
---|---|
Stop | kurtosis enclave rm pureth --force |
Start | kurtosis run --enclave pureth --image-download always github.com/etan-status/ethereum-package "$(cat ~/Downloads/network_params_pureth.yaml)" |
Ports | kurtosis enclave inspect pureth |
Logs | kurtosis service logs pureth -n 999999 -f |
To test the RPC, use kurtosis enclave inspect pureth
to find the real port to which the rpc
/ ws-rpc
port (8545) is mapped, e.g., if it says ws-rpc: 8545/tcp -> 127.0.0.1:60147
:
curl '127.0.0.1:60147' \
-X POST -H "Content-Type: application/json" --data @- <<EOF | jq .
{
"id": 1,
"jsonrpc": "2.0",
"method": "eth_getBlockByNumber",
"params": ["latest", false]
}
EOF
To trigger a new build, join Ethereum R&D Discord, and use one of the following slash commands in any of the channels. The build takes several minutes, progress can be monitored on ethpandaops/eth-client-docker-image-builder. After the build completes successfully, Kurtosis will automatically use the latest image on subsequent starts.
Execution client | Build command |
---|---|
Erigon | /build client-el docker_tag:eip-7745-m0 client:Erigon repository:DarkLord017/erigon ref:filtermaps |
Nimbus | /build client-el docker_tag:eip-7745-m0 client:NimbusEL repository:vineetpant/nimbus-eth1 ref:eip-7745-log-value-index |
Reth | /build client-el docker_tag:eip-7745-m0 client:Reth repository:18aaddy/reth ref:feat/2d-logs-filter |
Please ensure that the branches are regularly rebased on top of the latest stable client release to keep the diff to the upstream project minimal.