Skip to content

Commit

Permalink
Add async and sync buffered gas estimate middleware
Browse files Browse the repository at this point in the history
  • Loading branch information
kclowes committed Jun 4, 2021
1 parent 8265f13 commit 17264b4
Show file tree
Hide file tree
Showing 7 changed files with 244 additions and 55 deletions.
80 changes: 80 additions & 0 deletions web3/_utils/module_testing/eth_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,86 @@ def higher_gas_price_strategy(web3: "Web3", txn: TxParams) -> Wei:

assert txn['gasPrice'] == 20

@pytest.mark.asyncio
async def test_eth_estimate_gas(
self, async_w3: "Web3", unlocked_account_dual_type: ChecksumAddress
) -> None:
gas_estimate = await async_w3.async_eth.estimate_gas({
'from': unlocked_account_dual_type,
'to': unlocked_account_dual_type,
'value': Wei(1),
})
assert is_integer(gas_estimate)
assert gas_estimate > 0

@pytest.mark.asyncio
async def test_eth_getBlockByHash(
self, async_w3: "Web3", empty_block: BlockData
) -> None:
block = await async_w3.async_eth.get_block(empty_block['hash'])
assert block['hash'] == empty_block['hash']

@pytest.mark.asyncio
async def test_eth_getBlockByHash_not_found(
self, async_w3: "Web3", empty_block: BlockData
) -> None:
with pytest.raises(BlockNotFound):
await async_w3.async_eth.get_block(UNKNOWN_HASH)

@pytest.mark.asyncio
async def test_eth_getBlockByHash_pending(
self, async_w3: "Web3"
) -> None:
block = await async_w3.async_eth.get_block('pending')
assert block['hash'] is None

@pytest.mark.asyncio
async def test_eth_getBlockByNumber_with_integer(
self, async_w3: "Web3", empty_block: BlockData
) -> None:
block = await async_w3.async_eth.get_block(empty_block['number'])
assert block['number'] == empty_block['number']

@pytest.mark.asyncio
async def test_eth_getBlockByNumber_latest(
self, async_w3: "Web3", empty_block: BlockData
) -> None:
current_block_number = await async_w3.async_eth.block_number
block = await async_w3.async_eth.get_block('latest')
assert block['number'] == current_block_number

@pytest.mark.asyncio
async def test_eth_getBlockByNumber_not_found(
self, async_w3: "Web3", empty_block: BlockData
) -> None:
with pytest.raises(BlockNotFound):
await async_w3.async_eth.get_block(BlockNumber(12345))

@pytest.mark.asyncio
async def test_eth_getBlockByNumber_pending(
self, async_w3: "Web3", empty_block: BlockData
) -> None:
current_block_number = await async_w3.async_eth.block_number
block = await async_w3.async_eth.get_block('pending')
assert block['number'] == current_block_number + 1

@pytest.mark.asyncio
async def test_eth_getBlockByNumber_earliest(
self, async_w3: "Web3", empty_block: BlockData
) -> None:
genesis_block = await async_w3.async_eth.get_block(BlockNumber(0))
block = await async_w3.async_eth.get_block('earliest')
assert block['number'] == 0
assert block['hash'] == genesis_block['hash']

@pytest.mark.asyncio
async def test_eth_getBlockByNumber_full_transactions(
self, async_w3: "Web3", block_with_txn: BlockData
) -> None:
block = await async_w3.async_eth.get_block(block_with_txn['number'], True)
transaction = block['transactions'][0]
assert transaction['hash'] == block_with_txn['transactions'][0] # type: ignore


