Skip to content

Commit 264d699

Browse files
committed
London mocked backend logic and some more refactoring
1 parent 09feb17 commit 264d699

File tree

5 files changed

+103
-66
lines changed

5 files changed

+103
-66
lines changed

eth_tester/backends/mock/factory.py

+74-50
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
ZERO_32BYTES = b'\x00' * 32
2929
ZERO_8BYTES = b'\x00' * 8
3030
ZERO_ADDRESS = b'\x00' * 20
31+
BLOCK_ELASTICITY_MULTIPLIER = 2
32+
BASE_FEE_MAX_CHANGE_DENOMINATOR = 8
3133

3234

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

92-
if 'nonce' in overrides:
93-
yield 'nonce', overrides['nonce']
94-
else:
95-
yield 'nonce', 0
94+
is_dynamic_fee_transaction = (
95+
'gas_price' not in transaction and any(
96+
_ in transaction for _ in ('max_fee_per_gas', 'max_priority_fee_per_gas')
97+
)
98+
)
9699

97100
if 'hash' in overrides:
98101
yield 'hash', overrides['hash']
99102
else:
100103
# calculate hash after all fields are filled
101104
pass
102105

103-
if 'from' in overrides:
104-
yield 'from', overrides['from']
105-
else:
106-
yield 'from', transaction['from']
106+
# this pattern isn't the nicest to read but it keeps things clean. Here, we yield the overrides
107+
# value if it exists, else either the transaction value if that exists, or a default value
108+
yield 'nonce', overrides.get('nonce', 0)
109+
yield 'from', overrides.get('from', transaction.get('from'))
110+
yield 'to', overrides.get('to', transaction.get('to', b''))
111+
yield 'data', overrides.get('data', transaction.get('data', b''))
112+
yield 'value', overrides.get('value', transaction.get('value', 0))
113+
yield 'nonce', overrides.get('nonce', transaction.get('nonce', 0))
114+
yield 'gas', overrides.get('gas', transaction.get('gas'))
107115

108-
if 'gas' in overrides:
109-
yield 'gas', overrides['gas']
116+
if 'gas_price' in transaction or 'gas_price' in overrides:
117+
# gas price is 1 gwei in order to be at least the base fee at the (London) genesis block
118+
yield 'gas_price', overrides.get('gas_price', transaction.get('gas_price', 1000000000))
110119
else:
111-
yield 'gas', transaction['gas']
120+
# if no gas_price specified, default to dynamic fee txn parameters
121+
is_dynamic_fee_transaction = True
112122

113-
if 'gas_price' in overrides:
114-
yield 'gas_price', overrides['gas_price']
115-
else:
116-
yield 'gas_price', transaction.get('gas_price', 1) # TODO: make configurable
123+
if is_dynamic_fee_transaction:
124+
yield 'max_fee_per_gas', overrides.get(
125+
'max_fee_per_gas', transaction.get('max_fee_per_gas', 1000000000)
126+
)
127+
yield 'max_priority_fee_per_gas', overrides.get(
128+
'max_priority_fee_per_gas', transaction.get('max_priority_fee_per_gas', 1000000000)
129+
)
117130

118-
if 'to' in overrides:
119-
yield 'to', overrides['to']
131+
if is_dynamic_fee_transaction or 'access_list' in transaction:
132+
# if is typed transaction (dynamic fee or access list transaction)
133+
yield 'chain_id', overrides.get('chain_id', transaction.get('chain_id', 131277322940537))
134+
yield 'access_list', overrides.get('access_list', transaction.get('access_list', []))
135+
yield 'y_parity', overrides.get('y_parity', transaction.get('y_parity', 0))
120136
else:
121-
yield 'to', transaction.get('to', b'')
137+
yield 'v', overrides.get('v', transaction.get('v', 27))
122138

123-
if 'data' in overrides:
124-
yield 'data', overrides['data']
125-
else:
126-
yield 'data', transaction.get('data', b'')
127-
128-
if 'value' in overrides:
129-
yield 'value', overrides['value']
130-
else:
131-
yield 'value', transaction.get('value', 0)
132-
133-
if 'nonce' in overrides:
134-
yield 'nonce', overrides['nonce']
135-
else:
136-
yield 'nonce', transaction.get('nonce', 0)
137-
138-
if 'v' in overrides:
139-
yield 'v', overrides['v']
140-
else:
141-
yield 'v', transaction.get('v', 27)
142-
143-
if 'r' in overrides:
144-
yield 'r', overrides['r']
145-
else:
146-
yield 'r', transaction.get('r', 12345)
147-
148-
if 's' in overrides:
149-
yield 's', overrides['s']
150-
else:
151-
yield 's', transaction.get('s', 67890)
139+
yield 'r', overrides.get('r', transaction.get('r', 12345))
140+
yield 's', overrides.get('s', transaction.get('s', 67890))
152141

