Skip to content

Commit 43bcf1c

Browse files
committed
add fields to receipts and tests, more refactoring
- add effective_gas_price and type to transaction receipt object - add tests for calculating effective_gas_price and checking type in txn receipts
1 parent 86bee37 commit 43bcf1c

File tree

15 files changed

+349
-121
lines changed

15 files changed

+349
-121
lines changed

eth_tester/backends/mock/common.py

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
def extract_transaction_type(transaction):
2+
return (
3+
'0x2' if 'max_fee_per_gas' in transaction
4+
else '0x1' if 'max_fee_per_gas' not in transaction and 'access_list' in transaction
5+
else '0x0' # legacy transactions being '0x0' taken from current geth version v1.10.10
6+
)
7+
8+
9+
def calculate_effective_gas_price(transaction, block):
10+
return (
11+
min(
12+
transaction['max_fee_per_gas'],
13+
transaction['max_priority_fee_per_gas'] + block['base_fee_per_gas']
14+
)
15+
if 'max_fee_per_gas' in transaction
16+
else transaction['gas_price']
17+
)

eth_tester/backends/mock/factory.py

+34-27
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import functools
22
import time
33

4+
from eth_tester.backends.mock.common import (
5+
calculate_effective_gas_price,
6+
extract_transaction_type,
7+
)
48
from eth_utils import (
59
apply_to_return_value,
610
is_bytes,
@@ -20,6 +24,7 @@
2024
)
2125

