Skip to content

Commit 0cb25d9

Browse files
committed
Less broken things
All tests pass at the expense of cardboarded and duck-taped code. This needs some serious refactoring, perhaps more tests, then squashing all these WIP commits.
1 parent 0bc979b commit 0cb25d9

File tree

11 files changed

+571
-313
lines changed

11 files changed

+571
-313
lines changed

README.md

+156-103
Large diffs are not rendered by default.

eth_tester/backends/pyevm/main.py

+6-5
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
from eth_abi.exceptions import (
99
DecodingError
1010
)
11-
from toolz import dissoc
1211

1312
from eth_utils import (
1413
encode_hex,
@@ -120,6 +119,7 @@ def generate_genesis_state_for_keys(account_keys, overrides=None):
120119

121120

122121
def get_default_genesis_params(overrides=None):
122+
# commented out params became un-configurable in London
123123
default_genesis_params = {
124124
# "bloom": 0,
125125
"coinbase": GENESIS_COINBASE,
@@ -451,6 +451,11 @@ def _normalize_transaction(self, transaction, block_number='latest'):
451451
yield 'value', 0
452452
if 'to' not in transaction:
453453
yield 'to', b''
454+
if (
455+
all(_ in transaction for _ in ('max_fee_per_gas', 'max_priority_fee_per_gas')) and
456+
'access_list' not in transaction
457+
):
458+
yield 'access_list', []
454459

455460
def _get_normalized_and_unsigned_evm_transaction(self, transaction, block_number='latest'):
456461
normalized_transaction = self._normalize_transaction(transaction, block_number)
@@ -470,14 +475,10 @@ def _get_normalized_and_signed_evm_transaction(self, transaction, block_number='
470475

471476
def _create_type_aware_unsigned_transaction(self, normalized_txn):
472477
if all(_ in normalized_txn for _ in ("access_list", "gas_price")):
473-
# validated type='0x1' so can pop it back out since type is signed separate from txn
474-
normalized_txn = dissoc(normalized_txn, 'type')
475478
return self.chain.get_transaction_builder().new_unsigned_access_list_transaction(
476479
**normalized_txn
477480
)
478481
elif all(_ in normalized_txn for _ in ("max_fee_per_gas", "max_priority_fee_per_gas")):
479-
# validated type='0x2' so can pop it back out since type is signed separate from txn
480-
normalized_txn = dissoc(normalized_txn, 'type')
481482
return self.chain.get_transaction_builder().new_unsigned_dynamic_fee_transaction(
482483
**normalized_txn
483484
)

eth_tester/backends/pyevm/serializers.py

+51-33
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import rlp
2-
from toolz import dissoc, merge
32

3+
from toolz import (
4+
merge,
5+
)
6+
7+
from eth_tester.exceptions import ValidationError
48
from eth_tester.utils.address import (
59
generate_contract_address,
610
)
@@ -48,6 +52,7 @@ def serialize_block(block, full_transaction, is_pending):
4852
"timestamp": block.header.timestamp,
4953
"transactions": transactions,
5054
"uncles": [uncle.hash for uncle in block.uncles],
55+
"base_fee_per_gas": block.header.base_fee_per_gas,
5156
}
5257

5358

@@ -67,44 +72,57 @@ def serialize_transaction(block, transaction, transaction_index, is_pending):
6772
"value": transaction.value,
6873
"gas": transaction.gas,
6974
"data": transaction.data,
75+
"r": transaction.r,
76+
"s": transaction.s,
7077
}
71-
if hasattr(transaction, 'gas_price'):
72-
if hasattr(transaction, 'access_list'):
78+
if _field_in_transaction(transaction, 'gas_price'):
79+
if _field_in_transaction(transaction, 'access_list'):
7380
# access list transaction
74-
serialized_transaction = merge(
75-
common_transaction_params,
76-
{
77-
'gas_price': transaction.gas_price,
78-
'access_list': transaction.access_list,
79-
}
80-
)
81+
type_specific_params = {
82+
'chain_id': transaction.chain_id,
83+
'gas_price': transaction.gas_price,
84+
'access_list': transaction.access_list or [],
85+
'y_parity': transaction.y_parity,
86+
}
8187
else:
8288
# legacy transaction
83-
serialized_transaction = merge(
84-
common_transaction_params,
85-
{
86-
"gas_price": transaction.gas_price,
87-
"v": transaction.v,
88-
"r": transaction.r,
89-
"s": transaction.s,
90-
}
91-
)
92-
elif (
93-
hasattr(transaction, 'max_fee_per_gas') and
94-
hasattr(transaction, 'max_priority_fee_per_gas')
95-
):
96-
# dynamic fee transaction
97-
serialized_transaction = merge(
98-
common_transaction_params,
99-
{
100-
'max_fee_per_gas': transaction.max_fee_per_gas,
101-
'max_priority_fee_per_gas': transaction.max_priority_fee_per_gas,
102-
'access_list': transaction.access_list or None,
89+
type_specific_params = {
90+
'gas_price': transaction.gas_price,
91+
'v': transaction.v
10392
}
104-
)
93+
elif (_field_in_transaction(transaction, _) for _ in (
94+
'max_fee_per_gas' and 'max_priority_fee_per_gas'
95+
)):
96+
# dynamic fee transaction
97+
type_specific_params = {
98+
'chain_id': transaction.chain_id,
99+
'max_fee_per_gas': transaction.max_fee_per_gas,
100+
'max_priority_fee_per_gas': transaction.max_priority_fee_per_gas,
101+
'access_list': transaction.access_list or [],
102+
'y_parity': transaction.y_parity,
103+
}
105104
else:
106-
raise NotImplementedError('transaction type not recognized for serialization')
107-
return serialized_transaction
105+
raise ValidationError('Transaction serialization error')
106+
return merge(common_transaction_params, type_specific_params)
107+
108+
109+
def _field_in_transaction(transaction, field):
110+
"""
111+
There are many different classes of transactions, we have to be able to search for a
112+
particular field depending on the type of transaction - from a dict, to *LegacyTransaction to
113+
*TypedTransaction.
114+
"""
115+
if not isinstance(transaction, dict):
116+
try:
117+
txn_dict = transaction.as_dict()
118+
if not isinstance(txn_dict, dict):
119+
txn_dict = txn_dict.dictionary
120+
except AttributeError:
121+
pass
122+
try:
123+
return field in txn_dict
124+
except (NameError, TypeError):
125+
return hasattr(transaction, field)
108126

109127

110128
def serialize_transaction_receipt(

eth_tester/normalization/inbound.py

+20-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
from __future__ import absolute_import
22

3-
from eth_utils import encode_hex
3+
from eth_utils import (
4+
encode_hex,
5+
remove_0x_prefix,
6+
to_bytes,
7+
)
48

59
from eth_utils.curried import (
610
apply_one_of_formatters,
@@ -83,6 +87,19 @@ def normalize_private_key(value):
8387
(is_hex, to_canonical_address),
8488
))
8589

90+
91+
def _normalize_inbound_access_list(access_list):
92+
normalized_access_list = []
93+
for entry in access_list:
94+
normalized_address = to_bytes(hexstr=entry.get('address'))
95+
normalized_storage_keys = []
96+
for k in entry.get('storage_keys'):
97+
stripped_0x = remove_0x_prefix(k)
98+
normalized_storage_keys.append(int(stripped_0x))
99+
normalized_access_list.append((normalized_address, tuple(normalized_storage_keys)))
100+
return tuple(normalized_access_list)
101+
102+
86103
TRANSACTION_NORMALIZERS = {
87104
'chain_id': identity,
88105
'type': encode_hex,
@@ -95,13 +112,12 @@ def normalize_private_key(value):
95112
'nonce': identity,
96113
'value': identity,
97114
'data': decode_hex,
98-
'access_list': identity,
115+
'access_list': _normalize_inbound_access_list,
99116
'r': identity,
100117
's': identity,
101118
'v': identity,
119+
'y_parity': identity,
102120
}
103-
104-
105121
normalize_transaction = partial(normalize_dict, normalizers=TRANSACTION_NORMALIZERS)
106122

107123

@@ -116,8 +132,6 @@ def normalize_private_key(value):
116132
'data': decode_hex,
117133
'topics': partial(normalize_array, normalizer=decode_hex),
118134
}
119-
120-
121135
normalize_log_entry = partial(normalize_dict, normalizers=LOG_ENTRY_NORMALIZERS)
122136

123137

eth_tester/normalization/outbound.py

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

3-
from eth_utils import is_integer
4-
53
from eth_utils.curried import (
64
apply_one_of_formatters,
75
to_checksum_address,
@@ -23,6 +21,7 @@
2321
normalize_dict,
2422
normalize_array,
2523
)
24+
from ..utils.encoding import int_to_32byte_big_endian
2625

2726

2827
normalize_account = to_checksum_address
@@ -33,9 +32,23 @@
3332
(is_canonical_address, to_checksum_address),
3433
))
3534

