Skip to content

Implement EIP-7002 #950

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 114 additions & 45 deletions src/ethereum/cancun/fork.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,11 @@
calculate_total_blob_gas,
init_code_cost,
)
from .vm.interpreter import MAX_CODE_SIZE, process_message_call
from .vm.interpreter import (
MAX_CODE_SIZE,
MessageCallOutput,
process_message_call,
)

BASE_FEE_MAX_CHANGE_DENOMINATOR = 8
ELASTICITY_MULTIPLIER = 2
Expand Down Expand Up @@ -507,6 +511,102 @@ class ApplyBodyOutput:
blob_gas_used: Uint


def process_system_transaction(
target_address: Address,
data: Bytes,
block_hashes: List[Hash32],
coinbase: Address,
block_number: Uint,
base_fee_per_gas: Uint,
block_gas_limit: Uint,
block_time: U256,
prev_randao: Bytes32,
state: State,
chain_id: U64,
excess_blob_gas: U64,
) -> MessageCallOutput:
"""
Process a system transaction.

Parameters
----------
target_address :
Address of the contract to call.
data :
Data to pass to the contract.
block_hashes :
List of hashes of the previous blocks.
coinbase :
Address of the block's coinbase.
block_number :
Block number.
base_fee_per_gas :
Base fee per gas.
block_gas_limit :
Gas limit of the block.
block_time :
Time the block was produced.
prev_randao :
Previous randao value.
state :
Current state.
chain_id :
ID of the chain.
excess_blob_gas :
Excess blob gas.

Returns
-------
system_tx_output : `MessageCallOutput`
Output of processing the system transaction.
"""
system_contract_code = get_account(state, target_address).code

system_tx_message = Message(
caller=SYSTEM_ADDRESS,
target=target_address,
gas=SYSTEM_TRANSACTION_GAS,
value=U256(0),
data=data,
code=system_contract_code,
depth=Uint(0),
current_target=target_address,
code_address=target_address,
should_transfer_value=False,
is_static=False,
accessed_addresses=set(),
accessed_storage_keys=set(),
parent_evm=None,
)

system_tx_env = vm.Environment(
caller=SYSTEM_ADDRESS,
origin=SYSTEM_ADDRESS,
block_hashes=block_hashes,
coinbase=coinbase,
number=block_number,
gas_limit=block_gas_limit,
base_fee_per_gas=base_fee_per_gas,
gas_price=base_fee_per_gas,
time=block_time,
prev_randao=prev_randao,
state=state,
chain_id=chain_id,
traces=[],
excess_blob_gas=excess_blob_gas,
blob_versioned_hashes=(),
transient_storage=TransientStorage(),
)

system_tx_output = process_message_call(system_tx_message, system_tx_env)

destroy_touched_empty_accounts(
system_tx_env.state, system_tx_output.touched_accounts
)

return system_tx_output


