Skip to content
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

Additional changes to fully support Cancun #285

Merged
merged 5 commits into from
Apr 8, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
3 changes: 3 additions & 0 deletions eth_tester/backends/mock/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,9 @@ def make_genesis_block(overrides=None):
"base_fee_per_gas": 1000000000,
"withdrawals": [],
"withdrawals_root": BLANK_ROOT_HASH,
"parent_beacon_block_root": BLANK_ROOT_HASH,
"blob_gas_used": 0,
"excess_blob_gas": 0,
}
if overrides is not None:
genesis_block = merge_genesis_overrides(
Expand Down
7 changes: 7 additions & 0 deletions eth_tester/backends/mock/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ def serialize_full_transaction(transaction, block, transaction_index, is_pending
partial(assoc, key="type", value=extract_transaction_type(transaction)),
)

if int(serialized_transaction["type"], 16) > 0:
serialized_transaction = assoc(
serialized_transaction,
fselmo marked this conversation as resolved.
Show resolved Hide resolved
"y_parity",
transaction["v"],
)

if "gas_price" in transaction:
return serialized_transaction
else:
Expand Down
35 changes: 34 additions & 1 deletion eth_tester/backends/pyevm/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
from eth_abi.exceptions import (
DecodingError,
)
from eth_account._utils.typed_transactions import (
BlobTransaction,
)
from eth_account.hdaccount import (
HDPath,
seed_from_mnemonic,
Expand All @@ -42,6 +45,9 @@
from eth_utils.toolz import (
assoc,
)
from hexbytes import (
HexBytes,
)

from eth_tester.backends.base import (
BaseChainBackend,
Expand Down Expand Up @@ -89,6 +95,9 @@
ParisVM,
ShanghaiVM,
)
from eth.vm.forks.cancun import (
get_total_blob_gas,
)
from eth.vm.forks.shanghai.withdrawals import (
Withdrawal,
)
Expand Down Expand Up @@ -525,6 +534,12 @@ def mine_blocks(self, num_blocks=1, coinbase=ZERO_ADDRESS):
# `prevrandao` value.
mine_kwargs["mix_hash"] = os.urandom(32)

if isinstance(self.chain.get_vm(), CancunVM):
transactions = self.chain.get_block().transactions
mine_kwargs["blob_gas_used"] = sum(
get_total_blob_gas(tx) for tx in transactions
)

block = self.chain.mine_block(**mine_kwargs)
yield block.hash

Expand Down Expand Up @@ -585,6 +600,7 @@ def get_transaction_receipt(self, transaction_hash):
transaction,
transaction_index,
is_pending,
self.chain.get_vm(),
)

def get_fee_history(
Expand Down Expand Up @@ -734,7 +750,24 @@ def _create_type_aware_unsigned_transaction(self, normalized_txn):

def send_raw_transaction(self, raw_transaction):
vm = _get_vm_for_block_number(self.chain, "latest")
evm_transaction = vm.get_transaction_builder().decode(raw_transaction)

if raw_transaction[0] == 3:
fselmo marked this conversation as resolved.
Show resolved Hide resolved
# Blob transactions are handled differently
blob_transaction = BlobTransaction.from_bytes(HexBytes(raw_transaction))

# Blob data is handled in consensus layer, not sent to execution layer.
# Set it to `None` so the `payload()` re-encodes without the blob sidecar.
blob_transaction.blob_data = None

raw_evm_blob_transaction = (
to_bytes(blob_transaction.transaction_type) + blob_transaction.payload()
)
evm_transaction = vm.get_transaction_builder().decode(
raw_evm_blob_transaction
)
else:
evm_transaction = vm.get_transaction_builder().decode(raw_transaction)

self.chain.apply_transaction(evm_transaction)
return evm_transaction.hash

Expand Down
81 changes: 60 additions & 21 deletions eth_tester/backends/pyevm/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
BaseTransaction = None
TypedTransaction = None

from eth_tester.constants import (
GAS_PER_BLOB,
)
from eth_tester.exceptions import (
ValidationError,
)
Expand Down Expand Up @@ -74,13 +77,30 @@ def serialize_block(block, full_transaction, is_pending):
}

if hasattr(block.header, "base_fee_per_gas"):
# london
base_fee = block.header.base_fee_per_gas
block_info.update({"base_fee_per_gas": base_fee})

if hasattr(block.header, "withdrawals_root") and hasattr(block, "withdrawals"):
if hasattr(block.header, "withdrawals_root"):
# shanghai
block_info.update({"withdrawals": serialize_block_withdrawals(block)})
block_info.update({"withdrawals_root": block.header.withdrawals_root})

if all(
fselmo marked this conversation as resolved.
Show resolved Hide resolved
hasattr(block.header, cancun_attr)
for cancun_attr in (
"blob_gas_used",
"excess_blob_gas",
"parent_beacon_block_root",
)
):
# cancun
block_info.update(
{"parent_beacon_block_root": block.header.parent_beacon_block_root}
)
block_info.update({"blob_gas_used": block.header.blob_gas_used})
block_info.update({"excess_blob_gas": block.header.excess_blob_gas})

return block_info


Expand All @@ -103,13 +123,8 @@ def serialize_transaction(block, transaction, transaction_index, is_pending):
"value": transaction.value,
"gas": transaction.gas,
"data": transaction.data,
"r": transaction.r,
"s": transaction.s,
"v": transaction.v
if _field_in_transaction(transaction, "v")
else transaction.y_parity,
}
if _field_in_transaction(transaction, "gas_price"):
if int(txn_type, 16) in (0, 1):
fselmo marked this conversation as resolved.
Show resolved Hide resolved
type_specific_params = {"gas_price": transaction.gas_price}