2226
from eth_tester.backends.common import merge_genesis_overrides
27+
from eth_tester.constants import EIP_1559_TRANSACTION_PARAMS
2328
from eth_tester.utils.address import (
2429
generate_contract_address,
2530
)
@@ -88,6 +93,11 @@ def create_transaction(transaction, block, transaction_index, is_pending, overri
8893

8994
@to_dict
9095
def _fill_transaction(transaction, block, transaction_index, is_pending, overrides=None):
96+
is_dynamic_fee_transaction = (
97+
any(_ in transaction for _ in EIP_1559_TRANSACTION_PARAMS)
98+
or not any(_ in transaction for _ in EIP_1559_TRANSACTION_PARAMS + ('gas_price',))
99+
)
100+
91101
if overrides is None:
92102
overrides = {}
93103

@@ -105,7 +115,7 @@ def _fill_transaction(transaction, block, transaction_index, is_pending, overrid
105115
yield 'r', overrides.get('r', transaction.get('r', 12345))
106116
yield 's', overrides.get('s', transaction.get('s', 67890))
107117

108-
if 'gas_price' not in transaction:
118+
if is_dynamic_fee_transaction:
109119
# dynamic fee transaction (type = 2)
110120
yield 'max_fee_per_gas', overrides.get(
111121
'max_fee_per_gas', transaction.get('max_fee_per_gas', 1000000000)
@@ -128,7 +138,7 @@ def _fill_transaction(transaction, block, transaction_index, is_pending, overrid
128138

129139
def _yield_typed_transaction_fields(overrides, transaction):
130140
yield 'chain_id', overrides.get('chain_id', transaction.get('chain_id', 131277322940537))
131-
yield 'access_list', overrides.get('access_list', transaction.get('access_list', []))
141+
yield 'access_list', overrides.get('access_list', transaction.get('access_list', ()))
132142
yield 'y_parity', overrides.get('y_parity', transaction.get('y_parity', 0))
133143

134144

@@ -185,32 +195,29 @@ def make_receipt(transaction, block, _transaction_index, overrides=None):
185195
if overrides is None:
186196
overrides = {}
187197

188-
if 'gas_used' in overrides:
189-
gas_used = overrides['gas_used']
190-
else:
191-
gas_used = 21000
198+
gas_used = overrides.get('gas_used', 21000)
192199
yield 'gas_used', gas_used
193-
194-
if 'cumulative_gas_used' in overrides:
195-
yield 'cumulative_gas_used', overrides['cumulative_gas_used']
196-
else:
197-
yield 'cumulative_gas_used', block['gas_used'] + gas_used
198-
199-
if 'contract_address' in overrides:
200-
yield 'contract_address', overrides['contract_address']
201-
else:
202-
contract_address = generate_contract_address(transaction['from'], transaction['nonce'])
203-
yield 'contract_address', contract_address
204-
205-
if 'logs' in overrides:
206-
yield 'logs', overrides['logs']
207-
else:
208-
yield 'logs', []
209-
210-
if 'transaction_hash' in overrides:
211-
yield 'transaction_hash', overrides['transaction_hash']
212-
else:
213-
yield 'transaction_hash', transaction['hash']
200+
yield 'logs', overrides.get('logs', [])
201+
yield 'transaction_hash', overrides.get('transaction_hash', transaction.get('hash'))
202+
yield (
203+
'cumulative_gas_used',
204+
overrides.get('cumulative_gas_used', block.get('gas_used') + gas_used)
205+
)
206+
yield (
207+
'effective_gas_price',
208+
overrides.get('effective_gas_price', calculate_effective_gas_price(transaction, block))
209+
)
210+
yield (
211+
'type',
212+
overrides.get('type', transaction.get('type', extract_transaction_type(transaction)))
213+
)
214+
yield (
215+
'contract_address',
216+
overrides.get(
217+
'contract_address',
218+
generate_contract_address(transaction['from'], transaction['nonce'])
219+
)
220+
)
214221

215222

216223
GENESIS_NONCE = b'\x00\x00\x00\x00\x00\x00\x00*' # 42 encoded as big-endian-integer

eth_tester/backends/mock/main.py

+3-1
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

eth_tester/backends/mock/serializers.py

+21-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
from eth_tester.backends.mock.common import (
2+
calculate_effective_gas_price,
3+
extract_transaction_type,
4+
)
15
from eth_utils.toolz import (
26
assoc,
37
partial,
@@ -27,15 +31,25 @@ def serialize_full_transaction(transaction, block, transaction_index, is_pending
2731
block_number = block['number']
2832
block_hash = block['hash']
2933

30-
return pipe(
34+
serialized_transaction = pipe(
3135
transaction,
3236
partial(assoc, key='block_number', value=block_number),
3337
partial(assoc, key='block_hash', value=block_hash),
3438
partial(assoc, key='transaction_index', value=transaction_index),
39+
partial(assoc, key='type', value=extract_transaction_type(transaction))
3540
)
3641

42+
if 'gas_price' in transaction:
43+
return serialized_transaction
44+
else:
45+
return assoc(
46+
serialized_transaction,
47+
'gas_price',
48+
calculate_effective_gas_price(transaction, block)
49+
)
3750

38-
def serialize_receipt(transaction, block, transaction_index, is_pending):
51+
52+
def serialize_receipt(receipt, transaction, block, transaction_index, is_pending):
3953
if is_pending:
4054
block_number = None
4155
block_hash = None
@@ -45,9 +59,13 @@ def serialize_receipt(transaction, block, transaction_index, is_pending):
4559
block_hash = block['hash']
4660

4761
return pipe(
48-
transaction,
62+
receipt,
4963
partial(assoc, key='block_number', value=block_number),
5064
partial(assoc, key='block_hash', value=block_hash),
5165
partial(assoc, key='transaction_index', value=transaction_index),
5266
partial(assoc, key='state_root', value=b'\x00'),
67+
partial(assoc, key='effective_gas_price', value=(
68+
calculate_effective_gas_price(transaction, block)
69+
)),
70+
partial(assoc, key='type', value=extract_transaction_type(transaction))
5371
)

eth_tester/backends/pyevm/main.py

+10-4
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
from eth_keys import KeyAPI
2828

29+
from eth_tester.constants import EIP_1559_TRANSACTION_PARAMS
2930
from eth_tester.exceptions import (
3031
BackendDistributionNotFound,
3132
BlockNotFound,
@@ -438,12 +439,18 @@ def get_base_fee(self, block_number='latest'):
438439
#
439440
@to_dict
440441
def _normalize_transaction(self, transaction, block_number='latest'):
441-
is_dynamic_fee_transaction = 'gas_price' not in transaction # default to dynamic fee txn
442+
base_fee = self.get_base_fee(block_number)
443+
444+
is_dynamic_fee_transaction = (
445+
any(_ in transaction for _ in EIP_1559_TRANSACTION_PARAMS)
446+
or not any(_ in transaction for _ in EIP_1559_TRANSACTION_PARAMS + ('gas_price',))
447+
)
442448

443449
for key in transaction:
444-
if key == 'from':
450+
if key in ('from', 'type'):
445451
continue
446452
yield key, transaction[key]
453+
447454
if 'nonce' not in transaction:
448455
yield 'nonce', self.get_nonce(transaction['from'], block_number)
449456
if 'data' not in transaction:
@@ -454,11 +461,10 @@ def _normalize_transaction(self, transaction, block_number='latest'):
454461
yield 'to', b''
455462

456463
if is_dynamic_fee_transaction:
457-
if not any(_ in transaction for _ in ('max_fee_per_gas', 'max_priority_fee_per_gas')):
464+
if not any(_ in transaction for _ in EIP_1559_TRANSACTION_PARAMS):
458465
yield 'max_fee_per_gas', 1 * 10**9
459466
yield 'max_priority_fee_per_gas', 1 * 10**9
460467
elif 'max_priority_fee_per_gas' in transaction and 'max_fee_per_gas' not in transaction:
461-
base_fee = self.get_base_fee(block_number)
462468
yield 'max_fee_per_gas', transaction['max_priority_fee_per_gas'] + 2 * base_fee
463469

464470
if is_dynamic_fee_transaction or 'access_list' in transaction:

eth_tester/backends/pyevm/serializers.py

+36-4
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,10 @@ def serialize_transaction_hash(block, transaction, transaction_index, is_pending
7171

7272

7373
def serialize_transaction(block, transaction, transaction_index, is_pending):
74+
txn_type = _extract_transaction_type(transaction)
75+
7476
common_transaction_params = {
77+
"type": txn_type,
7578
"hash": transaction.hash,
7679
"nonce": transaction.nonce,
7780
"block_hash": None if is_pending else block.hash,
@@ -91,7 +94,7 @@ def serialize_transaction(block, transaction, transaction_index, is_pending):
9194
type_specific_params = {
9295
'chain_id': transaction.chain_id,
9396
'gas_price': transaction.gas_price,
94-
'access_list': transaction.access_list or [],
97+
'access_list': transaction.access_list or (),
9598
'y_parity': transaction.y_parity,
9699
}
97100
else:
@@ -108,11 +111,17 @@ def serialize_transaction(block, transaction, transaction_index, is_pending):
108111
'chain_id': transaction.chain_id,
109112
'max_fee_per_gas': transaction.max_fee_per_gas,
110113
'max_priority_fee_per_gas': transaction.max_priority_fee_per_gas,
111-
'access_list': transaction.access_list or [],
114+
'access_list': transaction.access_list or (),
112115
'y_parity': transaction.y_parity,
116+
117+
# TODO: Sometime in early 2022 (the merge?), the inclusion of gas_price will be removed
118+
# from dynamic fee transactions and we can get rid of this behavior.
119+
# https://github.com/ethereum/execution-specs/pull/251
120+
'gas_price': _calculate_effective_gas_price(transaction, block, txn_type),
113121
}
114122
else:
115123
raise ValidationError('Transaction serialization error')
124+
116125
return merge(common_transaction_params, type_specific_params)
117126

118127

@@ -128,7 +137,7 @@ def _field_in_transaction(transaction, field):
128137
# all legacy transactions inherit from BaseTransaction
129138
return field in transaction.as_dict()
130139
elif isinstance(transaction, TypedTransaction):
131-
# all typed transaction inherit from TypedTransaction
140+
# all typed transactions inherit from TypedTransaction
132141
return hasattr(transaction, field)
133142

134143

@@ -140,6 +149,7 @@ def serialize_transaction_receipt(
140149
is_pending
141150
):
142151
receipt = receipts[transaction_index]
152+
_txn_type = _extract_transaction_type(transaction)
143153

144154
if transaction.to == b'':
145155
contract_addr = generate_contract_address(
@@ -153,20 +163,21 @@ def serialize_transaction_receipt(
153163
origin_gas = 0
154164
else:
155165
origin_gas = receipts[transaction_index - 1].gas_used
156-
157166
return {
158167
"transaction_hash": transaction.hash,
159168
"transaction_index": None if is_pending else transaction_index,
160169
"block_number": None if is_pending else block.number,
161170
"block_hash": None if is_pending else block.hash,
162171
"cumulative_gas_used": receipt.gas_used,
163172
"gas_used": receipt.gas_used - origin_gas,
173+
"effective_gas_price": _calculate_effective_gas_price(transaction, block, _txn_type),
164174
"contract_address": contract_addr,
165175
"logs": [
166176
serialize_log(block, transaction, transaction_index, log, log_index, is_pending)
167177
for log_index, log in enumerate(receipt.logs)
168178
],
169179
'state_root': receipt.state_root,
180+
'type': _txn_type,
170181
}
171182

172183

@@ -182,3 +193,24 @@ def serialize_log(block, transaction, transaction_index, log, log_index, is_pend
182193
"data": log.data,
183194
"topics": [int_to_32byte_big_endian(topic) for topic in log.topics],
184195
}
196+
197+
198+
def _extract_transaction_type(transaction):
199+
if isinstance(transaction, TypedTransaction):
200+
try:
201+
transaction.gas_price # noqa: 201
202+
return '0x1'
203+
except AttributeError:
204+
return '0x2'
205+
return '0x0' # legacy transactions being '0x0' taken from current geth version v1.10.10
206+
207+
208+
def _calculate_effective_gas_price(transaction, block, transaction_type):
209+
return (
210+
min(
211+
transaction.max_fee_per_gas,
212+
transaction.max_priority_fee_per_gas + block.header.base_fee_per_gas
213+
)
214+
if transaction_type == '0x2'
215+
else transaction.gas_price
216+
)

eth_tester/constants.py

+5
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,8 @@
5454
SECPK1_Gx = 55066263022277343669578718895168534326250603453777594175500187360389116729240
5555
SECPK1_Gy = 32670510020758816978083085130507043184471273380659243275938904335757337482424
5656
SECPK1_G = (SECPK1_Gx, SECPK1_Gy)
57+
58+
#
59+
# EIP CONSTANTS
60+
#
61+
EIP_1559_TRANSACTION_PARAMS = ('max_fee_per_gas', 'max_priority_fee_per_gas')

eth_tester/main.py

+13
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,26 @@ def func_wrapper(self, *args, **kwargs):
7171
try:
7272
transaction_hash = func(self, *args, **kwargs)
7373
pending_transaction = self.get_transaction_by_hash(transaction_hash)
74+
pending_transaction = _clean_pending_transaction_for_sending(pending_transaction)
7475
# Remove any pending transactions with the same nonce
7576
self._pending_transactions = remove_matching_transaction_from_list(
7677
self._pending_transactions, pending_transaction)
7778
self._pending_transactions.append(pending_transaction)
7879
finally:
7980
self.revert_to_snapshot(snapshot)
8081
return transaction_hash
82+
83+
def _clean_pending_transaction_for_sending(pending_transaction):
84+
cleaned_transaction = dissoc(pending_transaction, 'type')
85+
86+
# TODO: Sometime in early 2022 (the merge?), the inclusion of gas_price will be removed
87+
# from dynamic fee transactions and we can get rid of this behavior.
88+
# https://github.com/ethereum/execution-specs/pull/251
89+
if 'gas_price' and 'max_fee_per_gas' in pending_transaction:
90+
cleaned_transaction = dissoc(cleaned_transaction, 'gas_price')
91+
92+
return cleaned_transaction
93+
8194
return func_wrapper
8295

8396

0 commit comments

Comments
 (0)