EIP-7745 wire protocol extension
EIP-7745 wire protocol extension
This document specifies the extensions to the Ethereum Wire Protocol required to initialize the log index.
Proposed new messages
GetLogIndexProof (0x12)
[request-id: P, referenceBlockHash: B_32, proofType: P, proofSubset: P]
Require peer to return a LogIndexProof message containing a log index proof that proves the specified subset of the specified type of initialization data from the log index tree belonging to the specified reference block.
Note that all clients are expected to be able to serve log index proofs using either the current finalized block or the previous one as reference block. Also note that the initialization data served by this protocol are split into a limited number of pre-defined subsets so that proofs can be pre-generated for each potential reference block. This, together with the limited size of each individual response, makes it easy to ensure that serving this data will not be an excessive burden on the clients.
LogIndexProof (0x13)
[request-id: P, log_index_proof]
This is the response to GetLogIndexProof, providing the RLP encoded log index proof of the requested partial initialization data. See the log index proof format specification.
Allowed proof types
EpochBoundaryProof (proofType = 0x01)
This proof allows the client to initialize log index rendering at epoch boundaries. It does not prove any filter row data, only index entries, typically of BlockEntry type. Epoch boundary i is defined as the boundary between epochs i and i+1 and allows the client to start rendering the index from epoch i+1. Epoch boundaries 0 <= i < epoch_count can be proven, where epoch_count = next_index // (MAPS_PER_EPOCH * VALUES_PER_MAP) is the number of completed epochs. Note that every proof proves next_index and thereby also epoch_count.
The range of proven boundaries is determined by the proofSubset parameter; boundaries proofSubset * 128 to min((proofSubset+1) * 128, epoch_count) - 1 are proven by the returned log index proof. Except for some corner cases listed below, each boundary i is proven by two adjacent BlockEntry entries: the last one whose map_entry_index < i * MAPS_PER_EPOCH * VALUES_PER_MAP and the first one whose map_entry_index >= i * MAPS_PER_EPOCH * VALUES_PER_MAP. This proves the map_entry_index position of the last block boundary in the previous epoch, which allows the client to start processing the next block, skip the appropriate number of map values and index entries until the epoch boundary, then start rendering the next epoch.
Note that rendering from an epoch boundary is one option to initialize the log index and also makes it possible to generate the index for older epochs later.
Corner cases
The typical scenario described above assumes that there is at least one BlockEntry both before and after the boundary. This assumption can be false in three valid corner cases:
- there is no BlockEntry before the boundary and the block number of the one after the boundary is
firstIndexedBlock. In this case rendering should start from the first epoch. - there is no BlockEntry after the boundary and the block number of the one before the boundary is
referenceBlock - 1. In this case rendering from the boundary is possible. - there are no BlockEntry entries anywhere in the index and
firstIndexedBlock == referenceBlock. In this case rendering should start from the first epoch.
In any other case the proof should be considered invalid.
Note that rendering an epoch as a part of a log index Merkle tree requires the sibling of the rendered epoch’s root node to be known. This is automatically true if a BlockEntry in the rendered epoch (the one after the boundary) is proven. Otherwise it is not always guaranteed, therefore if there is no BlockEntry in the next epoch after a proven boundary then the first index entry of that epoch should be proven, either as a an empty entry, a FalsePositiveLogEntry or a TxEntry.
CurrentMapProof (proofType = 0x02)
This proof allows the client to initialize log index rendering at the reference block. It proves all rows of the current filter map and the index entry at next_index. This dataset is split into a fixed number of subsets (0 <= proofSubset <= 63). The proven map index is calculated as mapIndex = next_index // VALUES_PER_MAP.
Each proof proves 1024 rows between row index proofSubset * 1024 and proofSubset * 1024 + 1023. Additionally, if proofSubset == 0 then the index entry at next_index is also proven as an empty entry. This type of proof always uses the general row encoding format and the FilterRow container type (long_rows is 1024 items long and short_rows is empty). Note that short row encoding has significant benefits when encoding a long section of rows of adjacent maps which is not the case here; in this case the majority of proof data is the sibling proof nodes of the Merkle path leading to each individual row of the same map index.