Skip to content

Commit

Permalink
London support final touches:
Browse files Browse the repository at this point in the history
- changes from last PR review with @kclowes
- changes from self PR review comments
- small README fix
- add effective_gas_price and type to transaction receipt object
- add tests for calculating effective_gas_price and checking type in txn receipts
- update README.md example displaying the new fields
- update the effective gas price calculation + validation + transaction param cleanup
- update CHANGELOG with London changes overview
  • Loading branch information
fselmo committed Nov 3, 2021
1 parent fa99e9d commit 8d18f65
Show file tree
Hide file tree
Showing 20 changed files with 517 additions and 240 deletions.
17 changes: 13 additions & 4 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,23 +1,32 @@
Unreleased
v0.6.0-beta.1
-------------

Unreleased

- Features

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

- Misc

- Adjust wording in README regarding genesis parameters


v0.5.0-beta.4
-------------

Released 2021-04-12

- Features

- Upgrade py-evm to v0.4.0-alpha.4 for Python 3.9 support
https://github.com/ethereum/eth-tester/pull/205
- Upgrade py-evm to v0.4.0-alpha.4 for Python 3.9 support
https://github.com/ethereum/eth-tester/pull/205
- Upgrade py-evm to v0.4.0-alpha.3, for Berlin support
Default to Berlin
https://github.com/ethereum/eth-tester/pull/204
Expand Down
58 changes: 28 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ pip install eth-tester
>>> t.send_transaction({
... 'from': '0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf',
... 'to': '0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF',
... 'gas': 30000,,
... 'gas': 30000,
... 'value': 1,
... 'max_fee_per_gas': 1000000000,
... 'max_priority_fee_per_gas': 1000000000,
Expand All @@ -60,9 +60,10 @@ pip install eth-tester
'0xc20b90af87bc65c3d748cf0a1fa54f3a86ffc94348e0fd91a70f1c5ba6ef4109'

>>> t.get_transaction_by_hash('0xc20b90af87bc65c3d748cf0a1fa54f3a86ffc94348e0fd91a70f1c5ba6ef4109')
{'hash': '0xc20b90af87bc65c3d748cf0a1fa54f3a86ffc94348e0fd91a70f1c5ba6ef4109',
{'type': '0x2',
'hash': '0xc20b90af87bc65c3d748cf0a1fa54f3a86ffc94348e0fd91a70f1c5ba6ef4109',
'nonce': 0,
'block_hash': '0xd481955268d1f3db58ee61685a899a35e33e8fd35b9cc0812f85b9f06757140e',
'block_hash': '0x28b95514984b0abbd91d88f1a542eaeeb810c24e0234e09891b7d6b3f94f47ed',
'block_number': 1,
'transaction_index': 0,
'from': '0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf',
Expand All @@ -80,19 +81,21 @@ pip install eth-tester
'0x0000000000000000000000000000000000000000000000000000000000000007')},
{'address': '0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413',
'storage_keys': ()}),
'y_parity': 0}
'y_parity': 0,
'gas_price': 1000000000}