class EthModuleTest:
def test_eth_protocol_version(self, web3: "Web3") -> None:
Expand Down
37 changes: 31 additions & 6 deletions web3/_utils/transactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,21 +112,46 @@ def wait_for_transaction_receipt(
return txn_receipt


def get_block_gas_limit(web3: "Web3", block_identifier: Optional[BlockIdentifier] = None) -> Wei:
def get_block_gas_limit(web3_eth: "Web3", block_identifier: Optional[BlockIdentifier] = None) -> Wei:
if block_identifier is None:
block_identifier = web3.eth.block_number
block = web3.eth.get_block(block_identifier)
block_identifier = web3_eth.block_number
block = web3_eth.get_block(block_identifier)
return block['gasLimit']


def get_buffered_gas_estimate(
web3: "Web3", transaction: TxParams, gas_buffer: Wei = Wei(100000)
web3_eth: "Web3", transaction: TxParams, gas_buffer: Wei = Wei(100000)
) -> Wei:
gas_estimate_transaction = cast(TxParams, dict(**transaction))

gas_estimate = web3.eth.estimate_gas(gas_estimate_transaction)
gas_estimate = web3_eth.estimate_gas(gas_estimate_transaction)

gas_limit = get_block_gas_limit(web3)
gas_limit = get_block_gas_limit(web3_eth)

if gas_estimate > gas_limit:
raise ValueError(
"Contract does not appear to be deployable within the "
"current network gas limits. Estimated: {0}. Current gas "
"limit: {1}".format(gas_estimate, gas_limit)
)

return Wei(min(gas_limit, gas_estimate + gas_buffer))


async def async_get_block_gas_limit(web3_eth: "Web3", block_identifier: Optional[BlockIdentifier] = None) -> Wei:
if block_identifier is None:
block_identifier = await web3_eth.block_number
block = await web3_eth.get_block(block_identifier)
return block['gasLimit']

async def async_get_buffered_gas_estimate(
web3_eth: "Web3", transaction: TxParams, gas_buffer: Wei = Wei(100000)
) -> Wei:
gas_estimate_transaction = cast(TxParams, dict(**transaction))

gas_estimate = await web3_eth.estimate_gas(gas_estimate_transaction)

gas_limit = await async_get_block_gas_limit(web3_eth)

if gas_estimate > gas_limit:
raise ValueError(
Expand Down
125 changes: 82 additions & 43 deletions web3/eth.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,12 +122,12 @@ def send_transaction_munger(self, transaction: TxParams) -> Tuple[TxParams]:
transaction = assoc(transaction, 'from', self.default_account)

# TODO: move gas estimation in middleware
if 'gas' not in transaction:
transaction = assoc(
transaction,
'gas',
get_buffered_gas_estimate(self.web3, transaction),
)
# if 'gas' not in transaction:
# transaction = assoc(
# transaction,
# 'gas',
# get_buffered_gas_estimate(self.web3, transaction),
# )
return (transaction,)

_send_transaction: Method[Callable[[TxParams], HexBytes]] = Method(
Expand All @@ -148,6 +148,49 @@ def _generate_gas_price(self, transaction_params: Optional[TxParams] = None) ->
def set_gas_price_strategy(self, gas_price_strategy: GasPriceStrategy) -> None:
self.gasPriceStrategy = gas_price_strategy

def estimate_gas_munger(
self,
transaction: TxParams,
block_identifier: Optional[BlockIdentifier] = None
) -> Sequence[Union[TxParams, BlockIdentifier]]:
if 'from' not in transaction and is_checksum_address(self.default_account):
transaction = assoc(transaction, 'from', self.default_account)

if block_identifier is None:
params: Sequence[Union[TxParams, BlockIdentifier]] = [transaction]
else:
params = [transaction, block_identifier]

return params

_estimate_gas: Method[Callable[..., Wei]] = Method(
RPC.eth_estimateGas,
mungers=[estimate_gas_munger]
)

def get_block_munger(
self, block_identifier: BlockIdentifier, full_transactions: bool = False
) -> Tuple[BlockIdentifier, bool]:
return (block_identifier, full_transactions)

"""
`eth_getBlockByHash`
`eth_getBlockByNumber`
"""
_get_block: Method[Callable[..., BlockData]] = Method(
method_choice_depends_on_args=select_method_for_block_identifier(
if_predefined=RPC.eth_getBlockByNumber,
if_hash=RPC.eth_getBlockByHash,
if_number=RPC.eth_getBlockByNumber,
),
mungers=[get_block_munger],
)

_get_block_number: Method[Callable[[], BlockNumber]] = Method(
RPC.eth_blockNumber,
mungers=None,
)


class AsyncEth(BaseEth):
is_async = True
Expand All @@ -168,6 +211,22 @@ async def generate_gas_price(
) -> Optional[Wei]:
return self._generate_gas_price(transaction_params)

async def estimate_gas(
self,
transaction: TxParams,
block_identifier: Optional[BlockIdentifier] = None
) -> Wei:
return await self._estimate_gas(transaction, block_identifier)

async def get_block(
self, block_identifier: BlockIdentifier, full_transactions: bool = False
) -> BlockData:
return await self._get_block(block_identifier, full_transactions)

@property
async def block_number(self) -> BlockNumber:
return await self._get_block_number()


class Eth(BaseEth, Module):
account = Account()
Expand Down Expand Up @@ -259,14 +318,14 @@ def gasPrice(self) -> Wei:
def accounts(self) -> Tuple[ChecksumAddress]:
return self.get_accounts()

get_block_number: Method[Callable[[], BlockNumber]] = Method(
RPC.eth_blockNumber,
mungers=None,
)
# get_block_number: Method[Callable[[], BlockNumber]] = Method(
# RPC.eth_blockNumber,
# mungers=None,
# )

@property
def block_number(self) -> BlockNumber:
return self.get_block_number()
return self._get_block_number()

@property
def blockNumber(self) -> BlockNumber:
Expand Down Expand Up @@ -398,23 +457,10 @@ def get_proof_munger(
mungers=[block_id_munger]
)

def get_block_munger(
def get_block(
self, block_identifier: BlockIdentifier, full_transactions: bool = False
) -> Tuple[BlockIdentifier, bool]:
return (block_identifier, full_transactions)

"""
`eth_getBlockByHash`
`eth_getBlockByNumber`
"""
get_block: Method[Callable[..., BlockData]] = Method(
method_choice_depends_on_args=select_method_for_block_identifier(
if_predefined=RPC.eth_getBlockByNumber,
if_hash=RPC.eth_getBlockByHash,
if_number=RPC.eth_getBlockByNumber,
),
mungers=[get_block_munger],
)
) -> BlockData:
return self._get_block(block_identifier, full_transactions)

"""
`eth_getBlockTransactionCountByHash`
Expand Down Expand Up @@ -587,25 +633,18 @@ def call_munger(
mungers=[call_munger]
)

def estimate_gas_munger(

# estimate_gas: Method[Callable[..., Wei]] = Method(
# RPC.eth_estimateGas,
# mungers=[estimate_gas_munger]
# )

def estimate_gas(
self,
transaction: TxParams,
block_identifier: Optional[BlockIdentifier] = None
) -> Sequence[Union[TxParams, BlockIdentifier]]:
if 'from' not in transaction and is_checksum_address(self.default_account):
transaction = assoc(transaction, 'from', self.default_account)

if block_identifier is None:
params: Sequence[Union[TxParams, BlockIdentifier]] = [transaction]
else:
params = [transaction, block_identifier]

return params

estimate_gas: Method[Callable[..., Wei]] = Method(
RPC.eth_estimateGas,
mungers=[estimate_gas_munger]
)
) -> Wei:
return self._estimate_gas(transaction, block_identifier)

def filter_munger(
self,
Expand Down
7 changes: 4 additions & 3 deletions web3/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from web3.middleware import (
abi_middleware,
attrdict_middleware,
buffered_gas_estimate_middleware,
gas_price_strategy_middleware,
name_to_address_middleware,
normalize_errors_middleware,
Expand Down Expand Up @@ -118,6 +119,7 @@ def default_middlewares(
(normalize_errors_middleware, 'normalize_errors'), # Add async
(validation_middleware, 'validation'), # Add async
(abi_middleware, 'abi'), # Delete
(buffered_gas_estimate_middleware, 'gas_estimate'),
]

#
Expand Down Expand Up @@ -175,9 +177,8 @@ async def coro_request(
if "error" in response:
apply_error_formatters(error_formatters, response)
raise ValueError(response["error"])

if response['result'] is None:
raise ValueError(f"The call to {method} did not return a value.")
elif response['result'] is None:
apply_error_formatters(error_formatters, response, params)

return response['result']

Expand Down
4 changes: 3 additions & 1 deletion web3/middleware/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,10 @@
construct_formatting_middleware,
)
from .gas_price_strategy import ( # noqa: F401
gas_price_strategy_middleware,
async_gas_price_strategy_middleware,
async_buffered_gas_estimate_middleware,
buffered_gas_estimate_middleware,
gas_price_strategy_middleware,
)
from .geth_poa import ( # noqa: F401
geth_poa_middleware,
Expand Down
Loading

0 comments on commit 17264b4

Please sign in to comment.