35+
36+
def normalize_access_list(access_list):
37+
formatted_access_list = []
38+
for entry in access_list:
39+
account = to_checksum_address(entry['account'])
40+
storage_keys = tuple(
41+
[encode_hex(int_to_32byte_big_endian(k)) for k in entry['storage_keys']]
42+
)
43+
formatted_access_list.append({
44+
'address': account,
45+
'storage_keys': tuple(storage_keys)
46+
})
47+
return tuple(formatted_access_list)
48+
49+
3650
TRANSACTION_NORMALIZERS = {
3751
"chain_id": identity,
38-
"type": encode_hex,
3952
"hash": encode_hex,
4053
"nonce": identity,
4154
"block_hash": partial(normalize_if, conditional_fn=is_bytes, normalizer=encode_hex),
@@ -49,10 +62,11 @@
4962
"max_fee_per_gas": identity,
5063
"max_priority_fee_per_gas": identity,
5164
"data": encode_hex,
52-
"access_list": identity,
53-
"v": identity,
65+
"access_list": normalize_access_list,
5466
"r": identity,
5567
"s": identity,
68+
"v": identity,
69+
"y_parity": identity,
5670
}
5771
normalize_transaction = partial(normalize_dict, normalizers=TRANSACTION_NORMALIZERS)
5872