>>> t.get_transaction_receipt('0x86acbf39865cd2fe86db7203742d2652bc1b58b10a3996befe1ee81738f1f58e')
>>> t.get_transaction_receipt('0xc20b90af87bc65c3d748cf0a1fa54f3a86ffc94348e0fd91a70f1c5ba6ef4109')
{'transaction_hash': '0xc20b90af87bc65c3d748cf0a1fa54f3a86ffc94348e0fd91a70f1c5ba6ef4109',
'transaction_index': 0,
'block_number': 1,
'block_hash': '0xd481955268d1f3db58ee61685a899a35e33e8fd35b9cc0812f85b9f06757140e',
'block_hash': '0x28b95514984b0abbd91d88f1a542eaeeb810c24e0234e09891b7d6b3f94f47ed',
'cumulative_gas_used': 29600,
'gas_used': 29600,
'effective_gas_price': 1000000000,
'contract_address': None,
'logs': (),
'type': '0x2',
'status': 1}
```

Expand Down Expand Up @@ -396,25 +399,26 @@ Returns the transaction for the given hash, raising a
transaction cannot be found.

```python
>>> t.get_transaction_by_hash('0x140c1da1370a908e4c0f7c6e33bb97182011707c6a9aff954bef1084c8a48b25')
{'hash': '0x256baca0284f7b76238e56964dec01dce423cb11415e5f47b341a0af088bb85d',
'nonce': 1,
'block_hash': '0xc25d844e866b4f3f3126e5d5ceba949a7ee12b597a3d54115da63e8596531f58',
'block_number': 2,
>>> t.get_transaction_by_hash('0x21ae665f707e12a5f1bb13ef8c706b65cc5accfd03e7067ce683d831f51122e6')
{'type': '0x2',
'hash': '0x21ae665f707e12a5f1bb13ef8c706b65cc5accfd03e7067ce683d831f51122e6',
'nonce': 0,
'block_hash': '0x810731efeb7498fc0ac3bc7c72a71571b672c9fdbfbfd8b435f483e368e8ef7e',
'block_number': 1,
'transaction_index': 0,
'from': '0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf',
'to': '0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF',
'value': 1,
'gas': 30000,
'from': '0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF',
'to': '0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf',
'value': 1337,
'gas': 21000,
'data': '0x',
'r': 113110058401990022576610285176909677614233866647576678068104192469544316041227,
's': 56040876284016779278390826082806410689817968450346221730224961588578254081442,
'r': 1713666669454033023988006960017431058214051587080823768269189498559514600280,
's': 32003859822305799628524852194521134173285969678963273753063458725692016415033,
'chain_id': 131277322940537,
'max_fee_per_gas': 1000000000,
'max_priority_fee_per_gas': 1000000000,
'max_fee_per_gas': 2000000000,
'max_priority_fee_per_gas': 500000000,
'access_list': (),
'y_parity': 1}

'y_parity': 0,
'gas_price': 1375000000}
```

> Note: For unmined transaction, `transaction_index`, `block_number` and `block_hash` will all be `None`.
Expand Down Expand Up @@ -537,20 +541,20 @@ values.
* `gas`: Sets the gas limit for transaction execution (integer).
* `value`: The amount of ether in wei that should be sent with the transaction (integer).
* `data`: The data for the transaction (hexadecimal string).
* `chain_id`: The integer id for the chain the transaction is meant to interact with.


In addition to the above, the following parameters are added based on the type of transaction being sent:

#### Legacy transactions
* `gas_price`: Sets the price per unit of gas in wei that will be paid for transaction execution (integer).

#### Access list transactions (EIP-2930)
* `chain_id`: The integer id for the chain the transaction is meant to interact with
* `gas_price`: Sets the price per unit of gas in wei that will be paid for transaction execution (integer).
* `access_list` (optional): Specifies accounts and storage slots expected to be accessed, based on the transaction, in order to
gain a discount on the gas for those executions (see quickstart example for usage).

#### Dynamic fee transactions (EIP-1559)
* `chain_id`: The integer id for the chain the transaction is meant to interact with
* `max_fee_per_gas`: Sets the maximum fee per unit of gas in wei that will be paid for transaction execution (integer).
* `max_priority_fee_per_gas`: Sets the fee per unit of gas in wei that is sent to the miner as an incentive for mining the transaction (integer).
* `access_list` (optional): Specifies accounts and storage slots expected to be accessed, based on the transaction, in order to
Expand Down Expand Up @@ -824,20 +828,15 @@ when initializing a backend. Only default values can be overridden or a `ValueE
# Default Genesis Parameters
default_genesis_params = {
"bloom": 0,
"coinbase": GENESIS_COINBASE,
"difficulty": GENESIS_DIFFICULTY,
"extra_data": GENESIS_EXTRA_DATA,
"gas_limit": GENESIS_GAS_LIMIT,
"gas_used": 0,
"mix_hash": GENESIS_MIX_HASH,
"nonce": GENESIS_NONCE,
"block_number": GENESIS_BLOCK_NUMBER,
"parent_hash": GENESIS_PARENT_HASH,
"receipt_root": BLANK_ROOT_HASH,
"timestamp": int(time.time()),
"transaction_root": BLANK_ROOT_HASH,
"uncles_hash": EMPTY_RLP_LIST_HASH
}
```

Expand All @@ -862,7 +861,6 @@ to `PyEVM.generate_genesis_params`.
# "receipt_root": BLANK_ROOT_HASH,
# "timestamp": int(time.time()),
# "transaction_root": BLANK_ROOT_HASH,
# "base_fee_per_gas": 1000000000,
# }
```

Expand Down
9 changes: 9 additions & 0 deletions eth_tester/backends/mock/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
def calculate_effective_gas_price(transaction, block):
return (
min(
transaction['max_fee_per_gas'],
transaction['max_priority_fee_per_gas'] + block['base_fee_per_gas']
)
if 'max_fee_per_gas' in transaction
else transaction['gas_price']
)
106 changes: 54 additions & 52 deletions eth_tester/backends/mock/factory.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import functools
import time

from eth_tester.backends.mock.common import (
calculate_effective_gas_price,
)
from eth_tester.utils.transactions import (
extract_transaction_type,
)
from eth_utils import (
apply_to_return_value,
is_bytes,
Expand All @@ -20,6 +26,7 @@
)

