Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ install_requires =
pycryptodome>=3,<4
coincurve>=18,<19
typing_extensions>=4
eth2spec @ git+https://github.com/ethereum/consensus-specs.git@7402712e4faebcb9cd87370d91acf638ae168949

[options.package_data]
ethereum =
Expand Down
8 changes: 8 additions & 0 deletions src/ethereum/base_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -956,6 +956,14 @@ class Bytes32(FixedBytes):
LENGTH = 32


class Bytes48(FixedBytes):
"""
Byte array of exactly 48 elements.
"""

LENGTH = 48


class Bytes64(FixedBytes):
"""
Byte array of exactly 64 elements.
Expand Down
117 changes: 106 additions & 11 deletions src/ethereum/cancun/fork.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
TX_DATA_COST_PER_ZERO,
AccessListTransaction,
Address,
BlobTransaction,
Block,
Bloom,
FeeMarketTransaction,
Expand All @@ -43,6 +44,7 @@
Receipt,
Root,
Transaction,
VersionedHash,
Withdrawal,
decode_transaction,
encode_transaction,
Expand All @@ -61,7 +63,13 @@
from .utils.hexadecimal import hex_to_address
from .utils.message import prepare_message
from .vm import Message
from .vm.gas import init_code_cost
from .vm.gas import (
calculate_blob_gas_price,
calculate_data_fee,
calculate_excess_blob_gas,
calculate_total_blob_gas,
init_code_cost,
)
from .vm.interpreter import MAX_CODE_SIZE, process_message_call

BASE_FEE_MAX_CHANGE_DENOMINATOR = 8
Expand All @@ -74,6 +82,8 @@
"0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02"
)
SYSTEM_TRANSACTION_GAS = Uint(30000000)
MAX_BLOB_GAS_PER_BLOCK = 786432
VERSIONED_HASH_VERSION_KZG = b"\x01"


@dataclass
Expand Down Expand Up @@ -172,6 +182,9 @@ def state_transition(chain: BlockChain, block: Block) -> None:
Block to apply to `chain`.
"""
parent_header = chain.blocks[-1].header
excess_blob_gas = calculate_excess_blob_gas(parent_header)
ensure(block.header.excess_blob_gas == excess_blob_gas, InvalidBlock)

validate_header(block.header, parent_header)
ensure(block.ommers == (), InvalidBlock)
(
Expand All @@ -181,6 +194,7 @@ def state_transition(chain: BlockChain, block: Block) -> None:
block_logs_bloom,
state,
withdrawals_root,
blob_gas_used,
) = apply_body(
chain.state,
get_last_256_block_hashes(chain),
Expand All @@ -194,13 +208,15 @@ def state_transition(chain: BlockChain, block: Block) -> None:
chain.chain_id,
block.withdrawals,
block.header.parent_beacon_block_root,
excess_blob_gas,
)
ensure(gas_used == block.header.gas_used, InvalidBlock)
ensure(transactions_root == block.header.transactions_root, InvalidBlock)
ensure(state_root(state) == block.header.state_root, InvalidBlock)
ensure(receipt_root == block.header.receipt_root, InvalidBlock)
ensure(block_logs_bloom == block.header.bloom, InvalidBlock)
ensure(withdrawals_root == block.header.withdrawals_root, InvalidBlock)
ensure(blob_gas_used == block.header.blob_gas_used, InvalidBlock)

