ethereum.forks.osaka.requestsethereum.forks.bpo1.requests
EIP-7685 generalizes how the execution layer communicates validator actions
to the consensus layer. Rather than adding a dedicated header field for each
new action type (as EIP-4895 did for withdrawals), the execution header
commits to a single requests_hash that aggregates an ordered list of
typed requests.
Each request is a type byte (see DEPOSIT_REQUEST_TYPE,
WITHDRAWAL_REQUEST_TYPE, and CONSOLIDATION_REQUEST_TYPE)
followed by an opaque payload. Deposit requests are discovered by scanning
transaction receipts for logs emitted by the deposit contract; withdrawal
and consolidation requests are produced by the corresponding system
contracts during block processing.
See parse_deposit_requests for how deposit logs become request data,
compute_requests_hash for how the list is hashed for inclusion in the
header, and process_general_purpose_requests for how the requests are
processed.
DEPOSIT_CONTRACT_ADDRESS¶
Mainnet address of the beacon chain deposit contract. Scanning block receipts for logs emitted by this address is how the execution layer discovers validator deposits, per EIP-6110.
| 45 | DEPOSIT_CONTRACT_ADDRESS = hex_to_address( |
|---|---|
| 46 | "0x00000000219ab540356cbb839cbe05303d7705fa" |
| 47 | ) |
DEPOSIT_EVENT_SIGNATURE_HASH¶
First log topic of the deposit contract's DepositEvent, equal to the
keccak256 of its Solidity event signature. Logs whose first topic does not
match this are ignored when collecting deposit requests.
| 56 | DEPOSIT_EVENT_SIGNATURE_HASH = hex_to_bytes32( |
|---|---|
| 57 | "0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5" |
| 58 | ) |
DEPOSIT_REQUEST_TYPE¶
Request type byte identifying a deposit request, per EIP-6110.
| 67 | DEPOSIT_REQUEST_TYPE = b"\x00" |
|---|
WITHDRAWAL_REQUEST_TYPE¶
Request type byte identifying an execution-triggered withdrawal request, per EIP-7002.
| 74 | WITHDRAWAL_REQUEST_TYPE = b"\x01" |
|---|
CONSOLIDATION_REQUEST_TYPE¶
Request type byte identifying a consolidation request, per EIP-7251.
| 82 | CONSOLIDATION_REQUEST_TYPE = b"\x02" |
|---|
DEPOSIT_EVENT_LENGTH¶
Total length in bytes of the ABI-encoded DepositEvent data payload. Every
well-formed event has this exact length.
| 90 | DEPOSIT_EVENT_LENGTH = Uint(576) |
|---|
PUBKEY_OFFSET¶
Position within the event payload of the validator public key's length prefix, as emitted by the Solidity ABI encoder.
| 96 | PUBKEY_OFFSET = Uint(160) |
|---|
WITHDRAWAL_CREDENTIALS_OFFSET¶
Position within the event payload of the withdrawal credentials' length prefix.
| 102 | WITHDRAWAL_CREDENTIALS_OFFSET = Uint(256) |
|---|
AMOUNT_OFFSET¶
Position within the event payload of the deposit amount's length prefix.
| 108 | AMOUNT_OFFSET = Uint(320) |
|---|
SIGNATURE_OFFSET¶
Position within the event payload of the deposit signature's length prefix.
| 113 | SIGNATURE_OFFSET = Uint(384) |
|---|
INDEX_OFFSET¶
Position within the event payload of the deposit index's length prefix.
| 118 | INDEX_OFFSET = Uint(512) |
|---|
PUBKEY_SIZE¶
Length of the BLS12-381 public key that identifies the validator receiving the deposit.
| 123 | PUBKEY_SIZE = Uint(48) |
|---|
WITHDRAWAL_CREDENTIALS_SIZE¶
Length of the withdrawal credentials, which determine where the staked ether may eventually be withdrawn.
| 129 | WITHDRAWAL_CREDENTIALS_SIZE = Uint(32) |
|---|
AMOUNT_SIZE¶
Length of the little-endian Gwei amount being deposited.
| 135 | AMOUNT_SIZE = Uint(8) |
|---|
SIGNATURE_SIZE¶
Length of the BLS12-381 signature over the deposit message.
| 140 | SIGNATURE_SIZE = Uint(96) |
|---|
INDEX_SIZE¶
Length of the monotonically-increasing deposit index assigned by the deposit contract when it emits the event.
| 145 | INDEX_SIZE = Uint(8) |
|---|
extract_deposit_data ¶
Strip the Solidity ABI framing from a DepositEvent payload and return
the concatenated raw fields in the order consumed by the consensus
layer: public key, withdrawal credentials, amount, signature, and
deposit index.
Because each field has a fixed length, every well-formed event has an
identical byte layout. Any deviation indicates a misbehaving or
compromised deposit contract, so this function raises InvalidBlock
rather than silently accepting unexpected data.
def extract_deposit_data(data: Bytes) -> Bytes:
| 153 | """ |
|---|---|
| 154 | Strip the Solidity ABI framing from a `DepositEvent` payload and return |
| 155 | the concatenated raw fields in the order consumed by the consensus |
| 156 | layer: public key, withdrawal credentials, amount, signature, and |
| 157 | deposit index. |
| 158 | |
| 159 | Because each field has a fixed length, every well-formed event has an |
| 160 | identical byte layout. Any deviation indicates a misbehaving or |
| 161 | compromised deposit contract, so this function raises [`InvalidBlock`] |
| 162 | rather than silently accepting unexpected data. |
| 163 | |
| 164 | [`InvalidBlock`]: ref:ethereum.exceptions.InvalidBlock |
| 165 | """ |
| 166 | if ulen(data) != DEPOSIT_EVENT_LENGTH: |
| 167 | raise InvalidBlock("Invalid deposit event data length") |
| 168 | |
| 169 | # Check that all the offsets are in order |
| 170 | pubkey_offset = Uint.from_be_bytes(data[0:32]) |
| 171 | if pubkey_offset != PUBKEY_OFFSET: |
| 172 | raise InvalidBlock("Invalid pubkey offset in deposit log") |
| 173 | |
| 174 | withdrawal_credentials_offset = Uint.from_be_bytes(data[32:64]) |
| 175 | if withdrawal_credentials_offset != WITHDRAWAL_CREDENTIALS_OFFSET: |
| 176 | raise InvalidBlock( |
| 177 | "Invalid withdrawal credentials offset in deposit log" |
| 178 | ) |
| 179 | |
| 180 | amount_offset = Uint.from_be_bytes(data[64:96]) |
| 181 | if amount_offset != AMOUNT_OFFSET: |
| 182 | raise InvalidBlock("Invalid amount offset in deposit log") |
| 183 | |
| 184 | signature_offset = Uint.from_be_bytes(data[96:128]) |
| 185 | if signature_offset != SIGNATURE_OFFSET: |
| 186 | raise InvalidBlock("Invalid signature offset in deposit log") |
| 187 | |
| 188 | index_offset = Uint.from_be_bytes(data[128:160]) |
| 189 | if index_offset != INDEX_OFFSET: |
| 190 | raise InvalidBlock("Invalid index offset in deposit log") |
| 191 | |
| 192 | # Check that all the sizes are in order |
| 193 | pubkey_size = Uint.from_be_bytes( |
| 194 | data[pubkey_offset : pubkey_offset + Uint(32)] |
| 195 | ) |
| 196 | if pubkey_size != PUBKEY_SIZE: |
| 197 | raise InvalidBlock("Invalid pubkey size in deposit log") |
| 198 | |
| 199 | pubkey = data[ |
| 200 | pubkey_offset + Uint(32) : pubkey_offset + Uint(32) + PUBKEY_SIZE |
| 201 | ] |
| 202 | |
| 203 | withdrawal_credentials_size = Uint.from_be_bytes( |
| 204 | data[ |
| 205 | withdrawal_credentials_offset : withdrawal_credentials_offset |
| 206 | + Uint(32) |
| 207 | ], |
| 208 | ) |
| 209 | if withdrawal_credentials_size != WITHDRAWAL_CREDENTIALS_SIZE: |
| 210 | raise InvalidBlock( |
| 211 | "Invalid withdrawal credentials size in deposit log" |
| 212 | ) |
| 213 | |
| 214 | withdrawal_credentials = data[ |
| 215 | withdrawal_credentials_offset |
| 216 | + Uint(32) : withdrawal_credentials_offset |
| 217 | + Uint(32) |
| 218 | + WITHDRAWAL_CREDENTIALS_SIZE |
| 219 | ] |
| 220 | |
| 221 | amount_size = Uint.from_be_bytes( |
| 222 | data[amount_offset : amount_offset + Uint(32)] |
| 223 | ) |
| 224 | if amount_size != AMOUNT_SIZE: |
| 225 | raise InvalidBlock("Invalid amount size in deposit log") |
| 226 | |
| 227 | amount = data[ |
| 228 | amount_offset + Uint(32) : amount_offset + Uint(32) + AMOUNT_SIZE |
| 229 | ] |
| 230 | |
| 231 | signature_size = Uint.from_be_bytes( |
| 232 | data[signature_offset : signature_offset + Uint(32)] |
| 233 | ) |
| 234 | if signature_size != SIGNATURE_SIZE: |
| 235 | raise InvalidBlock("Invalid signature size in deposit log") |
| 236 | |
| 237 | signature = data[ |
| 238 | signature_offset + Uint(32) : signature_offset |
| 239 | + Uint(32) |
| 240 | + SIGNATURE_SIZE |
| 241 | ] |
| 242 | |
| 243 | index_size = Uint.from_be_bytes( |
| 244 | data[index_offset : index_offset + Uint(32)] |
| 245 | ) |
| 246 | if index_size != INDEX_SIZE: |
| 247 | raise InvalidBlock("Invalid index size in deposit log") |
| 248 | |
| 249 | index = data[ |
| 250 | index_offset + Uint(32) : index_offset + Uint(32) + INDEX_SIZE |
| 251 | ] |
| 252 | |
| 253 | return pubkey + withdrawal_credentials + amount + signature + index |
parse_deposit_requests ¶
Walk the receipts produced during block execution, concatenating the raw payload of every valid deposit event into a single byte string.
A log is considered a deposit when it originates from
DEPOSIT_CONTRACT_ADDRESS and its first topic matches
DEPOSIT_EVENT_SIGNATURE_HASH. The returned bytes are the
direct concatenation of the unframed deposit fields, ready to be
prefixed with DEPOSIT_REQUEST_TYPE before being appended to
the block's request list.
def parse_deposit_requests(block_output: BlockOutput) -> Bytes:
| 257 | """ |
|---|---|
| 258 | Walk the receipts produced during block execution, concatenating the |
| 259 | raw payload of every valid deposit event into a single byte string. |
| 260 | |
| 261 | A log is considered a deposit when it originates from |
| 262 | [`DEPOSIT_CONTRACT_ADDRESS`][addr] and its first topic matches |
| 263 | [`DEPOSIT_EVENT_SIGNATURE_HASH`][sig]. The returned bytes are the |
| 264 | direct concatenation of the unframed deposit fields, ready to be |
| 265 | prefixed with [`DEPOSIT_REQUEST_TYPE`][dt] before being appended to |
| 266 | the block's request list. |
| 267 | |
| 268 | [addr]: ref:ethereum.forks.osaka.requests.DEPOSIT_CONTRACT_ADDRESS |
| 269 | [sig]: ref:ethereum.forks.osaka.requests.DEPOSIT_EVENT_SIGNATURE_HASH |
| 270 | [dt]: ref:ethereum.forks.osaka.requests.DEPOSIT_REQUEST_TYPE |
| 268 | [addr]: ref:ethereum.forks.bpo1.requests.DEPOSIT_CONTRACT_ADDRESS |
| 269 | [sig]: ref:ethereum.forks.bpo1.requests.DEPOSIT_EVENT_SIGNATURE_HASH |
| 270 | [dt]: ref:ethereum.forks.bpo1.requests.DEPOSIT_REQUEST_TYPE |
| 271 | """ |
| 272 | deposit_requests: Bytes = b"" |
| 273 | for key in block_output.receipt_keys: |
| 274 | receipt = trie_get(block_output.receipts_trie, key) |
| 275 | assert receipt is not None |
| 276 | decoded_receipt = decode_receipt(receipt) |
| 277 | for log in decoded_receipt.logs: |
| 278 | if log.address == DEPOSIT_CONTRACT_ADDRESS: |
| 279 | if ( |
| 280 | len(log.topics) > 0 |
| 281 | and log.topics[0] == DEPOSIT_EVENT_SIGNATURE_HASH |
| 282 | ): |
| 283 | request = extract_deposit_data(log.data) |
| 284 | deposit_requests += request |
| 285 | |
| 286 | return deposit_requests |
compute_requests_hash ¶
Compute the SHA2-256 commitment over an ordered list of type-prefixed requests, as defined by EIP-7685.
The commitment is the SHA2-256 hash of the concatenation of the
SHA2-256 hashes of each individual request. This is what the
execution header's requests_hash stores, and what the
consensus layer re-derives to validate that both layers observed the
same set of requests.
def compute_requests_hash(requests: List[Bytes]) -> Bytes:
| 290 | """ |
|---|---|
| 291 | Compute the [SHA2-256] commitment over an ordered list of |
| 292 | type-prefixed requests, as defined by [EIP-7685]. |
| 293 | |
| 294 | The commitment is the SHA2-256 hash of the concatenation of the |
| 295 | SHA2-256 hashes of each individual request. This is what the |
| 296 | execution header's [`requests_hash`][rh] stores, and what the |
| 297 | consensus layer re-derives to validate that both layers observed the |
| 298 | same set of requests. |
| 299 | |
| 300 | [EIP-7685]: https://eips.ethereum.org/EIPS/eip-7685 |
| 301 | [SHA2-256]: https://en.wikipedia.org/wiki/SHA-2 |
| 302 | [rh]: ref:ethereum.forks.osaka.blocks.Header.requests_hash |
| 302 | [rh]: ref:ethereum.forks.bpo1.blocks.Header.requests_hash |
| 303 | """ |
| 304 | m = sha256() |
| 305 | for request in requests: |
| 306 | m.update(sha256(request).digest()) |
| 307 | |
| 308 | return m.digest() |