Skip to content

Commit

Permalink
1559: fixture, maxFeePerGas, maxPriorityFeePerGas (#2033)
Browse files Browse the repository at this point in the history
  • Loading branch information
wolovim authored and fselmo committed Jul 8, 2021
1 parent 5efab0f commit 31668c4
Show file tree
Hide file tree
Showing 14 changed files with 555 additions and 29 deletions.
44 changes: 39 additions & 5 deletions docs/web3.eth.rst
Original file line number Diff line number Diff line change
Expand Up @@ -729,13 +729,20 @@ The following methods are available on the ``web3.eth`` namespace.
The ``transaction`` parameter should be a dictionary with the following fields.

* ``from``: ``bytes or text``, checksum address or ENS name - (optional, default:
``web3.eth.defaultAccount``) The address the transaction is send from.
``web3.eth.defaultAccount``) The address the transaction is sent from.
* ``to``: ``bytes or text``, checksum address or ENS name - (optional when creating new
contract) The address the transaction is directed to.
* ``gas``: ``integer`` - (optional, default: 90000) Integer of the gas
* ``gas``: ``integer`` - (optional) Integer of the gas
provided for the transaction execution. It will return unused gas.
* ``gasPrice``: ``integer`` - (optional, default: To-Be-Determined) Integer
of the gasPrice used for each paid gas
* ``maxFeePerGas``: ``integer or hex`` - (optional) maximum amount you're willing
to pay, inclusive of ``baseFeePerGas`` and ``maxPriorityFeePerGas``. The difference
between ``maxFeePerGas`` and ``baseFeePerGas + maxPriorityFeePerGas`` is refunded
to the user.
* ``maxPriorityFeePerGas``: ``integer or hex`` - (optional) the part of the fee
that goes to the miner
* ``gasPrice``: ``integer`` - Integer of the gasPrice used for each paid gas
**LEGACY** - unless you have good reason to, use ``maxFeePerGas``
and ``maxPriorityFeePerGas`` instead.
* ``value``: ``integer`` - (optional) Integer of the value send with this
transaction
* ``data``: ``bytes or text`` - The compiled code of a contract OR the hash
Expand All @@ -754,7 +761,34 @@ The following methods are available on the ``web3.eth`` namespace.

.. code-block:: python
>>> web3.eth.send_transaction({'to': '0xd3CdA913deB6f67967B99D67aCDFa1712C293601', 'from': web3.eth.coinbase, 'value': 12345})
# simple example (Web3.py determines gas and fee)
>>> web3.eth.send_transaction({
'to': '0xd3CdA913deB6f67967B99D67aCDFa1712C293601',
'from': web3.eth.coinbase,
'value': 12345
})
# EIP 1559-style transaction
HexBytes('0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331')
>>> web3.eth.send_transaction({
'to': '0xd3CdA913deB6f67967B99D67aCDFa1712C293601',
'from': web3.eth.coinbase,
'value': 12345,
'gas': 21000,
'maxFeePerGas': web3.toWei(250, 'gwei'),
'maxPriorityFeePerGas': web3.toWei(2, 'gwei'),
})
HexBytes('0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331')
# Legacy transaction (less efficient)
HexBytes('0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331')
>>> web3.eth.send_transaction({
'to': '0xd3CdA913deB6f67967B99D67aCDFa1712C293601',
'from': web3.eth.coinbase,
'value': 12345,
'gas': 21000,
'gasPrice': web3.toWei(50, 'gwei'),
})
HexBytes('0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331')
.. py:method:: Eth.sendTransaction(transaction)
Expand Down
1 change: 1 addition & 0 deletions newsfragments/2033.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Adds support for EIP 1559 transaction keys: `maxFeePerGas` and `maxPriorityFeePerGas`
1 change: 1 addition & 0 deletions tests/core/middleware/test_gas_price_strategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ def the_gas_price_strategy_middleware(web3):
return initialized


@pytest.mark.skip(reason="London TODO: generate_gas_price updates")
def test_gas_price_generated(the_gas_price_strategy_middleware):
the_gas_price_strategy_middleware.web3.eth.generate_gas_price.return_value = 5
method = 'eth_sendTransaction'
Expand Down
250 changes: 250 additions & 0 deletions tests/integration/generate_fixtures/common_1559.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
import contextlib
import os
import shutil
import signal
import socket
import subprocess
import tempfile
import time

from eth_utils import (
is_checksum_address,
to_text,
)

from web3.exceptions import (
TransactionNotFound,
)

COINBASE = '0xdc544d1aa88ff8bbd2f2aec754b1f1e99e1812fd'
COINBASE_PK = '0x58d23b55bc9cdce1f18c2500f40ff4ab7245df9a89505e9b1fa4851f623d241d'