153142

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

201190

202191
@to_dict
203-
def make_receipt(transaction, block, transaction_index, overrides=None):
192+
def make_receipt(transaction, block, _transaction_index, overrides=None):
204193
if overrides is None:
205194
overrides = {}
206195

@@ -253,12 +242,12 @@ def make_genesis_block(overrides=None):
253242
"total_difficulty": 131072,
254243
"size": 0,
255244
"extra_data": ZERO_32BYTES,
256-
"gas_limit": 3141592,
245+
"gas_limit": 30029122, # gas limit at London fork block 12965000 on mainnet
257246
"gas_used": 0,
258247
"timestamp": int(time.time()),
259248
"transactions": [],
260249
"uncles": [],
261-
"base_fee_per_gas": 1000000000,
250+
"base_fee_per_gas": 1000000000, # base fee at London fork block 12965000 on mainnet
262251
}
263252
if overrides is not None:
264253
genesis_block = merge_genesis_overrides(defaults=default_genesis_block,
@@ -369,3 +358,38 @@ def make_block_from_parent(parent_block, overrides=None):
369358
yield 'uncles', overrides['uncles']
370359
else:
371360
yield 'uncles', []
361+
362+
if 'base_fee_per_gas' in overrides:
363+
yield 'base_fee_per_gas', overrides['base_fee_per_gas']
364+
else:
365+
yield 'base_fee_per_gas', _calculate_expected_base_fee_per_gas(parent_block)
366+
367+
368+
def _calculate_expected_base_fee_per_gas(parent_block) -> int:
369+
"""py-evm logic for calculating the base fee from parent header"""
370+
parent_base_fee_per_gas = parent_block['base_fee_per_gas']
371+
372+
parent_gas_target = parent_block['gas_limit'] // BLOCK_ELASTICITY_MULTIPLIER
373+
parent_gas_used = parent_block['gas_used']
374+
375+
if parent_gas_used == parent_gas_target:
376+
return parent_base_fee_per_gas
377+
378+
elif parent_gas_used > parent_gas_target:
379+
gas_used_delta = parent_gas_used - parent_gas_target
380+
overburnt_wei = parent_base_fee_per_gas * gas_used_delta
381+
base_fee_per_gas_delta = max(
382+
overburnt_wei // parent_gas_target // BASE_FEE_MAX_CHANGE_DENOMINATOR,
383+
1,
384+
)
385+
return parent_base_fee_per_gas + base_fee_per_gas_delta
386+
387+
else:
388+
gas_used_delta = parent_gas_target - parent_gas_used
389+
underburnt_wei = parent_base_fee_per_gas * gas_used_delta
390+
base_fee_per_gas_delta = (
391+
underburnt_wei
392+
// parent_gas_target
393+
// BASE_FEE_MAX_CHANGE_DENOMINATOR
394+
)
395+
return max(parent_base_fee_per_gas - base_fee_per_gas_delta, 0)

eth_tester/backends/pyevm/main.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ def generate_genesis_state_for_keys(account_keys, overrides=None):
119119

120120

121121
def get_default_genesis_params(overrides=None):
122-
# commented out params became un-configurable in London
122+
# commented out params became un-configurable in py-evm during London refactor
123123
default_genesis_params = {
124124
# "bloom": 0,
125125
"coinbase": GENESIS_COINBASE,
@@ -193,7 +193,7 @@ def get_transaction_builder(self):
193193
base_db = get_db_backend()
194194

195195
chain = MainnetTesterNoProofChain.from_genesis(base_db, genesis_params, genesis_state)
196-
chain.chain_id = 1337 # typed transactions need a chain_id
196+
chain.chain_id = 131277322940537 # typed transactions need a chain_id
197197
return account_keys, chain
198198

199199

@@ -438,7 +438,7 @@ def get_base_fee(self, block_number='latest'):
438438
#
439439
@to_dict
440440
def _normalize_transaction(self, transaction, block_number='latest'):
441-
is_typed_transaction = False
441+
is_dynamic_fee_transaction = False
442442
for key in transaction:
443443
if key == 'from':
444444
continue
@@ -448,7 +448,7 @@ def _normalize_transaction(self, transaction, block_number='latest'):
448448
if 'data' not in transaction:
449449
yield 'data', b''
450450
if 'gas_price' not in transaction:
451-
is_typed_transaction = True
451+
is_dynamic_fee_transaction = True
452452
if not any(_ in transaction for _ in ('max_fee_per_gas', 'max_priority_fee_per_gas')):
453453
yield 'max_fee_per_gas', 1 * 10**9
454454
yield 'max_priority_fee_per_gas', 1 * 10**9
@@ -459,10 +459,10 @@ def _normalize_transaction(self, transaction, block_number='latest'):
459459
yield 'value', 0
460460
if 'to' not in transaction:
461461
yield 'to', b''
462-
if is_typed_transaction:
462+
if is_dynamic_fee_transaction or 'access_list' in transaction:
463463
if 'access_list' not in transaction:
464464
yield 'access_list', []
465-
if not transaction.get('chain_id'):
465+
if 'chain_id' not in transaction:
466466
yield 'chain_id', self.chain.chain_id
467467

468468
def _get_normalized_and_unsigned_evm_transaction(self, transaction, block_number='latest'):

eth_tester/utils/backend_testing.py

+14-5
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,6 @@
105105

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

127-
def _check_transactions(self, expected_transaction, actual_transaction):
126+
@staticmethod
127+
def _check_transactions(expected_transaction, actual_transaction):
128128
assert is_same_address(actual_transaction['from'], expected_transaction['from'])
129129
if 'to' not in expected_transaction or expected_transaction['to'] == '':
130130
assert actual_transaction['to'] == ''
131131
else:
132132
assert is_same_address(actual_transaction['to'], expected_transaction['to'])
133-
# assert actual_transaction['gas_price'] == expected_transaction['gas_price']
133+
134+
if expected_transaction.get('gas_price'):
135+
assert actual_transaction['gas_price'] == expected_transaction['gas_price']
136+
else:
137+
assert actual_transaction['max_fee_per_gas'] == expected_transaction['max_fee_per_gas']
138+
assert (
139+
actual_transaction['max_priority_fee_per_gas'] ==
140+
expected_transaction['max_priority_fee_per_gas']
141+
)
134142
assert actual_transaction['gas'] == expected_transaction['gas']
135143
assert actual_transaction['value'] == expected_transaction['value']
136144

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

332340
access_list_transaction = {
333-
'chain_id': 123456789,
341+
'chain_id': 131277322940537,
334342
'from': accounts[0],
335343
'to': accounts[0],
336344
'value': 1,
@@ -361,7 +369,7 @@ def test_send_dynamic_fee_transaction(self, eth_tester):
361369
assert accounts, "No accounts available for transaction sending"
362370

363371
dynamic_fee_transaction = {
364-
'chain_id': 123456789,
372+
'chain_id': 131277322940537,
365373
'from': accounts[0],
366374
'to': accounts[0],
367375
'value': 1,
@@ -596,6 +604,7 @@ def test_get_block_by_hash(self, eth_tester):
596604
block = eth_tester.get_block_by_hash(block_hash)
597605
assert block['number'] == block_number
598606
assert block['hash'] == block_hash
607+
assert block['base_fee_per_gas'] is not None
599608

600609
def test_get_block_by_hash_full_transactions(self, eth_tester):
601610
eth_tester.mine_blocks(2)

eth_tester/validation/outbound.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,12 @@ def validate_signature_v(value):
8787
raise ValidationError("The `v` portion of the signature must be 0, 1, 27, 28 or >= 35")
8888

8989

90+
def validate_y_parity(value):
91+
validate_positive_integer(value)
92+
if value not in (0, 1):
93+
raise ValidationError('y_parity must be either 0 or 1')
94+
95+
9096
def _validate_outbound_access_list(access_list):
9197
if not is_list_like(access_list):
9298
raise ValidationError('access_list is not list-like.')
@@ -128,7 +134,7 @@ def _validate_outbound_access_list(access_list):
128134
dissoc(LEGACY_TRANSACTION_VALIDATORS, 'v'), # y_parity in place of v for typed transactions
129135
{
130136
"chain_id": validate_uint256,
131-
"y_parity": validate_signature_v,
137+
"y_parity": validate_y_parity,
132138
"access_list": if_not_null(_validate_outbound_access_list),
133139
}
134140
)
@@ -175,7 +181,7 @@ def _validate_outbound_access_list(access_list):
175181

176182
BLOCK_VALIDATORS = {
177183
"number": validate_positive_integer,
178-
'base_fee_per_gas': if_not_null(validate_positive_integer),
184+
'base_fee_per_gas': validate_positive_integer,
179185
"hash": validate_block_hash,
180186
"parent_hash": validate_block_hash,
181187
"nonce": validate_nonce,

tests/core/validation/test_inbound_validation.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
from __future__ import unicode_literals
22

3-
import time
4-
53
import pytest
64

75
try:
@@ -65,7 +63,7 @@ def test_time_travel_input_timestamp_validation(validator, timestamp, is_valid):
6563
(b"earliest", False),
6664
),
6765
)
68-
def test_block_number_intput_validation(validator, block_number, is_valid):
66+
def test_block_number_input_validation(validator, block_number, is_valid):
6967
if is_valid:
7068
validator.validate_inbound_block_number(block_number)
7169
else:

0 commit comments

Comments
 (0)