if _field_in_transaction(transaction, "access_list"):
Expand All @@ -121,10 +136,7 @@ def serialize_transaction(block, transaction, transaction_index, is_pending):
"access_list": transaction.access_list or (),
},
)
elif any(
_field_in_transaction(transaction, _)
for _ in ("max_fee_per_gas" and "max_priority_fee_per_gas")
):
elif int(txn_type, 16) >= 2:
# dynamic fee transaction
type_specific_params = {
"chain_id": transaction.chain_id,
Expand All @@ -140,10 +152,32 @@ def serialize_transaction(block, transaction, transaction_index, is_pending):
else _calculate_effective_gas_price(transaction, block, txn_type)
),
}
if int(txn_type, 16) == 3:
# blob transaction
type_specific_params = merge(
type_specific_params,
{
"max_fee_per_blob_gas": transaction.max_fee_per_blob_gas,
"blob_versioned_hashes": transaction.blob_versioned_hashes,
},
)
else:
raise ValidationError("Invariant: code path should be unreachable")

return merge(common_transaction_params, type_specific_params)
# the signature fields are commonly the last fields in a node's JSON-RPC response
signed_tx_params = {
"v": (
transaction.v
if _field_in_transaction(transaction, "v")
else transaction.y_parity
),
"s": transaction.s,
"r": transaction.r,
}
if txn_type != "0x0":
signed_tx_params["y_parity"] = signed_tx_params["v"]

return merge(common_transaction_params, type_specific_params, signed_tx_params)


def _field_in_transaction(transaction, field):
Expand All @@ -163,7 +197,7 @@ def _field_in_transaction(transaction, field):