chain.blocks.append(block)
if len(chain.blocks) > 255:
Expand Down Expand Up @@ -350,7 +366,7 @@ def check_transaction(
ensure(tx.gas <= gas_available, InvalidBlock)
sender_address = recover_sender(chain_id, tx)

if isinstance(tx, FeeMarketTransaction):
if isinstance(tx, (FeeMarketTransaction, BlobTransaction)):
ensure(tx.max_fee_per_gas >= tx.max_priority_fee_per_gas, InvalidBlock)
ensure(tx.max_fee_per_gas >= base_fee_per_gas, InvalidBlock)

Expand Down Expand Up @@ -401,8 +417,10 @@ def make_receipt(

if isinstance(tx, AccessListTransaction):
return b"\x01" + rlp.encode(receipt)
if isinstance(tx, FeeMarketTransaction):
elif isinstance(tx, FeeMarketTransaction):
return b"\x02" + rlp.encode(receipt)
elif isinstance(tx, BlobTransaction):
return b"\x03" + rlp.encode(receipt)
else:
return receipt
Comment thread
SamWilsn marked this conversation as resolved.

Expand All @@ -420,7 +438,8 @@ def apply_body(
chain_id: U64,
withdrawals: Tuple[Withdrawal, ...],
parent_beacon_block_root: Root,
) -> Tuple[Uint, Root, Root, Bloom, State, Root]:
excess_blob_gas: U64,
) -> Tuple[Uint, Root, Root, Bloom, State, Root, Uint]:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we make a dataclass for this? It's getting really unwieldy.

"""
Executes a block.

Expand Down Expand Up @@ -461,6 +480,8 @@ def apply_body(
Withdrawals to be processed in the current block.
parent_beacon_block_root :
The root of the beacon block from the parent block.
excess_blob_gas :
Excess blob gas calculated from the previous block.

Returns
-------
Expand All @@ -476,6 +497,7 @@ def apply_body(
state : `ethereum.fork_types.State`
State after all transactions have been executed.
"""
blob_gas_used = Uint(0)
gas_available = block_gas_limit
transactions_trie: Trie[
Bytes, Optional[Union[Bytes, LegacyTransaction]]
Expand Down Expand Up @@ -507,6 +529,7 @@ def apply_body(
accessed_addresses=set(),
accessed_storage_keys=set(),
parent_evm=None,
blob_versioned_hashes=(),
)

system_tx_env = vm.Environment(
Expand All @@ -523,6 +546,7 @@ def apply_body(
state=state,
chain_id=chain_id,
traces=[],
excess_blob_gas=excess_blob_gas,
)

process_message_call(system_tx_message, system_tx_env)
Expand Down Expand Up @@ -550,6 +574,7 @@ def apply_body(
state=state,
chain_id=chain_id,
traces=[],
excess_blob_gas=excess_blob_gas,
)

gas_used, logs, error = process_transaction(env, tx)
Expand All @@ -566,7 +591,9 @@ def apply_body(
)

block_logs += logs
blob_gas_used += calculate_total_blob_gas(tx)

ensure(blob_gas_used <= MAX_BLOB_GAS_PER_BLOCK, InvalidBlock)
block_gas_used = block_gas_limit - gas_available

block_logs_bloom = logs_bloom(block_logs)
Expand All @@ -586,6 +613,7 @@ def apply_body(
block_logs_bloom,
state,
root(withdrawals_trie),
blob_gas_used,
)


Expand Down Expand Up @@ -623,32 +651,58 @@ def process_transaction(
sender = env.origin
sender_account = get_account(env.state, sender)

if isinstance(tx, FeeMarketTransaction):
gas_fee = tx.gas * tx.max_fee_per_gas
if isinstance(tx, (FeeMarketTransaction, BlobTransaction)):
max_gas_fee = tx.gas * tx.max_fee_per_gas
Comment thread
gurukamath marked this conversation as resolved.
else:
gas_fee = tx.gas * tx.gas_price
max_gas_fee = tx.gas * tx.gas_price

if isinstance(tx, BlobTransaction):
ensure(len(tx.blob_versioned_hashes) > 0, InvalidBlock)
for blob_versioned_hash in tx.blob_versioned_hashes:
ensure(
blob_versioned_hash[0:1] == VERSIONED_HASH_VERSION_KZG,
InvalidBlock,
)

ensure(
tx.max_fee_per_blob_gas >= calculate_blob_gas_price(env),
InvalidBlock,
)

max_gas_fee += calculate_total_blob_gas(tx) * tx.max_fee_per_blob_gas
blob_gas_fee = calculate_data_fee(env, tx)
else:
blob_gas_fee = Uint(0)

ensure(sender_account.nonce == tx.nonce, InvalidBlock)
ensure(sender_account.balance >= gas_fee + tx.value, InvalidBlock)
ensure(sender_account.balance >= max_gas_fee + tx.value, InvalidBlock)
ensure(sender_account.code == bytearray(), InvalidBlock)

effective_gas_fee = tx.gas * env.gas_price

gas = tx.gas - calculate_intrinsic_cost(tx)
increment_nonce(env.state, sender)

sender_balance_after_gas_fee = sender_account.balance - effective_gas_fee
sender_balance_after_gas_fee = (
sender_account.balance - effective_gas_fee - blob_gas_fee
)
set_account_balance(env.state, sender, sender_balance_after_gas_fee)

preaccessed_addresses = set()
preaccessed_storage_keys = set()
preaccessed_addresses.add(env.coinbase)
if isinstance(tx, (AccessListTransaction, FeeMarketTransaction)):
if isinstance(
tx, (AccessListTransaction, FeeMarketTransaction, BlobTransaction)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we make (and backport) a has_access_list function? Not specifically for this pull request, just in general.

@gurukamath gurukamath Feb 5, 2024

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would probably make the maintaining of these properties easier.

Edit: Apparently, mypy is unable to infer the narrowing of types if done inside another function.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to use User-Defined Type Guards.

):
for address, keys in tx.access_list:
preaccessed_addresses.add(address)
for key in keys:
preaccessed_storage_keys.add((address, key))

blob_versioned_hashes: Tuple[VersionedHash, ...] = ()
if isinstance(tx, BlobTransaction):
blob_versioned_hashes = tx.blob_versioned_hashes

message = prepare_message(
sender,
tx.to,
Expand All @@ -658,6 +712,7 @@ def process_transaction(
env,
preaccessed_addresses=frozenset(preaccessed_addresses),
preaccessed_storage_keys=frozenset(preaccessed_storage_keys),
blob_versioned_hashes=blob_versioned_hashes,
)

output = process_message_call(message, env)
Expand Down Expand Up @@ -773,7 +828,9 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint:
create_cost = 0

access_list_cost = 0
if isinstance(tx, (AccessListTransaction, FeeMarketTransaction)):
if isinstance(
tx, (AccessListTransaction, FeeMarketTransaction, BlobTransaction)
):
for _address, keys in tx.access_list:
access_list_cost += TX_ACCESS_LIST_ADDRESS_COST
access_list_cost += len(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST
Expand Down Expand Up @@ -829,6 +886,10 @@ def recover_sender(chain_id: U64, tx: Transaction) -> Address:
public_key = secp256k1_recover(
r, s, tx.y_parity, signing_hash_1559(tx)
)
elif isinstance(tx, BlobTransaction):
public_key = secp256k1_recover(
r, s, tx.y_parity, signing_hash_4844(tx)
)

return Address(keccak256(public_key)[12:32])

Expand Down Expand Up @@ -957,6 +1018,40 @@ def signing_hash_1559(tx: FeeMarketTransaction) -> Hash32:
)


def signing_hash_4844(tx: BlobTransaction) -> Hash32:
"""
Compute the hash of a transaction used in a EIP-4844 signature.

Parameters
----------
tx :
Transaction of interest.

Returns
-------
hash : `ethereum.crypto.hash.Hash32`
Hash of the transaction.
"""
return keccak256(
b"\x03"
+ rlp.encode(
(
tx.chain_id,
tx.nonce,
tx.max_priority_fee_per_gas,
tx.max_fee_per_gas,
tx.gas,
tx.to,
tx.value,
tx.data,
tx.access_list,
tx.max_fee_per_blob_gas,
tx.blob_versioned_hashes,
)
)
)


def compute_header_hash(header: Header) -> Hash32:
"""
Computes the hash of a block header.
Expand Down
35 changes: 34 additions & 1 deletion src/ethereum/cancun/fork_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

Address = Bytes20
Root = Hash32
VersionedHash = Hash32

Bloom = Bytes256

Expand Down Expand Up @@ -103,8 +104,34 @@ class FeeMarketTransaction:
s: U256


@slotted_freezable
@dataclass
class BlobTransaction:
"""
The transaction type added in EIP-4844.
"""

chain_id: U64
nonce: U256
max_priority_fee_per_gas: Uint
max_fee_per_gas: Uint
gas: Uint
to: Union[Bytes0, Address]
value: U256
data: Bytes
access_list: Tuple[Tuple[Address, Tuple[Bytes32, ...]], ...]
max_fee_per_blob_gas: U256
blob_versioned_hashes: Tuple[VersionedHash, ...]
y_parity: U256
r: U256
s: U256


Transaction = Union[
LegacyTransaction, AccessListTransaction, FeeMarketTransaction
LegacyTransaction,
AccessListTransaction,
FeeMarketTransaction,
BlobTransaction,
]


Expand All @@ -118,6 +145,8 @@ def encode_transaction(tx: Transaction) -> Union[LegacyTransaction, Bytes]:
return b"\x01" + rlp.encode(tx)
elif isinstance(tx, FeeMarketTransaction):
return b"\x02" + rlp.encode(tx)
elif isinstance(tx, BlobTransaction):
return b"\x03" + rlp.encode(tx)
else:
raise Exception(f"Unable to encode transaction of type {type(tx)}")

Expand All @@ -131,6 +160,8 @@ def decode_transaction(tx: Union[LegacyTransaction, Bytes]) -> Transaction:
return rlp.decode_to(AccessListTransaction, tx[1:])
elif tx[0] == 2:
return rlp.decode_to(FeeMarketTransaction, tx[1:])
elif tx[0] == 3:
return rlp.decode_to(BlobTransaction, tx[1:])
else:
raise InvalidBlock
else:
Expand Down Expand Up @@ -210,6 +241,8 @@ class Header:
nonce: Bytes8
base_fee_per_gas: Uint
withdrawals_root: Root
blob_gas_used: U64
excess_blob_gas: U64
parent_beacon_block_root: Root


Expand Down
Loading