def apply_body(
state: State,
block_hashes: List[Hash32],
Expand Down Expand Up @@ -583,50 +683,19 @@ def apply_body(
)
block_logs: Tuple[Log, ...] = ()

beacon_block_roots_contract_code = get_account(
state, BEACON_ROOTS_ADDRESS
).code

system_tx_message = Message(
caller=SYSTEM_ADDRESS,
target=BEACON_ROOTS_ADDRESS,
gas=SYSTEM_TRANSACTION_GAS,
value=U256(0),
data=parent_beacon_block_root,
code=beacon_block_roots_contract_code,
depth=Uint(0),
current_target=BEACON_ROOTS_ADDRESS,
code_address=BEACON_ROOTS_ADDRESS,
should_transfer_value=False,
is_static=False,
accessed_addresses=set(),
accessed_storage_keys=set(),
parent_evm=None,
)

system_tx_env = vm.Environment(
caller=SYSTEM_ADDRESS,
origin=SYSTEM_ADDRESS,
block_hashes=block_hashes,
coinbase=coinbase,
number=block_number,
gas_limit=block_gas_limit,
base_fee_per_gas=base_fee_per_gas,
gas_price=base_fee_per_gas,
time=block_time,
prev_randao=prev_randao,
state=state,
chain_id=chain_id,
traces=[],
excess_blob_gas=excess_blob_gas,
blob_versioned_hashes=(),
transient_storage=TransientStorage(),
)

system_tx_output = process_message_call(system_tx_message, system_tx_env)

destroy_touched_empty_accounts(
system_tx_env.state, system_tx_output.touched_accounts
process_system_transaction(
BEACON_ROOTS_ADDRESS,
parent_beacon_block_root,
block_hashes,
coinbase,
block_number,
base_fee_per_gas,
block_gas_limit,
block_time,
prev_randao,
state,
chain_id,
excess_blob_gas,
)

for i, tx in enumerate(map(decode_transaction, transactions)):
Expand Down
125 changes: 90 additions & 35 deletions src/ethereum/prague/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@
from dataclasses import dataclass
from typing import Tuple, Union

from ethereum.exceptions import InvalidBlock

from .. import rlp
from ..base_types import (
U64,
Expand All @@ -27,13 +25,21 @@
)
from ..crypto.hash import Hash32
from .fork_types import Address, Bloom, Root
from .transactions import LegacyTransaction
from .transactions import (
AccessListTransaction,
BlobTransaction,
FeeMarketTransaction,
LegacyTransaction,
Transaction,
)
from .utils.hexadecimal import hex_to_address

DEPOSIT_CONTRACT_ADDRESS = hex_to_address(
"0x00000000219ab540356cbb839cbe05303d7705fa"
)
DEPOSIT_REQUEST_TYPE = b"\x00"
WITHDRAWAL_REQUEST_TYPE = b"\x01"
WITHDRAWAL_REQUEST_LENGTH = 76


@slotted_freezable
Expand Down Expand Up @@ -118,22 +124,29 @@ class Receipt:
logs: Tuple[Log, ...]


def validate_requests(requests: Tuple[Bytes, ...]) -> None:
def encode_receipt(tx: Transaction, receipt: Receipt) -> Union[Bytes, Receipt]:
"""
Validate a list of requests.
Encodes a receipt.
"""
current_request_type = b"\x00"
for request in requests:
request_type = request[:1]
if isinstance(tx, AccessListTransaction):
return b"\x01" + rlp.encode(receipt)
elif isinstance(tx, FeeMarketTransaction):
return b"\x02" + rlp.encode(receipt)
elif isinstance(tx, BlobTransaction):
return b"\x03" + rlp.encode(receipt)
else:
return receipt

# Ensure that no undefined requests are present.
if request_type != DEPOSIT_REQUEST_TYPE:
raise InvalidBlock("BlockException.INVALID_REQUESTS")

# Ensure that requests are in order.
if request_type < current_request_type:
raise InvalidBlock("BlockException.INVALID_REQUESTS")
current_request_type = request_type
def decode_receipt(receipt: Union[Bytes, Receipt]) -> Receipt:
"""
Decodes a receipt.
"""
if isinstance(receipt, Bytes):
assert receipt[0] in (b"\x01", b"\x02", b"\x03")
return rlp.decode_to(Receipt, receipt[1:])
else:
return receipt


@slotted_freezable
Expand All @@ -150,7 +163,19 @@ class DepositRequest:
index: U64


Request = Union[DepositRequest]
@slotted_freezable
@dataclass
class WithdrawalRequest:
"""
Requests for execution layer withdrawals (See EIP-7002).
"""

source_address: Address
validator_pubkey: Bytes48
amount: U64


Request = Union[DepositRequest, WithdrawalRequest]


def encode_request(req: Request) -> Bytes:
Expand All @@ -159,11 +184,13 @@ def encode_request(req: Request) -> Bytes:
"""
if isinstance(req, DepositRequest):
return DEPOSIT_REQUEST_TYPE + rlp.encode(req)
elif isinstance(req, WithdrawalRequest):
return WITHDRAWAL_REQUEST_TYPE + rlp.encode(req)
else:
raise Exception("Unknown request type")


def parse_deposit_data(data: Bytes) -> Bytes:
def parse_deposit_data(data: Bytes) -> DepositRequest:
"""
Parses Deposit Request from the DepositContract.DepositEvent data.
"""
Expand All @@ -175,27 +202,55 @@ def parse_deposit_data(data: Bytes) -> Bytes:
index=U64.from_le_bytes(data[544:552]),
)

return encode_request(deposit_request)
return deposit_request


def validate_deposit_requests(
receipts: Tuple[Receipt, ...], requests: Tuple[Bytes, ...]
) -> None:
def parse_deposit_requests_from_receipt(
receipt: Union[Bytes, Receipt],
) -> Tuple[Bytes, ...]:
"""
Validate a list of deposit requests.
Parse deposit requests from a receipt.
"""
# Retrieve all deposit requests from receipts.
expected_deposit_requests = []
for receipt in receipts:
for log in receipt.logs:
if log.address == DEPOSIT_CONTRACT_ADDRESS:
deposit_request_rlp = parse_deposit_data(log.data)
expected_deposit_requests.append(deposit_request_rlp)
deposit_requests: Tuple[Bytes, ...] = ()
decoded_receipt = decode_receipt(receipt)
for log in decoded_receipt.logs:
if log.address == DEPOSIT_CONTRACT_ADDRESS:
deposit_request = parse_deposit_data(log.data)
deposit_requests += (encode_request(deposit_request),)

return deposit_requests


def parse_withdrawal_data(data: Bytes) -> WithdrawalRequest:
"""
Parses Withdrawal Request from the data.
"""
assert len(data) == WITHDRAWAL_REQUEST_LENGTH
req = WithdrawalRequest(
source_address=Address(data[:20]),
validator_pubkey=Bytes48(data[20:68]),
amount=U64.from_be_bytes(data[68:76]),
)

return req


def parse_withdrawal_requests_from_system_tx(
evm_call_output: Bytes,
) -> Tuple[Bytes, ...]:
"""
Parse withdrawal requests from the system transaction output.
"""
count_withdrawal_requests = (
len(evm_call_output) // WITHDRAWAL_REQUEST_LENGTH
)

# Retrieve all deposit requests from block body
deposit_requests = [
req for req in requests if req[:1] == DEPOSIT_REQUEST_TYPE
]
withdrawal_requests: Tuple[Bytes, ...] = ()
for i in range(count_withdrawal_requests):
start = i * WITHDRAWAL_REQUEST_LENGTH
withdrawal_request = parse_withdrawal_data(
evm_call_output[start : start + WITHDRAWAL_REQUEST_LENGTH]
)
withdrawal_requests += (encode_request(withdrawal_request),)

if deposit_requests != expected_deposit_requests:
raise InvalidBlock("BlockException.INVALID_REQUESTS")
return withdrawal_requests
Loading
Loading