from eth_tester.backends.common import merge_genesis_overrides
from eth_tester.constants import DYNAMIC_FEE_TRANSACTION_PARAMS
from eth_tester.utils.address import (
generate_contract_address,
)
Expand Down Expand Up @@ -88,55 +95,53 @@ def create_transaction(transaction, block, transaction_index, is_pending, overri

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

is_dynamic_fee_transaction = (
'gas_price' not in transaction and any(
_ in transaction for _ in ('max_fee_per_gas', 'max_priority_fee_per_gas')
)
any(_ in transaction for _ in DYNAMIC_FEE_TRANSACTION_PARAMS)
or not any(_ in transaction for _ in DYNAMIC_FEE_TRANSACTION_PARAMS + ('gas_price',))
)

if 'hash' in overrides:
if overrides is None:
overrides = {}

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

# 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
# Here, we yield the key with the overrides value if it exists, else either the transaction
# value if it 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 'gas', overrides.get('gas', transaction.get('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:
# if no gas_price specified, default to dynamic fee txn parameters
is_dynamic_fee_transaction = True
yield 'r', overrides.get('r', transaction.get('r', 12345))
yield 's', overrides.get('s', transaction.get('s', 67890))

if is_dynamic_fee_transaction:
# dynamic fee transaction (type = 2)
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)
)
yield from _yield_typed_transaction_fields(overrides, transaction)

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 'v', overrides.get('v', transaction.get('v', 27))
yield 'gas_price', overrides.get('gas_price', transaction.get('gas_price'))
if 'access_list' in transaction:
# access list transaction (type = 1)
yield from _yield_typed_transaction_fields(overrides, transaction)

yield 'r', overrides.get('r', transaction.get('r', 12345))
yield 's', overrides.get('s', transaction.get('s', 67890))
else:
# legacy transaction
yield 'v', overrides.get('v', transaction.get('v', 27))


def _yield_typed_transaction_fields(overrides, 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))


@to_dict
Expand Down Expand Up @@ -192,32 +197,29 @@ def make_receipt(transaction, block, _transaction_index, overrides=None):
if overrides is None:
overrides = {}

if 'gas_used' in overrides:
gas_used = overrides['gas_used']
else:
gas_used = 21000
gas_used = overrides.get('gas_used', 21000)
yield 'gas_used', gas_used

if 'cumulative_gas_used' in overrides:
yield 'cumulative_gas_used', overrides['cumulative_gas_used']
else:
yield 'cumulative_gas_used', block['gas_used'] + gas_used

if 'contract_address' in overrides:
yield 'contract_address', overrides['contract_address']
else:
contract_address = generate_contract_address(transaction['from'], transaction['nonce'])
yield 'contract_address', contract_address

if 'logs' in overrides:
yield 'logs', overrides['logs']
else:
yield 'logs', []

if 'transaction_hash' in overrides:
yield 'transaction_hash', overrides['transaction_hash']
else:
yield 'transaction_hash', transaction['hash']
yield 'logs', overrides.get('logs', [])
yield 'transaction_hash', overrides.get('transaction_hash', transaction.get('hash'))
yield (
'cumulative_gas_used',
overrides.get('cumulative_gas_used', block.get('gas_used') + gas_used)
)
yield (
'effective_gas_price',
overrides.get('effective_gas_price', calculate_effective_gas_price(transaction, block))
)
yield (
'type',
overrides.get('type', transaction.get('type', extract_transaction_type(transaction)))
)
yield (
'contract_address',
overrides.get(
'contract_address',
generate_contract_address(transaction['from'], transaction['nonce'])
)
)


GENESIS_NONCE = b'\x00\x00\x00\x00\x00\x00\x00*' # 42 encoded as big-endian-integer
Expand Down
4 changes: 3 additions & 1 deletion eth_tester/backends/mock/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,9 +246,10 @@ def get_transaction_receipt(self, transaction_hash):
raise TransactionNotFound(
f"No transaction found for hash: {transaction_hash}"
)
_, block, transaction_index = self._get_transaction_by_hash(transaction_hash)
transaction, block, transaction_index = self._get_transaction_by_hash(transaction_hash)
return serialize_receipt(
receipt,
transaction,
block,
transaction_index,
is_pending=(block['number'] == self.block['number']),
Expand Down Expand Up @@ -284,6 +285,7 @@ def send_raw_transaction(self, raw_transaction):
'from': _generate_dummy_address(0),
'hash': transaction_hash,
'gas': 21000,
'gas_price': 1000000000,
}
return self.send_transaction(transaction)

Expand Down
Loading

0 comments on commit 8d18f65

Please sign in to comment.