def serialize_transaction_receipt(
block, receipts, transaction, transaction_index, is_pending
block, receipts, transaction, transaction_index, is_pending, vm
):
receipt = receipts[transaction_index]
_txn_type = _extract_transaction_type(transaction)
Expand All @@ -182,7 +216,7 @@ def serialize_transaction_receipt(
else:
origin_gas = receipts[transaction_index - 1].gas_used

return {
receipt_fields = {
"block_hash": None if is_pending else block.hash,
"block_number": None if is_pending else block.number,
"contract_address": contract_addr,
Expand All @@ -206,6 +240,14 @@ def serialize_transaction_receipt(
"type": _txn_type,
}

if int(_txn_type, 16) == 3:
# blob transaction
blob_gas_used = GAS_PER_BLOB * len(transaction.blob_versioned_hashes)
receipt_fields["blob_gas_used"] = blob_gas_used
receipt_fields["blob_gas_price"] = vm.state.blob_base_fee * blob_gas_used

return receipt_fields


def serialize_log(block, transaction, transaction_index, log, log_index, is_pending):
return {
Expand All @@ -223,12 +265,9 @@ def serialize_log(block, transaction, transaction_index, log, log_index, is_pend

def _extract_transaction_type(transaction):
if isinstance(transaction, TypedTransaction):
try:
transaction.gas_price # noqa: 201
return "0x1"
except AttributeError:
return "0x2"
# legacy transactions being '0x0' taken from current geth version v1.10.10
return hex(transaction.type_id)

# legacy transactions are now considered "0x0" type
return "0x0"


Expand All @@ -238,7 +277,7 @@ def _calculate_effective_gas_price(transaction, block, transaction_type):
transaction.max_fee_per_gas,
transaction.max_priority_fee_per_gas + block.header.base_fee_per_gas,
)
if transaction_type == "0x2"
if int(transaction_type, 16) >= 2
else transaction.gas_price
)

Expand Down
5 changes: 5 additions & 0 deletions eth_tester/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,9 @@
#
# EIP CONSTANTS
#
# EIP-1559
DYNAMIC_FEE_TRANSACTION_PARAMS = ("max_fee_per_gas", "max_priority_fee_per_gas")

# EIP-4844
BLOB_TRANSACTION_PARAMS = ("max_fee_per_blob_gas", "blob_versioned_hashes")
GAS_PER_BLOB = 2**17
4 changes: 2 additions & 2 deletions eth_tester/normalization/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
encode_hex,
is_hex,
to_dict,
to_tuple,
to_list,
fselmo marked this conversation as resolved.
Show resolved Hide resolved
)
from eth_utils.toolz import (
curry,
Expand All @@ -22,7 +22,7 @@ def normalize_dict(value, normalizers):


@curry
@to_tuple
@to_list
def normalize_array(value, normalizer):
"""
Just `map` but it's nice to have it return a consistent type
Expand Down
13 changes: 13 additions & 0 deletions eth_tester/normalization/outbound.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ def _normalize_outbound_access_list(access_list):

TRANSACTION_NORMALIZERS = {
"type": identity,
"blob_versioned_hashes": partial(
normalize_array,
normalizer=partial(
normalize_if, conditional_fn=is_bytes, normalizer=encode_hex
),
),
"chain_id": identity,
"hash": encode_hex,
"nonce": identity,
Expand All @@ -68,13 +74,15 @@ def _normalize_outbound_access_list(access_list):
"value": identity,
"gas": identity,
"gas_price": identity,
"max_fee_per_blob_gas": identity,
"max_fee_per_gas": identity,
"max_priority_fee_per_gas": identity,
"data": encode_hex,
"access_list": _normalize_outbound_access_list,
"r": identity,
"s": identity,
"v": identity,
"y_parity": identity,
}
normalize_transaction = partial(normalize_dict, normalizers=TRANSACTION_NORMALIZERS)

Expand Down Expand Up @@ -143,6 +151,9 @@ def _remove_fork_specific_fields_if_none(block):
"uncles": partial(normalize_array, normalizer=encode_hex),
"withdrawals": partial(normalize_array, normalizer=normalize_withdrawal),
"withdrawals_root": encode_hex,
"parent_beacon_block_root": encode_hex,
"blob_gas_used": identity,
"excess_blob_gas": identity,
}
normalize_block = compose(
partial(normalize_dict, normalizers=BLOCK_NORMALIZERS),
Expand Down Expand Up @@ -205,6 +216,8 @@ def _normalize_contract_address(receipt):
"to": to_empty_or_checksum_address,
"type": identity,
"base_fee_per_gas": identity,
"blob_gas_used": identity,
"blob_gas_price": identity,
}
normalize_receipt = compose(
partial(normalize_dict, normalizers=RECEIPT_NORMALIZERS),
Expand Down
2 changes: 1 addition & 1 deletion eth_tester/validation/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ def validate_transaction_type(value):
f"Transaction type string must be hex string. Got: {value}"
)
type_int = int(value, 16) if is_hexstr(value) else int(value)
if type_int not in (0, 1, 2):
if type_int not in (0, 1, 2, 3):
raise ValidationError(f"Transaction type '{value}' not recognized.")


Expand Down
8 changes: 8 additions & 0 deletions eth_tester/validation/inbound.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
)

from eth_tester.constants import (
BLOB_TRANSACTION_PARAMS,
BLOCK_NUMBER_META_VALUES,
)
from eth_tester.exceptions import (
Expand Down Expand Up @@ -237,6 +238,13 @@ def validate_transaction(value, txn_internal_type):
)
)
if unknown_keys:
if any(k in value for k in BLOB_TRANSACTION_PARAMS):
raise ValidationError(
"Transaction contains blob-specific parameters. Blob transactions are "
"only supported via `eth_sendRawTransaction`, rlp encoding the blob "
"sidecar data along with the transaction as per the EIP-4844 "
"`PooledTransaction` model."
)
raise ValidationError(
"Only the keys '{}' are allowed. Got extra keys: '{}'".format(
"/".join(
Expand Down
Loading