KEYFILE_DATA = '{"address":"dc544d1aa88ff8bbd2f2aec754b1f1e99e1812fd","crypto":{"cipher":"aes-128-ctr","ciphertext":"52e06bc9397ea9fa2f0dae8de2b3e8116e92a2ecca9ad5ff0061d1c449704e98","cipherparams":{"iv":"aa5d0a5370ef65395c1a6607af857124"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"9fdf0764eb3645ffc184e166537f6fe70516bf0e34dc7311dea21f100f0c9263"},"mac":"4e0b51f42b865c15c485f4faefdd1f01a38637e5247f8c75ffe6a8c0eba856f6"},"id":"5a6124e0-10f1-4c1c-ae3e-d903eacb740a","version":3}' # noqa: E501

KEYFILE_PW = 'web3py-test'
KEYFILE_FILENAME = 'UTC--2017-08-24T19-42-47.517572178Z--dc544d1aa88ff8bbd2f2aec754b1f1e99e1812fd' # noqa: E501

RAW_TXN_ACCOUNT = '0x39EEed73fb1D3855E90Cbd42f348b3D7b340aAA6'

UNLOCKABLE_PRIVATE_KEY = '0x392f63a79b1ff8774845f3fa69de4a13800a59e7083f5187f1558f0797ad0f01'
UNLOCKABLE_ACCOUNT = '0x12efdc31b1a8fa1a1e756dfd8a1601055c971e13'
UNLOCKABLE_ACCOUNT_PW = KEYFILE_PW

GENESIS_DATA = {
"config": {
"chainId": 131277322940537, # the string 'web3py' as an integer
"homesteadBlock": 0,
"byzantiumBlock": 0,
"constantinopleBlock": 0,
"eip150Block": 0,
"eip155Block": 0,
"eip158Block": 0,
"istanbulBlock": 0,
"petersburgBlock": 0,
"berlinBlock": 0,
"londonBlock": 0,
},
"nonce": "0x0000000000000042",
"alloc": {
COINBASE: {"balance": "1000000000000000000000000000"},
UNLOCKABLE_ACCOUNT: {"balance": "1000000000000000000000000000"},
RAW_TXN_ACCOUNT: {"balance": "1000000000000000000000000000"},
"0000000000000000000000000000000000000001": {"balance": "1"},
"0000000000000000000000000000000000000002": {"balance": "1"},
"0000000000000000000000000000000000000003": {"balance": "1"},
"0000000000000000000000000000000000000004": {"balance": "1"},
"0000000000000000000000000000000000000005": {"balance": "1"},
"0000000000000000000000000000000000000006": {"balance": "1"},
},
"timestamp": "0x00",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"extraData": "0x3535353535353535353535353535353535353535353535353535353535353535",
"gasLimit": "0x3b9aca00", # 1,000,000,000
"difficulty": "0x10000",
"mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"coinbase": COINBASE
}


def ensure_path_exists(dir_path):
"""
Make sure that a path exists
"""
if not os.path.exists(dir_path):
os.makedirs(dir_path)
return True
return False


@contextlib.contextmanager
def tempdir():
dir_path = tempfile.mkdtemp()
try:
yield dir_path
finally:
shutil.rmtree(dir_path)


def get_geth_binary():
from geth.install import (
get_executable_path,
install_geth,
)

if 'GETH_BINARY' in os.environ:
return os.environ['GETH_BINARY']
elif 'GETH_VERSION' in os.environ:
geth_version = os.environ['GETH_VERSION']
_geth_binary = get_executable_path(geth_version)
if not os.path.exists(_geth_binary):
install_geth(geth_version)
assert os.path.exists(_geth_binary)
return _geth_binary
else:
return 'geth'


def wait_for_popen(proc, timeout):
start = time.time()
while time.time() < start + timeout:
if proc.poll() is None:
time.sleep(0.01)
else:
break


def kill_proc_gracefully(proc):
if proc.poll() is None:
proc.send_signal(signal.SIGINT)
wait_for_popen(proc, 13)

if proc.poll() is None:
proc.terminate()
wait_for_popen(proc, 5)

if proc.poll() is None:
proc.kill()
wait_for_popen(proc, 2)


def wait_for_socket(ipc_path, timeout=30):
start = time.time()
while time.time() < start + timeout:
try:
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.connect(ipc_path)
sock.settimeout(timeout)
except (FileNotFoundError, socket.error):
time.sleep(0.01)
else:
break


@contextlib.contextmanager
def get_geth_process(geth_binary,
datadir,
genesis_file_path,
ipc_path,
port,
networkid,
skip_init=False):
if not skip_init:
init_datadir_command = (
geth_binary,
'--datadir', datadir,
'init',
genesis_file_path,
)
print(' '.join(init_datadir_command))
subprocess.check_output(
init_datadir_command,
stdin=subprocess.PIPE,
stderr=subprocess.PIPE,
)

