ethereum.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.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.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()