eth_tester/utils/backend_testing.py

+51-8
Original file line numberDiff line numberDiff line change
@@ -322,25 +322,68 @@ def test_send_transaction(self, eth_tester, test_transaction):
322322

323323
self._send_and_check_transaction(eth_tester, test_transaction, accounts[0])
324324

325-
def test_outbound_transaction_formatters(self, eth_tester):
325+
def test_send_access_list_transaction(self, eth_tester):
326326
accounts = eth_tester.get_accounts()
327327
assert accounts, "No accounts available for transaction sending"
328328

329-
test_transaction = {
329+
access_list_transaction = {
330+
'chain_id': 123456789,
331+
'from': accounts[0],
332+
'to': accounts[0],
333+
'value': 1,
334+
'gas': 40000,
335+
'gas_price': 1000000000,
336+
'access_list': [],
337+
}
338+
self._send_and_check_transaction(eth_tester, access_list_transaction, accounts[0])
339+
340+
# with access list
341+
access_list_transaction['access_list'] = (
342+
{
343+
'address': '0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae',
344+
'storage_keys': (
345+
'0x0000000000000000000000000000000000000000000000000000000000000003',
346+
'0x0000000000000000000000000000000000000000000000000000000000000007',
347+
)
348+
},
349+
{
350+
'address': '0xbb9bc244d798123fde783fcc1c72d3bb8c189413',
351+
'storage_keys': ()
352+
},
353+
)
354+
self._send_and_check_transaction(eth_tester, access_list_transaction, accounts[0])
355+
356+
def test_send_dynamic_fee_transaction(self, eth_tester):
357+
accounts = eth_tester.get_accounts()
358+
assert accounts, "No accounts available for transaction sending"
359+
360+
dynamic_fee_transaction = {
330361
'chain_id': 123456789,
331-
'type': '0x02',
332362
'from': accounts[0],
333363
'to': accounts[0],
334364
'value': 1,
335365
'gas': 40000,
336366
'max_fee_per_gas': 2000000000,
337367
'max_priority_fee_per_gas': 1000000000,
338-
'access_list': ((b'\xf0' * 20, (2, 3)),),
368+
'access_list': [],
339369
}
340-
341-
txn_hash = eth_tester.send_transaction(test_transaction)
342-
txn = eth_tester.get_transaction_by_hash(txn_hash)
343-
assert txn
370+
self._send_and_check_transaction(eth_tester, dynamic_fee_transaction, accounts[0])
371+
372+
# with access list
373+
dynamic_fee_transaction['access_list'] = (
374+
{
375+
'address': '0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae',
376+
'storage_keys': (
377+
'0x0000000000000000000000000000000000000000000000000000000000000003',
378+
'0x0000000000000000000000000000000000000000000000000000000000000007',
379+
)
380+
},
381+
{
382+
'address': '0xbb9bc244d798123fde783fcc1c72d3bb8c189413',
383+
'storage_keys': ()
384+
},
385+
)
386+
self._send_and_check_transaction(eth_tester, dynamic_fee_transaction, accounts[0])
344387

345388
def test_block_number_auto_mine_transactions_enabled(self, eth_tester):
346389
eth_tester.mine_blocks()

eth_tester/validation/common.py

+1-4
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,7 @@ def validate_transaction_params(value):
129129
)):
130130
raise ValidationError("legacy gas price and dynamic fee transaction parameters present")
131131
if "type" in value:
132-
if value["type"] in ("0x1", 1) and "access_list" not in value:
133-
raise ValidationError("type 1 transaction is missing access_list")
134-
if value["type"] in ("0x2", 2) and not all(_ in value for _ in (
132+
if value["type"] == "0x2" and not (_ in value for _ in (
135133
"max_fee_per_gas", "max_priority_fee_per_gas"
136134
)):
137135
raise ValidationError("type 2 transaction is missing fee values")
@@ -154,7 +152,6 @@ def validate_dict(value, key_validators):
154152
validate_transaction_params(value) # if transaction
155153
else:
156154
validate_has_required_keys(value, key_validators.keys())
157-
158155
key_errors = _accumulate_dict_errors(value, key_validators)
159156
if key_errors:
160157
key_messages = tuple(

0 commit comments

Comments
 (0)