Skip to content

Commit

Permalink
perf: avoiding needing to get tx rpc
Browse files Browse the repository at this point in the history
  • Loading branch information
antazoey committed Jul 31, 2024
1 parent d939e8e commit 8636cf6
Show file tree
Hide file tree
Showing 7 changed files with 54 additions and 38 deletions.
6 changes: 3 additions & 3 deletions src/ape/contracts/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1131,7 +1131,7 @@ def get_event_by_signature(self, signature: str) -> ContractEvent:
:class:`~ape.contracts.base.ContractEvent`
"""

name_from_sig = signature.split("(")[0].strip()
name_from_sig = signature.partition("(")[0].strip()
options = self._events_.get(name_from_sig, [])

err = ContractDataError(f"No event found with signature '{signature}'.")
Expand All @@ -1156,7 +1156,7 @@ def get_error_by_signature(self, signature: str) -> type[CustomError]:
:class:`~ape.exceptions.CustomError`
"""

name_from_sig = signature.split("(")[0].strip()
name_from_sig = signature.partition("(")[0].strip()
options = self._errors_.get(name_from_sig, [])
err = ContractDataError(f"No error found with signature '{signature}'.")
if not options:
Expand Down Expand Up @@ -1604,7 +1604,7 @@ def _get_name(cc: ContractContainer) -> str:
return contract

elif "." in search_name:
next_node = search_name.split(".")[0]
next_node = search_name.partition(".")[0]
if next_node != item:
continue

