Skip to content

Commit 99a7765

Browse files
authored
Merge pull request #946 from ethereum/eips/prague/eip-7685
Implement EIP 7685 and 6110
2 parents 6eb1cb7 + 47eb332 commit 99a7765

File tree

12 files changed

+223
-3
lines changed

12 files changed

+223
-3
lines changed

.github/workflows/test.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ jobs:
4141
4242
- name: Run Tox (CPython)
4343
if: "${{ !startsWith(matrix.py, 'pypy') }}"
44-
run: tox -e static,optimized,py3
44+
run: tox -e static,py3
4545

4646
- name: Run Tox (PyPy)
4747
if: "${{ startsWith(matrix.py, 'pypy') }}"

src/ethereum/base_types.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -910,6 +910,17 @@ class Bytes64(FixedBytes):
910910
"""
911911

912912

913+
class Bytes96(FixedBytes):
914+
"""
915+
Byte array of exactly 96 elements.
916+
"""
917+
918+
LENGTH = 96
919+
"""
920+
Number of bytes in each instance of this class.
921+
"""
922+
923+
913924
class Bytes256(FixedBytes):
914925
"""
915926
Byte array of exactly 256 elements.

src/ethereum/prague/blocks.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,29 @@
1111
from dataclasses import dataclass
1212
from typing import Tuple, Union
1313

14+
from ethereum.exceptions import InvalidBlock
15+
16+
from .. import rlp
1417
from ..base_types import (
1518
U64,
1619
U256,
1720
Bytes,
1821
Bytes8,
1922
Bytes32,
23+
Bytes48,
24+
Bytes96,
2025
Uint,
2126
slotted_freezable,
2227
)
2328
from ..crypto.hash import Hash32
2429
from .fork_types import Address, Bloom, Root
2530
from .transactions import LegacyTransaction
31+
from .utils.hexadecimal import hex_to_address
32+
33+
DEPOSIT_CONTRACT_ADDRESS = hex_to_address(
34+
"0x00000000219ab540356cbb839cbe05303d7705fa"
35+
)
36+
DEPOSIT_REQUEST_TYPE = b"\x00"
2637

2738

2839
@slotted_freezable
@@ -65,6 +76,7 @@ class Header:
6576
blob_gas_used: U64
6677
excess_blob_gas: U64
6778
parent_beacon_block_root: Root
79+
requests_root: Root
6880

6981

7082
@slotted_freezable
@@ -78,6 +90,7 @@ class Block:
7890
transactions: Tuple[Union[Bytes, LegacyTransaction], ...]
7991
ommers: Tuple[Header, ...]
8092
withdrawals: Tuple[Withdrawal, ...]
93+
requests: Tuple[Bytes, ...]
8194

8295

8396
@slotted_freezable
@@ -103,3 +116,86 @@ class Receipt:
103116
cumulative_gas_used: Uint
104117
bloom: Bloom
105118
logs: Tuple[Log, ...]
119+
120+
121+
def validate_requests(requests: Tuple[Bytes, ...]) -> None:
122+
"""
123+
Validate a list of requests.
124+
"""
125+
current_request_type = b"\x00"
126+
for request in requests:
127+
request_type = request[:1]
128+
129+
# Ensure that no undefined requests are present.
130+
if request_type != DEPOSIT_REQUEST_TYPE:
131+
raise InvalidBlock("BlockException.INVALID_REQUESTS")
132+
133+
# Ensure that requests are in order.
134+
if request_type < current_request_type:
135+
raise InvalidBlock("BlockException.INVALID_REQUESTS")
136+
current_request_type = request_type
137+
138+
139+
@slotted_freezable
140+
@dataclass
141+
class DepositRequest:
142+
"""
143+
Requests for validator deposits on chain (See EIP-6110).
144+
"""
145+
146+
pubkey: Bytes48
147+
withdrawal_credentials: Bytes32
148+
amount: U64
149+
signature: Bytes96
150+
index: U64
151+
152+
153+
Request = Union[DepositRequest]
154+
155+
156+
def encode_request(req: Request) -> Bytes:
157+
"""
158+
Encode a request.
159+
"""
160+
if isinstance(req, DepositRequest):
161+
return DEPOSIT_REQUEST_TYPE + rlp.encode(req)
162+
else:
163+
raise Exception("Unknown request type")
164+
165+
166+
def parse_deposit_data(data: Bytes) -> Bytes:
167+
"""
168+
Parses Deposit Request from the DepositContract.DepositEvent data.
169+
"""
170+
deposit_request = DepositRequest(
171+
pubkey=Bytes48(data[192:240]),
172+
withdrawal_credentials=Bytes32(data[288:320]),
173+
amount=U64.from_le_bytes(data[352:360]),
174+
signature=Bytes96(data[416:512]),
175+
index=U64.from_le_bytes(data[544:552]),
176+
)
177+
178+
return encode_request(deposit_request)
179+
180+
181+
def validate_deposit_requests(
182+
receipts: Tuple[Receipt, ...], requests: Tuple[Bytes, ...]
183+
) -> None:
184+
"""
185+
Validate a list of deposit requests.
186+
"""
187+
# Retrieve all deposit requests from receipts.
188+
expected_deposit_requests = []
189+
for receipt in receipts:
190+
for log in receipt.logs:
191+
if log.address == DEPOSIT_CONTRACT_ADDRESS:
192+
deposit_request_rlp = parse_deposit_data(log.data)
193+
expected_deposit_requests.append(deposit_request_rlp)
194+
195+
# Retrieve all deposit requests from block body
196+
deposit_requests = [
197+
req for req in requests if req[:1] == DEPOSIT_REQUEST_TYPE
198+
]
199+
200+
if deposit_requests != expected_deposit_requests:
201+
raise InvalidBlock("BlockException.INVALID_REQUESTS")

