Skip to content

Commit

Permalink
London mocked backend logic and some more refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
fselmo committed Oct 14, 2021
1 parent 09feb17 commit 264d699
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 66 deletions.
124 changes: 74 additions & 50 deletions eth_tester/backends/mock/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
ZERO_32BYTES = b'\x00' * 32
ZERO_8BYTES = b'\x00' * 8
ZERO_ADDRESS = b'\x00' * 20
BLOCK_ELASTICITY_MULTIPLIER = 2
BASE_FEE_MAX_CHANGE_DENOMINATOR = 8


@apply_to_return_value(b'|'.join)
Expand Down Expand Up @@ -89,66 +91,53 @@ def _fill_transaction(transaction, block, transaction_index, is_pending, overrid
if overrides is None:
overrides = {}

if 'nonce' in overrides:
yield 'nonce', overrides['nonce']
else:
yield 'nonce', 0
is_dynamic_fee_transaction = (
'gas_price' not in transaction and any(
_ in transaction for _ in ('max_fee_per_gas', 'max_priority_fee_per_gas')
)
)

if 'hash' in overrides:
yield 'hash', overrides['hash']
else:
# calculate hash after all fields are filled
pass

if 'from' in overrides:
yield 'from', overrides['from']
else:
yield 'from', transaction['from']
# this pattern isn't the nicest to read but it keeps things clean. Here, we yield the overrides
# value if it exists, else either the transaction value if that exists, or a default value
yield 'nonce', overrides.get('nonce', 0)
yield 'from', overrides.get('from', transaction.get('from'))
yield 'to', overrides.get('to', transaction.get('to', b''))
yield 'data', overrides.get('data', transaction.get('data', b''))
yield 'value', overrides.get('value', transaction.get('value', 0))
yield 'nonce', overrides.get('nonce', transaction.get('nonce', 0))
yield 'gas', overrides.get('gas', transaction.get('gas'))

if 'gas' in overrides:
yield 'gas', overrides['gas']
if 'gas_price' in transaction or 'gas_price' in overrides:
# gas price is 1 gwei in order to be at least the base fee at the (London) genesis block
yield 'gas_price', overrides.get('gas_price', transaction.get('gas_price', 1000000000))
else:
yield 'gas', transaction['gas']
# if no gas_price specified, default to dynamic fee txn parameters
is_dynamic_fee_transaction = True

if 'gas_price' in overrides:
yield 'gas_price', overrides['gas_price']
else:
yield 'gas_price', transaction.get('gas_price', 1) # TODO: make configurable
if is_dynamic_fee_transaction:
yield 'max_fee_per_gas', overrides.get(
'max_fee_per_gas', transaction.get('max_fee_per_gas', 1000000000)
)
yield 'max_priority_fee_per_gas', overrides.get(
'max_priority_fee_per_gas', transaction.get('max_priority_fee_per_gas', 1000000000)
)

if 'to' in overrides:
yield 'to', overrides['to']
if is_dynamic_fee_transaction or 'access_list' in transaction:
# if is typed transaction (dynamic fee or access list transaction)
yield 'chain_id', overrides.get('chain_id', transaction.get('chain_id', 131277322940537))
yield 'access_list', overrides.get('access_list', transaction.get('access_list', []))
yield 'y_parity', overrides.get('y_parity', transaction.get('y_parity', 0))
else:
yield 'to', transaction.get('to', b'')
yield 'v', overrides.get('v', transaction.get('v', 27))

if 'data' in overrides:
yield 'data', overrides['data']
else:
yield 'data', transaction.get('data', b'')

if 'value' in overrides:
yield 'value', overrides['value']
else:
yield 'value', transaction.get('value', 0)

if 'nonce' in overrides:
yield 'nonce', overrides['nonce']
else:
yield 'nonce', transaction.get('nonce', 0)

if 'v' in overrides:
yield 'v', overrides['v']
else:
yield 'v', transaction.get('v', 27)

if 'r' in overrides:
yield 'r', overrides['r']
else:
yield 'r', transaction.get('r', 12345)

if 's' in overrides:
yield 's', overrides['s']
else:
yield 's', transaction.get('s', 67890)
yield 'r', overrides.get('r', transaction.get('r', 12345))
yield 's', overrides.get('s', transaction.get('s', 67890))


@to_dict
Expand Down Expand Up @@ -200,7 +189,7 @@ def make_log(transaction, block, transaction_index, log_index, overrides=None):


@to_dict
def make_receipt(transaction, block, transaction_index, overrides=None):
def make_receipt(transaction, block, _transaction_index, overrides=None):
if overrides is None:
overrides = {}

Expand Down Expand Up @@ -253,12 +242,12 @@ def make_genesis_block(overrides=None):
"total_difficulty": 131072,
"size": 0,
"extra_data": ZERO_32BYTES,
"gas_limit": 3141592,
"gas_limit": 30029122, # gas limit at London fork block 12965000 on mainnet
"gas_used": 0,
"timestamp": int(time.time()),
"transactions": [],
"uncles": [],
"base_fee_per_gas": 1000000000,
"base_fee_per_gas": 1000000000, # base fee at London fork block 12965000 on mainnet
}
if overrides is not None:
genesis_block = merge_genesis_overrides(defaults=default_genesis_block,
Expand Down Expand Up @@ -369,3 +358,38 @@ def make_block_from_parent(parent_block, overrides=None):
yield 'uncles', overrides['uncles']
else:
yield 'uncles', []

if 'base_fee_per_gas' in overrides:
yield 'base_fee_per_gas', overrides['base_fee_per_gas']
else:
yield 'base_fee_per_gas', _calculate_expected_base_fee_per_gas(parent_block)


def _calculate_expected_base_fee_per_gas(parent_block) -> int:
"""py-evm logic for calculating the base fee from parent header"""
parent_base_fee_per_gas = parent_block['base_fee_per_gas']

parent_gas_target = parent_block['gas_limit'] // BLOCK_ELASTICITY_MULTIPLIER
parent_gas_used = parent_block['gas_used']

if parent_gas_used == parent_gas_target:
return parent_base_fee_per_gas

elif parent_gas_used > parent_gas_target:
gas_used_delta = parent_gas_used - parent_gas_target
overburnt_wei = parent_base_fee_per_gas * gas_used_delta
base_fee_per_gas_delta = max(
overburnt_wei // parent_gas_target // BASE_FEE_MAX_CHANGE_DENOMINATOR,
1,
)
return parent_base_fee_per_gas + base_fee_per_gas_delta

else:
gas_used_delta = parent_gas_target - parent_gas_used
underburnt_wei = parent_base_fee_per_gas * gas_used_delta
base_fee_per_gas_delta = (
underburnt_wei
// parent_gas_target
// BASE_FEE_MAX_CHANGE_DENOMINATOR
)
return max(parent_base_fee_per_gas - base_fee_per_gas_delta, 0)
12 changes: 6 additions & 6 deletions eth_tester/backends/pyevm/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ def generate_genesis_state_for_keys(account_keys, overrides=None):


def get_default_genesis_params(overrides=None):
# commented out params became un-configurable in London
# commented out params became un-configurable in py-evm during London refactor
default_genesis_params = {
# "bloom": 0,
"coinbase": GENESIS_COINBASE,
Expand Down Expand Up @@ -193,7 +193,7 @@ def get_transaction_builder(self):
base_db = get_db_backend()

chain = MainnetTesterNoProofChain.from_genesis(base_db, genesis_params, genesis_state)
chain.chain_id = 1337 # typed transactions need a chain_id
chain.chain_id = 131277322940537 # typed transactions need a chain_id
return account_keys, chain


Expand Down Expand Up @@ -438,7 +438,7 @@ def get_base_fee(self, block_number='latest'):
#
@to_dict
def _normalize_transaction(self, transaction, block_number='latest'):
is_typed_transaction = False
is_dynamic_fee_transaction = False
for key in transaction:
if key == 'from':
continue
Expand All @@ -448,7 +448,7 @@ def _normalize_transaction(self, transaction, block_number='latest'):
if 'data' not in transaction:
yield 'data', b''
if 'gas_price' not in transaction:
is_typed_transaction = True
is_dynamic_fee_transaction = True
if not any(_ in transaction for _ in ('max_fee_per_gas', 'max_priority_fee_per_gas')):
yield 'max_fee_per_gas', 1 * 10**9
yield 'max_priority_fee_per_gas', 1 * 10**9
Expand All @@ -459,10 +459,10 @@ def _normalize_transaction(self, transaction, block_number='latest'):
yield 'value', 0
if 'to' not in transaction:
yield 'to', b''
if is_typed_transaction:
if is_dynamic_fee_transaction or 'access_list' in transaction:
if 'access_list' not in transaction:
yield 'access_list', []
if not transaction.get('chain_id'):
if 'chain_id' not in transaction:
yield 'chain_id', self.chain.chain_id

def _get_normalized_and_unsigned_evm_transaction(self, transaction, block_number='latest'):
Expand Down
19 changes: 14 additions & 5 deletions eth_tester/utils/backend_testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@

def _validate_serialized_block(block):
missing_keys = BLOCK_KEYS.difference(block.keys())
missing_keys = dissoc(missing_keys, 'base_fee_per_gas')
if missing_keys:
error_message = "Serialized block is missing the following keys: {}".format(
"|".join(sorted(missing_keys)),
Expand All @@ -124,13 +123,22 @@ def _send_and_check_transaction(self, eth_tester, test_transaction, _from):
txn = eth_tester.get_transaction_by_hash(txn_hash)
self._check_transactions(transaction, txn)

def _check_transactions(self, expected_transaction, actual_transaction):
@staticmethod
def _check_transactions(expected_transaction, actual_transaction):
assert is_same_address(actual_transaction['from'], expected_transaction['from'])
if 'to' not in expected_transaction or expected_transaction['to'] == '':
assert actual_transaction['to'] == ''
else:
assert is_same_address(actual_transaction['to'], expected_transaction['to'])
# assert actual_transaction['gas_price'] == expected_transaction['gas_price']

if expected_transaction.get('gas_price'):
assert actual_transaction['gas_price'] == expected_transaction['gas_price']
else:
assert actual_transaction['max_fee_per_gas'] == expected_transaction['max_fee_per_gas']
assert (
actual_transaction['max_priority_fee_per_gas'] ==
expected_transaction['max_priority_fee_per_gas']
)
assert actual_transaction['gas'] == expected_transaction['gas']
assert actual_transaction['value'] == expected_transaction['value']

Expand Down Expand Up @@ -330,7 +338,7 @@ def test_send_access_list_transaction(self, eth_tester):
assert accounts, "No accounts available for transaction sending"

access_list_transaction = {
'chain_id': 123456789,
'chain_id': 131277322940537,
'from': accounts[0],
'to': accounts[0],
'value': 1,
Expand Down Expand Up @@ -361,7 +369,7 @@ def test_send_dynamic_fee_transaction(self, eth_tester):
assert accounts, "No accounts available for transaction sending"

dynamic_fee_transaction = {
'chain_id': 123456789,
'chain_id': 131277322940537,
'from': accounts[0],
'to': accounts[0],
'value': 1,
Expand Down Expand Up @@ -596,6 +604,7 @@ def test_get_block_by_hash(self, eth_tester):
block = eth_tester.get_block_by_hash(block_hash)
assert block['number'] == block_number
assert block['hash'] == block_hash
assert block['base_fee_per_gas'] is not None

def test_get_block_by_hash_full_transactions(self, eth_tester):
eth_tester.mine_blocks(2)
Expand Down
10 changes: 8 additions & 2 deletions eth_tester/validation/outbound.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ def validate_signature_v(value):
raise ValidationError("The `v` portion of the signature must be 0, 1, 27, 28 or >= 35")


def validate_y_parity(value):
validate_positive_integer(value)
if value not in (0, 1):
raise ValidationError('y_parity must be either 0 or 1')


def _validate_outbound_access_list(access_list):
if not is_list_like(access_list):
raise ValidationError('access_list is not list-like.')
Expand Down Expand Up @@ -128,7 +134,7 @@ def _validate_outbound_access_list(access_list):
dissoc(LEGACY_TRANSACTION_VALIDATORS, 'v'), # y_parity in place of v for typed transactions
{
"chain_id": validate_uint256,
"y_parity": validate_signature_v,
"y_parity": validate_y_parity,
"access_list": if_not_null(_validate_outbound_access_list),
}
)
Expand Down Expand Up @@ -175,7 +181,7 @@ def _validate_outbound_access_list(access_list):

BLOCK_VALIDATORS = {
"number": validate_positive_integer,
'base_fee_per_gas': if_not_null(validate_positive_integer),
'base_fee_per_gas': validate_positive_integer,
"hash": validate_block_hash,
"parent_hash": validate_block_hash,
"nonce": validate_nonce,
Expand Down
4 changes: 1 addition & 3 deletions tests/core/validation/test_inbound_validation.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from __future__ import unicode_literals

import time

import pytest

try:
Expand Down Expand Up @@ -65,7 +63,7 @@ def test_time_travel_input_timestamp_validation(validator, timestamp, is_valid):
(b"earliest", False),
),
)
def test_block_number_intput_validation(validator, block_number, is_valid):
def test_block_number_input_validation(validator, block_number, is_valid):
if is_valid:
validator.validate_inbound_block_number(block_number)
else:
Expand Down

0 comments on commit 264d699

Please sign in to comment.