Expand Down
4 changes: 2 additions & 2 deletions src/ape/utils/abi.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ def _decode(
return values

elif has_array_of_tuples_return:
item_type_str = str(_types[0].type).split("[")[0]
item_type_str = str(_types[0].type).partition("[")[0]
data = {
**_types[0].model_dump(),
"type": item_type_str,
Expand All @@ -179,7 +179,7 @@ def _decode(
else:
for output_type, value in zip(_types, values):
if isinstance(value, (tuple, list)):
item_type_str = str(output_type.type).split("[")[0]
item_type_str = str(output_type.type).partition("[")[0]
if item_type_str == "tuple":
# Either an array of structs or nested structs.
item_type_data = {
Expand Down
2 changes: 1 addition & 1 deletion src/ape/utils/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ def get_package_version(obj: Any) -> str:

# Reduce module string to base package
# NOTE: Assumed that string input is module name e.g. `__name__`
pkg_name = obj.split(".")[0]
pkg_name = obj.partition(".")[0]

# NOTE: In case the distribution and package name differ
dists = _get_distributions(pkg_name)
Expand Down
1 change: 0 additions & 1 deletion src/ape_ethereum/ecosystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -1212,7 +1212,6 @@ def _enrich_calldata(
contract_type: ContractType,
**kwargs,
) -> dict:
breakpoint()
calldata = call["calldata"]
if isinstance(calldata, (str, bytes, int)):
calldata_arg = HexBytes(calldata)
Expand Down
54 changes: 36 additions & 18 deletions src/ape_ethereum/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -592,17 +592,31 @@ def get_receipt(
ecosystem_config = self.network.ecosystem_config.model_dump(by_alias=True)
network_config: dict = ecosystem_config.get(self.network.name, {})
max_retries = network_config.get("max_get_transaction_retries", DEFAULT_MAX_RETRIES_TX)
txn = {}
for attempt in range(max_retries):
try:
txn = dict(self.web3.eth.get_transaction(HexStr(txn_hash)))
break
except TransactionNotFound:
if attempt < max_retries - 1: # if this wasn't the last attempt
time.sleep(1) # Wait for 1 second before retrying.
continue # Continue to the next iteration, effectively retrying the operation.
else: # if it was the last attempt
raise # Re-raise the last exception.

if transaction := kwargs.get("transaction"):
# perf: If called `send_transaction()`, we should already have the data!
txn = (
transaction
if isinstance(transaction, dict)
else transaction.model_dump(by_alias=True, mode="json")
)

else:
txn = {}
for attempt in range(max_retries):
try:
txn = dict(self.web3.eth.get_transaction(HexStr(txn_hash)))
break

except TransactionNotFound:
if attempt < max_retries - 1:
# Not the last attempt. Wait and then retry.
time.sleep(0.5)
continue

else:
# It was the last attempt - raise the exception as-is.
raise

data = {"required_confirmations": required_confirmations, **txn, **receipt_data}
receipt = self._create_receipt(**data)
Expand Down Expand Up @@ -917,6 +931,7 @@ def prepare_transaction(self, txn: TransactionAPI) -> TransactionAPI:

def send_transaction(self, txn: TransactionAPI) -> ReceiptAPI:
vm_err = None
txn_data = None
try:
if txn.signature or not txn.sender:
txn_hash = self.web3.eth.send_raw_transaction(txn.serialize_transaction()).hex()
Expand All @@ -925,7 +940,7 @@ def send_transaction(self, txn: TransactionAPI) -> ReceiptAPI:
self.chain_manager.provider.unlock_account(txn.sender)

# NOTE: Using JSON mode since used as request data.
txn_data = cast(TxParams, txn.model_dump(by_alias=True, mode="json"))
txn_data = txn_data or cast(TxParams, txn.model_dump(by_alias=True, mode="json"))
txn_hash = self.web3.eth.send_transaction(txn_data).hex()

except (ValueError, Web3ContractLogicError) as err:
Expand All @@ -940,26 +955,30 @@ def send_transaction(self, txn: TransactionAPI) -> ReceiptAPI:
if txn.required_confirmations is not None
else self.network.required_confirmations
)
txn_dict = txn.model_dump(by_alias=True, mode="json")
txn_data = txn_data or cast(TxParams, txn.model_dump(by_alias=True, mode="json"))
if vm_err:
receipt = self._create_receipt(
block_number=-1, # Not in a block.
error=vm_err,
required_confirmations=required_confirmations,
status=TransactionStatusEnum.FAILING,
txn_hash=txn_hash,
**txn_dict,
**txn_data,
)
else:
receipt = self.get_receipt(txn_hash, required_confirmations=required_confirmations)
receipt = self.get_receipt(
txn_hash,
required_confirmations=required_confirmations,
transaction=txn_data,
)

# NOTE: Ensure to cache even the failed receipts.
# NOTE: Caching must happen before error enrichment.
self.chain_manager.history.append(receipt)

if receipt.failed:
# NOTE: Using JSON mode since used as request data.
txn_params = cast(TxParams, txn_dict)
txn_params = cast(TxParams, txn_data)

# Replay txn to get revert reason
try:
Expand Down Expand Up @@ -1297,10 +1316,9 @@ def _complete_connect(self):
self.concurrency = 32
self.block_page_size = 50_000
else:
client_name = client_version.split("/")[0]
client_name = client_version.partition("/")[0]
logger.info(f"Connecting to a '{client_name}' node.")

self.web3.middleware_onion.clear()
if not self.network.is_dev:
self.web3.eth.set_gas_price_strategy(rpc_gas_price_strategy)

Expand Down
15 changes: 5 additions & 10 deletions src/ape_test/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ def connect(self):
del self.__dict__["tester"]

self._web3 = Web3(self.tester)
self._web3.middleware_onion.clear()
# Handle disabling auto-mine if the user requested via config.
if self.config.provider.auto_mine is False:
self.auto_mine = False # type: ignore[misc]
Expand Down Expand Up @@ -237,6 +236,7 @@ def send_call(

def send_transaction(self, txn: TransactionAPI) -> ReceiptAPI:
vm_err = None
txn_dict = None
try:
txn_hash = self.tester.ethereum_tester.send_raw_transaction(
txn.serialize_transaction().hex()
Expand All @@ -254,22 +254,17 @@ def send_transaction(self, txn: TransactionAPI) -> ReceiptAPI:
required_confirmations=required_confirmations, error=vm_err, txn_hash=txn_hash
)
else:
receipt_data = self.tester.ethereum_tester.get_transaction_receipt(txn_hash)
receipt = self._create_receipt(
error=vm_err,
txn_hash=txn_hash,
required_confirmations=required_confirmations,
**receipt_data,
txn_dict = txn_dict or txn.model_dump(mode="json")
receipt = self.get_receipt(
txn_hash, required_confirmations=required_confirmations, transaction=txn_dict
)
if required_confirmations > 0:
receipt.await_confirmations()

# NOTE: Caching must happen before error enrichment.
self.chain_manager.history.append(receipt)

if receipt.failed:
# NOTE: Using JSON mode since used as request data.
txn_dict = txn.model_dump(mode="json")
txn_dict = txn_dict or txn.model_dump(mode="json")

txn_dict["nonce"] += 1
txn_params = cast(TxParams, txn_dict)
Expand Down
10 changes: 7 additions & 3 deletions tests/functional/test_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from eth_tester.exceptions import TransactionFailed # type: ignore
from eth_typing import HexStr
from eth_utils import ValidationError
from hexbytes import HexBytes
from requests import HTTPError
from web3.exceptions import ContractPanicError

Expand Down Expand Up @@ -297,8 +298,10 @@ def test_no_comma_in_rpc_url():


def test_send_transaction_when_no_error_and_receipt_fails(
mocker, mock_transaction, eth_tester_provider, owner, vyper_contract_instance
mocker, mock_web3, mock_transaction, eth_tester_provider, owner, vyper_contract_instance
):
start_web3 = eth_tester_provider._web3
eth_tester_provider._web3 = mock_web3
mock_eth_tester = mocker.MagicMock()
original_tester = eth_tester_provider.tester
eth_tester_provider.__dict__["tester"] = mock_eth_tester
Expand All @@ -322,16 +325,17 @@ def test_send_transaction_when_no_error_and_receipt_fails(
"gasUsed": 123,
"gasLimit": 100,
}
mock_eth_tester.ethereum_tester.get_transaction_receipt.return_value = receipt_data
mock_web3.eth.wait_for_transaction_receipt.return_value = receipt_data

# Attempting to replay the tx does not produce any error.
# mock_web3.eth.call.return_value = HexBytes("")
mock_web3.eth.call.return_value = HexBytes("")

# Execute test.
with pytest.raises(TransactionError):
eth_tester_provider.send_transaction(mock_transaction)

finally:
eth_tester_provider._web3 = start_web3
eth_tester_provider.__dict__["tester"] = original_tester


Expand Down

0 comments on commit 8636cf6

Please sign in to comment.