src/ethereum/prague/fork.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,15 @@
2323
from .. import rlp
2424
from ..base_types import U64, U256, Bytes, Uint
2525
from . import vm
26-
from .blocks import Block, Header, Log, Receipt, Withdrawal
26+
from .blocks import (
27+
Block,
28+
Header,
29+
Log,
30+
Receipt,
31+
Withdrawal,
32+
validate_deposit_requests,
33+
validate_requests,
34+
)
2735
from .bloom import logs_bloom
2836
from .fork_types import Address, Bloom, Root, VersionedHash
2937
from .state import (
@@ -188,6 +196,7 @@ def state_transition(chain: BlockChain, block: Block) -> None:
188196
block.withdrawals,
189197
block.header.parent_beacon_block_root,
190198
excess_blob_gas,
199+
block.requests,
191200
)
192201
if apply_body_output.block_gas_used != block.header.gas_used:
193202
raise InvalidBlock
@@ -482,6 +491,8 @@ class ApplyBodyOutput:
482491
Trie root of all the withdrawals in the block.
483492
blob_gas_used : `ethereum.base_types.Uint`
484493
Total blob gas used in the block.
494+
requests_root : `ethereum.fork_types.Root`
495+
Trie root of all the requests in the block.
485496
"""
486497

487498
block_gas_used: Uint
@@ -491,6 +502,7 @@ class ApplyBodyOutput:
491502
state_root: Root
492503
withdrawals_root: Root
493504
blob_gas_used: Uint
505+
requests_root: Root
494506

495507

496508
def apply_body(
@@ -506,6 +518,7 @@ def apply_body(
506518
withdrawals: Tuple[Withdrawal, ...],
507519
parent_beacon_block_root: Root,
508520
excess_blob_gas: U64,
521+
requests: Tuple[Bytes, ...],
509522
) -> ApplyBodyOutput:
510523
"""
511524
Executes a block.
@@ -546,6 +559,8 @@ def apply_body(
546559
The root of the beacon block from the parent block.
547560
excess_blob_gas :
548561
Excess blob gas calculated from the previous block.
562+
requests :
563+
Requests to be processed in the current block.
549564
550565
Returns
551566
-------
@@ -563,7 +578,11 @@ def apply_body(
563578
withdrawals_trie: Trie[Bytes, Optional[Union[Bytes, Withdrawal]]] = Trie(
564579
secured=False, default=None
565580
)
581+
requests_trie: Trie[Bytes, Optional[Bytes]] = Trie(
582+
secured=False, default=None
583+
)
566584
block_logs: Tuple[Log, ...] = ()
585+
receipts: Tuple[Receipt, ...] = ()
567586

568587
beacon_block_roots_contract_code = get_account(
569588
state, BEACON_ROOTS_ADDRESS
@@ -658,6 +677,8 @@ def apply_body(
658677
rlp.encode(Uint(i)),
659678
receipt,
660679
)
680+
if isinstance(receipt, Receipt):
681+
receipts += (receipt,)
661682

662683
block_logs += logs
663684
blob_gas_used += calculate_total_blob_gas(tx)
@@ -675,6 +696,13 @@ def apply_body(
675696
if account_exists_and_is_empty(state, wd.address):
676697
destroy_account(state, wd.address)
677698

699+
# Requests are to be in ascending order of request type
700+
validate_requests(requests)
701+
702+
validate_deposit_requests(receipts, requests)
703+
for i, request in enumerate(requests):
704+
trie_set(requests_trie, rlp.encode(Uint(i)), request)
705+
678706
return ApplyBodyOutput(
679707
block_gas_used,
680708
root(transactions_trie),
@@ -683,6 +711,7 @@ def apply_body(
683711
state_root(state),
684712
root(withdrawals_trie),
685713
blob_gas_used,
714+
root(requests_trie),
686715
)
687716

688717

src/ethereum_spec_tools/evm_tools/loaders/fixture_loader.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,20 @@ def json_to_withdrawals(self, raw: Any) -> Any:
8989

9090
return self.fork.Withdrawal(*parameters)
9191

92+
def json_to_request(self, raw: Any) -> Any:
93+
"""Converts json requests data to a request object"""
94+
deposit_request = self.fork.DepositRequest(
95+
pubkey=hex_to_bytes(raw.get("pubkey")),
96+
withdrawal_credentials=hex_to_bytes32(
97+
raw.get("withdrawalCredentials")
98+
),
99+
amount=hex_to_u64(raw.get("amount")),
100+
signature=hex_to_bytes(raw.get("signature")),
101+
index=hex_to_u64(raw.get("index")),
102+
)
103+
104+
return self.fork.encode_request(deposit_request)
105+
92106
def json_to_block(
93107
self,
94108
json_block: Any,
@@ -177,4 +191,8 @@ def json_to_header(self, raw: Any) -> Any:
177191
)
178192
parameters.append(parent_beacon_block_root)
179193

194+
if "requestsRoot" in raw:
195+
requests_root = self.fork.hex_to_root(raw.get("requestsRoot"))
196+
parameters.append(requests_root)
197+
180198
return self.fork.Header(*parameters)

src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,26 @@ def Block(self) -> Any:
127127
"""Block class of the fork"""
128128
return self._module("blocks").Block
129129

130+
@property
131+
def validate_requests(self) -> Any:
132+
"""validate_requests function of the fork"""
133+
return self._module("blocks").validate_requests
134+
135+
@property
136+
def validate_deposit_requests(self) -> Any:
137+
"""validate_deposit_requests function of the fork"""
138+
return self._module("blocks").validate_deposit_requests
139+
140+
@property
141+
def DepositRequest(self) -> Any:
142+
"""Deposit request class of the fork"""
143+
return self._module("blocks").DepositRequest
144+
145+
@property
146+
def encode_request(self) -> Any:
147+
"""encode_request function of the fork"""
148+
return self._module("blocks").encode_request
149+
130150
@property
131151
def Bloom(self) -> Any:
132152
"""Bloom class of the fork"""

src/ethereum_spec_tools/evm_tools/t8n/__init__.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import json
77
import os
88
from functools import partial
9-
from typing import Any, TextIO
9+
from typing import Any, TextIO, Tuple
1010

1111
from ethereum import rlp, trace
1212
from ethereum.base_types import U64, U256, Uint
@@ -312,6 +312,9 @@ def apply_body(self) -> None:
312312
receipts_trie = self.fork.Trie(secured=False, default=None)
313313
block_logs = ()
314314
blob_gas_used = Uint(0)
315+
if self.fork.is_after_fork("ethereum.prague"):
316+
requests_trie = self.fork.Trie(secured=False, default=None)
317+
receipts: Tuple[bytes, ...] = ()
315318

316319
if (
317320
self.fork.is_after_fork("ethereum.cancun")
@@ -411,6 +414,8 @@ def apply_body(self) -> None:
411414
rlp.encode(Uint(i)),
412415
receipt,
413416
)
417+
if self.fork.is_after_fork("ethereum.prague"):
418+
receipts += (receipt,)
414419

415420
self.txs.add_receipt(tx, gas_consumed)
416421

@@ -448,6 +453,15 @@ def apply_body(self) -> None:
448453
self.result.blob_gas_used = blob_gas_used
449454
self.result.excess_blob_gas = self.env.excess_blob_gas
450455

456+
if self.fork.is_after_fork("ethereum.prague"):
457+
if not self.fork.validate_requests(self.env.requests):
458+
raise InvalidBlock
459+
460+
self.fork.validate_deposit_requests(receipts, self.env.requests)
461+
462+
for i, request in enumerate(self.env.requests):
463+
self.fork.trie_set(requests_trie, rlp.encode(Uint(i)), request)
464+
451465
self.result.state_root = self.fork.state_root(self.alloc.state)
452466
self.result.tx_root = self.fork.root(transactions_trie)
453467
self.result.receipt_root = self.fork.root(receipts_trie)
@@ -457,6 +471,9 @@ def apply_body(self) -> None:
457471
self.result.receipts = self.txs.successful_receipts
458472
self.result.gas_used = block_gas_used
459473

474+
if self.fork.is_after_fork("ethereum.prague"):
475+
self.result.requests_root = self.fork.root(requests_trie)
476+
460477
def run(self) -> int:
461478
"""Run the transition and provide the relevant outputs"""
462479
try:

0 commit comments

Comments
 (0)