Skip to content

Commit 4246265

Browse files
authored
Merge pull request #206 from fselmo/berlin-and-london-updates
Berlin typed transactions and London support
2 parents 06def9d + 8d18f65 commit 4246265

23 files changed

+1320
-384
lines changed

CHANGELOG

+13-4
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,32 @@
1-
Unreleased
1+
v0.6.0-beta.1
22
-------------
33

4+
Unreleased
5+
46
- Features
57

8+
- London support (https://github.com/ethereum/eth-tester/pull/206)
9+
- Upgrade py-evm to v0.5.0-alpha.1 for London support
10+
- Default to London
11+
- Support access list transactions and dynamic fee transactions
12+
- Transaction param support for `access_list`, `type`, `max_fee_per_gas`, `max_priority_fee_per_gas`
13+
- Transaction receipt param support for `type` and `effective_gas_price`
14+
- Block param support for `base_fee_per_gas`
615
- Support for custom mnemonic when initializing the Backend for EthTester
716

817
- Misc
918

1019
- Adjust wording in README regarding genesis parameters
1120

12-
1321
v0.5.0-beta.4
1422
-------------
23+
1524
Released 2021-04-12
1625

1726
- Features
1827

19-
- Upgrade py-evm to v0.4.0-alpha.4 for Python 3.9 support
20-
https://github.com/ethereum/eth-tester/pull/205
28+
- Upgrade py-evm to v0.4.0-alpha.4 for Python 3.9 support
29+
https://github.com/ethereum/eth-tester/pull/205
2130
- Upgrade py-evm to v0.4.0-alpha.3, for Berlin support
2231
Default to Berlin
2332
https://github.com/ethereum/eth-tester/pull/204

README.md

+163-109
Large diffs are not rendered by default.

eth_tester/backends/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def get_chain_backend_class(backend_import_path=None):
2828
backend_import_path = get_import_path(PyEVMBackend)
2929
else:
3030
warnings.warn(UserWarning(
31-
"Ethereum Tester: No backend was explicitely set, and no *full* "
31+
"Ethereum Tester: No backend was explicitly set, and no *full* "
3232
"backends were available. Falling back to the `MockBackend` "
3333
"which does not support all EVM functionality. Please refer to "
3434
"the `eth-tester` documentation for information on what "

eth_tester/backends/mock/common.py

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
def calculate_effective_gas_price(transaction, block):
2+
return (
3+
min(
4+
transaction['max_fee_per_gas'],
5+
transaction['max_priority_fee_per_gas'] + block['base_fee_per_gas']
6+
)
7+
if 'max_fee_per_gas' in transaction
8+
else transaction['gas_price']
9+
)

eth_tester/backends/mock/factory.py

+108-82
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import functools
22
import time
33

4+
from eth_tester.backends.mock.common import (
5+
calculate_effective_gas_price,
6+
)
7+
from eth_tester.utils.transactions import (
8+
extract_transaction_type,
9+
)
410
from eth_utils import (
511
apply_to_return_value,
612
is_bytes,
@@ -20,6 +26,7 @@
2026
)
2127

2228
from eth_tester.backends.common import merge_genesis_overrides
29+
from eth_tester.constants import DYNAMIC_FEE_TRANSACTION_PARAMS
2330
from eth_tester.utils.address import (
2431
generate_contract_address,
2532
)
@@ -28,6 +35,8 @@
2835
ZERO_32BYTES = b'\x00' * 32
2936
ZERO_8BYTES = b'\x00' * 8
3037
ZERO_ADDRESS = b'\x00' * 20
38+
BLOCK_ELASTICITY_MULTIPLIER = 2
39+
BASE_FEE_MAX_CHANGE_DENOMINATOR = 8
3140

3241

3342
@apply_to_return_value(b'|'.join)
@@ -86,69 +95,53 @@ def create_transaction(transaction, block, transaction_index, is_pending, overri
8695

8796
@to_dict
8897
def _fill_transaction(transaction, block, transaction_index, is_pending, overrides=None):
98+
is_dynamic_fee_transaction = (
99+
any(_ in transaction for _ in DYNAMIC_FEE_TRANSACTION_PARAMS)
100+
or not any(_ in transaction for _ in DYNAMIC_FEE_TRANSACTION_PARAMS + ('gas_price',))
101+
)
102+
89103
if overrides is None:
90104
overrides = {}
91105

92-
if 'nonce' in overrides:
93-
yield 'nonce', overrides['nonce']
94-
else:
95-
yield 'nonce', 0
96-
97-
if 'hash' in overrides:
106+
if 'hash' in overrides: # else calculate hash after all fields are filled
98107
yield 'hash', overrides['hash']
99-
else:
100-
# calculate hash after all fields are filled
101-
pass
102-
103-
if 'from' in overrides:
104-
yield 'from', overrides['from']
105-
else:
106-
yield 'from', transaction['from']
107108

108-
if 'gas' in overrides:
109-
yield 'gas', overrides['gas']
110-
else:
111-
yield 'gas', transaction['gas']
109+
# Here, we yield the key with the overrides value if it exists, else either the transaction
110+
# value if it exists or a default value
111+
yield 'nonce', overrides.get('nonce', 0)
112+
yield 'from', overrides.get('from', transaction.get('from'))
113+
yield 'to', overrides.get('to', transaction.get('to', b''))
114+
yield 'data', overrides.get('data', transaction.get('data', b''))
115+
yield 'value', overrides.get('value', transaction.get('value', 0))
116+
yield 'gas', overrides.get('gas', transaction.get('gas'))
117+
yield 'r', overrides.get('r', transaction.get('r', 12345))
118+
yield 's', overrides.get('s', transaction.get('s', 67890))
119+
120+
if is_dynamic_fee_transaction:
121+
# dynamic fee transaction (type = 2)
122+
yield 'max_fee_per_gas', overrides.get(
123+
'max_fee_per_gas', transaction.get('max_fee_per_gas', 1000000000)
124+
)
125+
yield 'max_priority_fee_per_gas', overrides.get(
126+
'max_priority_fee_per_gas', transaction.get('max_priority_fee_per_gas', 1000000000)
127+
)
128+
yield from _yield_typed_transaction_fields(overrides, transaction)
129+
130+
else:
131+
yield 'gas_price', overrides.get('gas_price', transaction.get('gas_price'))
132+
if 'access_list' in transaction:
133+
# access list transaction (type = 1)
134+
yield from _yield_typed_transaction_fields(overrides, transaction)
112135

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
117-
118-
if 'to' in overrides:
119-
yield 'to', overrides['to']
120-
else:
121-
yield 'to', transaction.get('to', b'')
122-
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)
136+
else:
137+
# legacy transaction
138+
yield 'v', overrides.get('v', transaction.get('v', 27))
142139

143-
if 'r' in overrides:
144-
yield 'r', overrides['r']
145-
else:
146-
yield 'r', transaction.get('r', 12345)
147140

148-
if 's' in overrides:
149-
yield 's', overrides['s']
150-
else:
151-
yield 's', transaction.get('s', 67890)
141+
def _yield_typed_transaction_fields(overrides, transaction):
142+
yield 'chain_id', overrides.get('chain_id', transaction.get('chain_id', 131277322940537))
143+
yield 'access_list', overrides.get('access_list', transaction.get('access_list', ()))
144+
yield 'y_parity', overrides.get('y_parity', transaction.get('y_parity', 0))
152145

153146

154147
@to_dict
@@ -200,36 +193,33 @@ def make_log(transaction, block, transaction_index, log_index, overrides=None):
200193

201194

202195
@to_dict
203-
def make_receipt(transaction, block, transaction_index, overrides=None):
196+
def make_receipt(transaction, block, _transaction_index, overrides=None):
204197
if overrides is None:
205198
overrides = {}
206199

207-
if 'gas_used' in overrides:
208-
gas_used = overrides['gas_used']
209-
else:
210-
gas_used = 21000
200+
gas_used = overrides.get('gas_used', 21000)
211201
yield 'gas_used', gas_used
212-
213-
if 'cumulative_gas_used' in overrides:
214-
yield 'cumulative_gas_used', overrides['cumulative_gas_used']
215-
else:
216-
yield 'cumulative_gas_used', block['gas_used'] + gas_used
217-
218-
if 'contract_address' in overrides:
219-
yield 'contract_address', overrides['contract_address']
220-
else:
221-
contract_address = generate_contract_address(transaction['from'], transaction['nonce'])
222-
yield 'contract_address', contract_address
223-
224-
if 'logs' in overrides:
225-
yield 'logs', overrides['logs']
226-
else:
227-
yield 'logs', []
228-
229-
if 'transaction_hash' in overrides:
230-
yield 'transaction_hash', overrides['transaction_hash']
231-
else:
232-
yield 'transaction_hash', transaction['hash']
202+
yield 'logs', overrides.get('logs', [])
203+
yield 'transaction_hash', overrides.get('transaction_hash', transaction.get('hash'))
204+
yield (
205+
'cumulative_gas_used',
206+
overrides.get('cumulative_gas_used', block.get('gas_used') + gas_used)
207+
)
208+
yield (
209+
'effective_gas_price',
210+
overrides.get('effective_gas_price', calculate_effective_gas_price(transaction, block))
211+
)
212+
yield (
213+
'type',
214+
overrides.get('type', transaction.get('type', extract_transaction_type(transaction)))
215+
)
216+
yield (
217+
'contract_address',
218+
overrides.get(
219+
'contract_address',
220+
generate_contract_address(transaction['from'], transaction['nonce'])
221+
)
222+
)
233223

234224

235225
GENESIS_NONCE = b'\x00\x00\x00\x00\x00\x00\x00*' # 42 encoded as big-endian-integer
@@ -253,11 +243,12 @@ def make_genesis_block(overrides=None):
253243
"total_difficulty": 131072,
254244
"size": 0,
255245
"extra_data": ZERO_32BYTES,
256-
"gas_limit": 3141592,
246+
"gas_limit": 30029122, # gas limit at London fork block 12965000 on mainnet
257247
"gas_used": 0,
258248
"timestamp": int(time.time()),
259249
"transactions": [],
260250
"uncles": [],
251+
"base_fee_per_gas": 1000000000, # base fee at London fork block 12965000 on mainnet
261252
}
262253
if overrides is not None:
263254
genesis_block = merge_genesis_overrides(defaults=default_genesis_block,
@@ -368,3 +359,38 @@ def make_block_from_parent(parent_block, overrides=None):
368359
yield 'uncles', overrides['uncles']
369360
else:
370361
yield 'uncles', []
362+
363+
if 'base_fee_per_gas' in overrides:
364+
yield 'base_fee_per_gas', overrides['base_fee_per_gas']
365+
else:
366+
yield 'base_fee_per_gas', _calculate_expected_base_fee_per_gas(parent_block)
367+
368+
369+
def _calculate_expected_base_fee_per_gas(parent_block) -> int:
370+
"""py-evm logic for calculating the base fee from parent header"""
371+
parent_base_fee_per_gas = parent_block['base_fee_per_gas']
372+
373+
parent_gas_target = parent_block['gas_limit'] // BLOCK_ELASTICITY_MULTIPLIER
374+
parent_gas_used = parent_block['gas_used']
375+
376+
if parent_gas_used == parent_gas_target:
377+
return parent_base_fee_per_gas
378+
379+
elif parent_gas_used > parent_gas_target:
380+
gas_used_delta = parent_gas_used - parent_gas_target
381+
overburnt_wei = parent_base_fee_per_gas * gas_used_delta
382+
base_fee_per_gas_delta = max(
383+
overburnt_wei // parent_gas_target // BASE_FEE_MAX_CHANGE_DENOMINATOR,
384+
1,
385+
)
386+
return parent_base_fee_per_gas + base_fee_per_gas_delta
387+
388+
else:
389+
gas_used_delta = parent_gas_target - parent_gas_used
390+
underburnt_wei = parent_base_fee_per_gas * gas_used_delta
391+
base_fee_per_gas_delta = (
392+
underburnt_wei
393+
// parent_gas_target
394+
// BASE_FEE_MAX_CHANGE_DENOMINATOR
395+
)
396+
return max(parent_base_fee_per_gas - base_fee_per_gas_delta, 0)

eth_tester/backends/mock/main.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -246,9 +246,10 @@ def get_transaction_receipt(self, transaction_hash):
246246
raise TransactionNotFound(
247247
f"No transaction found for hash: {transaction_hash}"
248248
)
249-
_, block, transaction_index = self._get_transaction_by_hash(transaction_hash)
249+
transaction, block, transaction_index = self._get_transaction_by_hash(transaction_hash)
250250
return serialize_receipt(
251251
receipt,
252+
transaction,
252253
block,
253254
transaction_index,
254255
is_pending=(block['number'] == self.block['number']),
@@ -284,6 +285,7 @@ def send_raw_transaction(self, raw_transaction):
284285
'from': _generate_dummy_address(0),
285286
'hash': transaction_hash,
286287
'gas': 21000,
288+
'gas_price': 1000000000,
287289
}
288290
return self.send_transaction(transaction)
289291

@@ -304,7 +306,7 @@ def send_transaction(self, transaction):
304306
return full_transaction['hash']
305307

306308
def send_signed_transaction(self, signed_transaction):
307-
transaction = dissoc(signed_transaction, 'r', 's', 'v')
309+
transaction = dissoc(signed_transaction, 'r', 's', 'v', 'y_parity')
308310
return self.send_transaction(transaction)
309311

310312
def estimate_gas(self, transaction):

eth_tester/backends/mock/serializers.py

+26-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
from eth_tester.backends.mock.common import (
2+
calculate_effective_gas_price,
3+
)
4+
from eth_tester.utils.transactions import (
5+
extract_transaction_type,
6+
)
17
from eth_utils.toolz import (
28
assoc,
39
partial,
@@ -27,15 +33,28 @@ def serialize_full_transaction(transaction, block, transaction_index, is_pending
2733
block_number = block['number']
2834
block_hash = block['hash']
2935

30-
return pipe(
36+
serialized_transaction = pipe(
3137
transaction,
3238
partial(assoc, key='block_number', value=block_number),
3339
partial(assoc, key='block_hash', value=block_hash),
3440
partial(assoc, key='transaction_index', value=transaction_index),
41+
partial(assoc, key='type', value=extract_transaction_type(transaction))
3542
)
3643

44+
if 'gas_price' in transaction:
45+
return serialized_transaction
46+
else:
47+
# TODO: Sometime in 2022 the inclusion of gas_price may be removed from dynamic fee
48+
# transactions and we can get rid of this behavior.
49+
# https://github.com/ethereum/execution-specs/pull/251
50+
gas_price = (
51+
transaction['max_fee_per_gas'] if is_pending
52+
else calculate_effective_gas_price(transaction, block)
53+
)
54+
return assoc(serialized_transaction, 'gas_price', gas_price)
55+
3756

38-
def serialize_receipt(transaction, block, transaction_index, is_pending):
57+
def serialize_receipt(receipt, transaction, block, transaction_index, is_pending):
3958
if is_pending:
4059
block_number = None
4160
block_hash = None
@@ -45,9 +64,13 @@ def serialize_receipt(transaction, block, transaction_index, is_pending):
4564
block_hash = block['hash']
4665

4766
return pipe(
48-
transaction,
67+
receipt,
4968
partial(assoc, key='block_number', value=block_number),
5069
partial(assoc, key='block_hash', value=block_hash),
5170
partial(assoc, key='transaction_index', value=transaction_index),
5271
partial(assoc, key='state_root', value=b'\x00'),
72+
partial(assoc, key='effective_gas_price', value=(
73+
calculate_effective_gas_price(transaction, block)
74+
)),
75+
partial(assoc, key='type', value=extract_transaction_type(transaction))
5376
)

0 commit comments

Comments
 (0)