run_geth_command = (
geth_binary,
'--datadir', datadir,
'--ipcpath', ipc_path,
'--nodiscover',
'--port', port,
'--networkid', networkid,
'--etherbase', COINBASE[2:],
)
print(' '.join(run_geth_command))
try:
proc = get_process(run_geth_command)
yield proc
finally:
kill_proc_gracefully(proc)
output, errors = proc.communicate()
print(
"Geth Process Exited:\n"
"stdout:{0}\n\n"
"stderr:{1}\n\n".format(
to_text(output),
to_text(errors),
)
)


def get_process(run_command):
proc = subprocess.Popen(
run_command,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
return proc


def mine_block(web3):
origin_block_number = web3.eth.block_number

start_time = time.time()
web3.geth.miner.start(1)
while time.time() < start_time + 120:
block_number = web3.eth.block_number
if block_number > origin_block_number:
web3.geth.miner.stop()
return block_number
else:
time.sleep(0.1)
else:
raise ValueError("No block mined during wait period")


def mine_transaction_hash(web3, txn_hash):
start_time = time.time()
web3.geth.miner.start(1)
while time.time() < start_time + 120:
try:
receipt = web3.eth.get_transaction_receipt(txn_hash)
except TransactionNotFound:
continue
if receipt is not None:
web3.geth.miner.stop()
return receipt
else:
time.sleep(0.1)
else:
raise ValueError("Math contract deploy transaction not mined during wait period")


def deploy_contract(web3, name, factory):
web3.geth.personal.unlock_account(web3.eth.coinbase, KEYFILE_PW)
deploy_txn_hash = factory.constructor().transact(
{
"from": web3.eth.coinbase,
"maxFeePerGas": hex(100000000),
"maxPriorityFeePerGas": hex(1),
"gas": 2000000,
}
)
print('{0}_CONTRACT_DEPLOY_HASH: '.format(name.upper()), deploy_txn_hash)
deploy_receipt = mine_transaction_hash(web3, deploy_txn_hash)
print('{0}_CONTRACT_DEPLOY_TRANSACTION_MINED'.format(name.upper()))
contract_address = deploy_receipt['contractAddress']
assert is_checksum_address(contract_address)
print('{0}_CONTRACT_ADDRESS:'.format(name.upper()), contract_address)
return deploy_receipt
2 changes: 1 addition & 1 deletion tests/integration/generate_fixtures/go_ethereum.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
valmap,
)

import common
import common_1559 as common
from tests.utils import (
get_open_port,
)
Expand Down
Binary file added tests/integration/geth-london-fixture.zip
Binary file not shown.
21 changes: 13 additions & 8 deletions tests/integration/go_ethereum/common.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from concurrent.futures._base import (
TimeoutError as FuturesTimeoutError,
)
# from concurrent.futures._base import (
# TimeoutError as FuturesTimeoutError,
# )
import pytest

from web3._utils.module_testing import ( # noqa: F401
Expand All @@ -20,16 +20,21 @@ def _check_web3_clientVersion(self, client_version):


class GoEthereumEthModuleTest(EthModuleTest):
@pytest.mark.xfail(
strict=False,
raises=FuturesTimeoutError,
reason='Sometimes a TimeoutError is hit when waiting for the txn to be mined',
)
# @pytest.mark.xfail(
# strict=False,
# raises=FuturesTimeoutError,
# reason='Sometimes a TimeoutError is hit when waiting for the txn to be mined',
# )
@pytest.mark.skip(reason="London TODO: crashes on [address_conversion_func1]")
def test_eth_replace_transaction_already_mined(self, web3, unlocked_account_dual_type):
web3.geth.miner.start()
super().test_eth_replace_transaction_already_mined(web3, unlocked_account_dual_type)
web3.geth.miner.stop()

@pytest.mark.skip(reason="London TODO: pending call isn't found")
def test_eth_call_old_contract_state(self, web3, math_contract, unlocked_account):
super().test_eth_call_old_contract_state(web3, math_contract, unlocked_account)

@pytest.mark.xfail(reason='eth_signTypedData has not been released in geth')
def test_eth_sign_typed_data(self, web3, unlocked_account_dual_type):
super().test_eth_sign_typed_data(web3, unlocked_account_dual_type)
Expand Down
3 changes: 2 additions & 1 deletion tests/integration/go_ethereum/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@

KEYFILE_PW = 'web3py-test'

GETH_FIXTURE_ZIP = 'geth-1.10.4-fixture.zip'
GETH_FIXTURE_ZIP = 'geth-london-fixture.zip'
# GETH_FIXTURE_ZIP = 'geth-1-10-4-fixture.zip'


@pytest.fixture(scope='module')
Expand Down
Loading

0 comments on commit 31668c4

Please sign in to comment.