Skip to content

Commit

Permalink
feat: contract creation caching
Browse files Browse the repository at this point in the history
move query to contract cache
change ots est to 250ms so it doesn't clash with etherscan.
store txn hash instead of receipt.
  • Loading branch information
banteg committed Apr 12, 2024
1 parent 3cad4cc commit 744edfe
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 14 deletions.
11 changes: 6 additions & 5 deletions src/ape/api/query.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
from functools import lru_cache
from typing import Any, Dict, Iterator, List, Optional, Sequence, Set, Type, Union

from ethpm_types.abi import BaseModel, EventABI, MethodABI
from ethpm_types.abi import EventABI, MethodABI
from pydantic import NonNegativeInt, PositiveInt, model_validator

from ape.api.transactions import ReceiptAPI, TransactionAPI
from ape.logging import logger
from ape.types import AddressType
from ape.utils import BaseInterface, BaseInterfaceModel, abstractmethod, cached_property
from ape.utils.basemodel import BaseModel

QueryType = Union[
"BlockQuery",
Expand Down Expand Up @@ -157,17 +158,17 @@ def check_start_nonce_before_stop_nonce(cls, values: Dict) -> Dict:
class ContractCreationQuery(_BaseQuery):
"""
A ``QueryType`` that obtains information about contract deployment.
Returns ``ContractCreation(receipt, deployer, factory, deploy_block)``.
Returns ``ContractCreation(txn_hash, deployer, factory, deploy_block)``.
"""

contract: AddressType


class ContractCreation(BaseModel):
receipt: ReceiptAPI
deployer: AddressType
factory: Optional[AddressType]
txn_hash: str
deploy_block: int
deployer: AddressType
factory: Optional[AddressType] = None


class ContractEventQuery(_BaseBlockQuery):
Expand Down
13 changes: 7 additions & 6 deletions src/ape/contracts/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
from ape.api.address import BaseAddress
from ape.api.query import (
ContractCreation,
ContractCreationQuery,
ContractEventQuery,
extract_fields,
validate_and_expand_columns,
Expand Down Expand Up @@ -900,18 +899,20 @@ def receipt(self) -> ReceiptAPI:
self._cached_receipt = receipt
return receipt

# Brute force find the receipt.
receipt = self.meta.receipt
# query the deployment to find the receipt
receipt = self.chain_manager.get_receipt(self.meta.txn_hash)
self._cached_receipt = receipt
return receipt

@cached_property
def meta(self) -> ContractCreation:
"""
Contract creation details: deployer, factory, block, receipt.
Contract creation details: txn_hash, deployer, factory, deploy_block.
"""
query = ContractCreationQuery(columns=["*"], contract=self.address)
return next(self.query_manager.query(query))
data = self.chain_manager.contracts.get_creation_metadata(self.address)
if data is not None:
return data
raise AssertionError("unreachable")

@log_instead_of_fail(default="<ContractInstance>")
def __repr__(self) -> str:
Expand Down
51 changes: 51 additions & 0 deletions src/ape/managers/chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
from ape.api.query import (
AccountTransactionQuery,
BlockQuery,
ContractCreation,
ContractCreationQuery,
extract_fields,
validate_and_expand_columns,
)
Expand Down Expand Up @@ -617,6 +619,7 @@ class ContractCache(BaseManager):
_local_proxies: Dict[AddressType, ProxyInfoAPI] = {}
_local_blueprints: Dict[str, ContractType] = {}
_local_deployments_mapping: Dict[str, Dict] = {}
_local_contract_creation: Dict[str, ContractCreation] = {}

# chain_id -> address -> custom_err
# Cached to prevent calling `new_class` multiple times with conflicts.
Expand Down Expand Up @@ -661,6 +664,10 @@ def _proxy_info_cache(self) -> Path:
def _blueprint_cache(self) -> Path:
return self._network_cache / "blueprints"

@property
def _contract_creation_cache(self) -> Path:
return self._network_cache / "contract_creation"

@property
def _full_deployments(self) -> Dict:
deployments = self._local_deployments_mapping
Expand Down Expand Up @@ -834,6 +841,37 @@ def get_proxy_info(self, address: AddressType) -> Optional[ProxyInfoAPI]:

return self._local_proxies.get(address) or self._get_proxy_info_from_disk(address)

def get_creation_metadata(self, address: AddressType) -> Optional[ContractCreation]:
"""
Get contract creation metadata containing txn_hash, deployer, factory, deploy_block.
Args:
address (AddressType): The address of the contract.
Returns:
Optional[:class:`~pae.api.query.ContractCreation`]
"""
creation = self._local_contract_creation.get(address)
if creation is not None:
return creation
# read from disk
creation = self._get_contract_creation_from_disk(address)
if creation is not None:
self._local_contract_creation[address] = creation
return creation
# query and cache
query = ContractCreationQuery(columns=["*"], contract=address)
try:
creation = next(self.query_manager.query(query))
except StopIteration:
creation = None
if creation is not None:
self._cache_contract_creation_to_disk(address, creation)
self._local_contract_creation[address] = creation
return creation

return None

def get_blueprint(self, blueprint_id: str) -> Optional[ContractType]:
"""
Get a cached blueprint contract type.
Expand Down Expand Up @@ -1250,6 +1288,7 @@ def clear_local_caches(self):
self._local_proxies = {}
self._local_blueprints = {}
self._local_deployments_mapping = {}
self._local_creation_metadata = {}

def _get_contract_type_from_disk(self, address: AddressType) -> Optional[ContractType]:
address_file = self._contract_types_cache / f"{address}.json"
Expand Down Expand Up @@ -1288,6 +1327,13 @@ def _get_contract_type_from_explorer(self, address: AddressType) -> Optional[Con

return contract_type

def _get_contract_creation_from_disk(self, address: AddressType) -> Optional[ContractCreation]:
path = self._contract_creation_cache / f"{address}.json"
if not path.is_file():
return None

return ContractCreation.model_validate_json(path.read_text())

def _cache_contract_to_disk(self, address: AddressType, contract_type: ContractType):
self._contract_types_cache.mkdir(exist_ok=True, parents=True)
address_file = self._contract_types_cache / f"{address}.json"
Expand All @@ -1303,6 +1349,11 @@ def _cache_blueprint_to_disk(self, blueprint_id: str, contract_type: ContractTyp
blueprint_file = self._blueprint_cache / f"{blueprint_id}.json"
blueprint_file.write_text(contract_type.model_dump_json())

def _cache_contract_creation_to_disk(self, address: AddressType, creation: ContractCreation):
self._contract_creation_cache.mkdir(exist_ok=True, parents=True)
path = self._contract_creation_cache / f"{address}.json"
path.write_text(creation.model_dump_json())

def _load_deployments_cache(self) -> Dict:
return (
json.loads(self._deployments_mapping_cache.read_text())
Expand Down
2 changes: 1 addition & 1 deletion src/ape/managers/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ def find_creation_block(lo, hi):
receipt = self.chain_manager.get_receipt(tx["transactionHash"])
creator = self.conversion_manager.convert(trace["action"]["from"], AddressType)
yield ContractCreation(
receipt=receipt,
txn_hash=tx["transactionHash"],
deployer=receipt.sender,
factory=creator if creator != receipt.sender else None,
deploy_block=deploy_block,
Expand Down
4 changes: 2 additions & 2 deletions src/ape_geth/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def perform_query(self, query: QueryType) -> Iterator: # type: ignore[override]
@estimate_query.register
def estimate_contract_creation_query(self, query: ContractCreationQuery) -> Optional[int]:
if getattr(self.provider, "_ots_api_level", None) is not None:
return 300
return 250
return None

@perform_query.register
Expand All @@ -35,7 +35,7 @@ def get_contract_creation_receipt(
creator = self.conversion_manager.convert(ots["creator"], AddressType)
receipt = self.provider.get_receipt(ots["hash"])
yield ContractCreation(
receipt=receipt,
txn_hash=ots["hash"],
deployer=receipt.sender,
factory=creator if creator != receipt.sender else None,
deploy_block=receipt.block_number,
Expand Down

0 comments on commit 744edfe

Please sign in to comment.