Skip to content

Commit

Permalink
fix: reog
Browse files Browse the repository at this point in the history
  • Loading branch information
antazoey committed Nov 6, 2023
2 parents 78e33c7 + 098f680 commit 9fb1920
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 87 deletions.
21 changes: 21 additions & 0 deletions src/ape/api/networks.py
Original file line number Diff line number Diff line change
Expand Up @@ -817,6 +817,27 @@ def explorer(self) -> Optional["ExplorerAPI"]:

return None # May not have an block explorer

@property
def is_fork(self) -> bool:
"""
True when using a forked network.
"""
return self.name.endswith("-fork")

@property
def is_local(self) -> bool:
"""
True when using the local network.
"""
return self.name == LOCAL_NETWORK_NAME

@property
def is_dev(self) -> bool:
"""
True when using a local network, including forks.
"""
return self.is_local or self.is_fork

@cached_property
def providers(self): # -> Dict[str, Partial[ProviderAPI]]
"""
Expand Down
157 changes: 77 additions & 80 deletions src/ape/api/providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from web3.exceptions import MethodUnavailable, TimeExhausted, TransactionNotFound
from web3.types import RPCEndpoint, TxParams

from ape._pydantic_compat import Field, root_validator, validator, dataclass
from ape._pydantic_compat import Field, dataclass, root_validator, validator
from ape.api.config import PluginConfig
from ape.api.networks import LOCAL_NETWORK_NAME, NetworkAPI
from ape.api.query import BlockTransactionQuery
Expand Down Expand Up @@ -496,7 +496,7 @@ def send_private_transaction(self, txn: TransactionAPI, **kwargs) -> ReceiptAPI:
Returns:
:class:`~ape.api.transactions.ReceiptAPI`
"""
if self.network.name == LOCAL_NETWORK_NAME or self.network.name.endswith("-fork"):
if self.network.is_dev:
# Send the transaction as normal so testers can verify private=True
# and the txn still goes through.
logger.warning(
Expand Down Expand Up @@ -1014,10 +1014,14 @@ def estimate_gas_cost(self, txn: TransactionAPI, **kwargs) -> int:
@cached_property
def chain_id(self) -> int:
default_chain_id = None
if self.network.name not in (
"adhoc",
LOCAL_NETWORK_NAME,
) and not self.network.name.endswith("-fork"):
if (
self.network.name
not in (
"adhoc",
LOCAL_NETWORK_NAME,
)
and not self.network.is_fork
):
# If using a live network, the chain ID is hardcoded.
default_chain_id = self.network.chain_id

Expand Down Expand Up @@ -1368,7 +1372,7 @@ def get_transactions_by_account_nonce(
if start_nonce > stop_nonce:
raise ValueError("Starting nonce cannot be greater than stop nonce for search")

if self.network.name != LOCAL_NETWORK_NAME and (stop_nonce - start_nonce) > 2:
if not self.network.is_local and (stop_nonce - start_nonce) > 2:
# NOTE: RPC usage might be acceptable to find 1 or 2 transactions reasonably quickly
logger.warning(
"Performing this action is likely to be very slow and may "
Expand Down Expand Up @@ -1434,101 +1438,94 @@ def poll_blocks(
required_confirmations: Optional[int] = None,
new_block_timeout: Optional[int] = None,
) -> Iterator[BlockAPI]:
network_name = self.network.name
# Wait half the time as the block time
# to get data faster.
block_time = self.network.block_time
wait_time = block_time / 2

# The timeout occurs when there is chain activity
# after a certain time.
timeout = (
(
10.0
if network_name == LOCAL_NETWORK_NAME or network_name.endswith("-fork")
else 50 * block_time
)
(10.0 if self.network.is_dev else 50 * block_time)
if new_block_timeout is None
else new_block_timeout
)

# Only yield confirmed blocks.
if required_confirmations is None:
required_confirmations = self.network.required_confirmations

# Track if a re-org happens, a what height.
reorg_height = None

@dataclass
class YieldAction:
hash: bytes
number: int
time: float

# Pretend we _did_ yield the last confirmed item, for logic's sake.
fake_last_block = self.get_block(self.web3.eth.block_number - required_confirmations)
last = YieldAction(
number=self.web3.eth.block_number - required_confirmations,
time=time.time()
number=fake_last_block.number, hash=fake_last_block.hash, time=time.time()
)

# A helper method for various points of ensuring we didn't timeout.
def assert_chain_activity():
time_waiting = time.time() - last.time
if time_waiting > timeout:
raise ProviderError("Timed out waiting for next block.")

# Begin the daemon.
while True:
# The next block we want is simply 1 after the last.
next_block = last.number + 1

# elif (
# current_height - required_confirmations <= end_of_last_range
# and reorg_height is None
# ):
# # In this case, we have not yet reached the point where a new block is confirmed,
# # but things may have progressed.
# logger.debug("No confirmed blocks. Waiting.")
# time.sleep(wait_time)
#
# if time.time() - time_of_last_yield > timeout:
# raise ProviderError("Timed out waiting for confirmable block.")
#
# continue

start_block_range = last_number_yielded + 1
end_block_range = current_height - required_confirmations + 1
if end_block_range < start_block_range:
breakpoint()
# Use an "adjused" head, based on the required confirmations.
head = self.get_block("latest")

else:
for block_num in range(start_block_range, end_block_range):
confirmed_block = self.web3.eth.get_block(block_num)

if (
reorg_height is not None
and confirmed_block is not None
and confirmed_block.number == reorg_height
):
# Done handling re-org.
reorg_height = None

if not confirmed_block:
start_time = time.time()
while confirmed_block is None:
if time.time() - start_time > timeout:
raise ProviderError(
f"Timed out waiting for block {block_num} to be available."
)
time.sleep(1)
confirmed_block = self.web3.eth.get_block(block_num)

if last_number_yielded and confirmed_block.number < last_number_yielded:
num_blocks_behind = last_number_yielded - confirmed_block.number
reorg_height = confirmed_block.number

if num_blocks_behind > required_confirmations:
logger.error(
f"{num_blocks_behind} Block reorganization detected. "
+ "Try adjusting the required network confirmations"
)
else:
# No "bad" blocks were yielded yet.
logger.warning(
f"{num_blocks_behind} Block reorganization detected. "
+ "Reorg is within the required network confirmations"
)
continue

yield self.network.ecosystem.decode_block(dict(confirmed_block))
last_number_yielded = confirmed_block.number

if stop_block is not None and confirmed_block.number == stop_block:
return
try:
adjusted_head = self.get_block(head.number - required_confirmations)
except Exception:
# TODO: I did encounter this sometimes in a re-org, needs better handling
continue

if adjusted_head.number == last.number and adjusted_head.hash == last.hash:
# The chain has not moved! Verify we have activity.
assert_chain_activity()
time.sleep(wait_time)
continue

elif adjusted_head.number < last.number or (
adjusted_head.number == last.number and adjusted_head.hash != last.hash
):
# Re-org detected! Error and catch up the chain.
logger.error(
"Chain has reorganized since returning the last block. "
"Try adjusting the required network confirmations."
)
# Catch up the chain by setting the "next" to this tiny head.
next_block = adjusted_head.number

# NOTE: Drop down to code outside of switch-of-ifs

elif adjusted_head.number < next_block:
# Wait for the next block.
# But first, let's make sure the chain is still active.
assert_chain_activity()
time.sleep(wait_time)
continue

# NOTE: Should only get here if yielding blocks!
# Either because it is finally time or because a re-org allows us.
for block_idx in range(next_block, adjusted_head.number + 1):
block = self.get_block(block_idx)
yield block

# This is the point at which the daemon will end,
# provider the user passes in a `stop_block` arg.
if stop_block is not None and block.number >= stop_block:
return

# Set the last action, used for checking timeouts and re-orgs.
last = YieldAction(number=block.number, hash=block.hash, time=time.time())

def poll_logs(
self,
Expand Down
6 changes: 3 additions & 3 deletions src/ape/managers/chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

from ape.api import BlockAPI, ReceiptAPI
from ape.api.address import BaseAddress
from ape.api.networks import LOCAL_NETWORK_NAME, NetworkAPI, ProxyInfoAPI
from ape.api.networks import NetworkAPI, ProxyInfoAPI
from ape.api.query import (
AccountTransactionQuery,
BlockQuery,
Expand Down Expand Up @@ -624,7 +624,7 @@ def _is_live_network(self) -> bool:
if not self.network_manager.active_provider:
return False

return self._network.name != LOCAL_NETWORK_NAME and not self._network.name.endswith("-fork")
return not self._network.is_dev

@property
def _data_network_name(self) -> str:
Expand Down Expand Up @@ -992,7 +992,7 @@ def get(

return contract_type

if self._network.name == LOCAL_NETWORK_NAME:
if self._network.is_local:
# Don't check disk-cache or explorer when using local
if default:
self._local_contract_types[address_key] = default
Expand Down
4 changes: 1 addition & 3 deletions src/ape/managers/project/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
from ethpm_types.utils import AnyUrl, Hex

from ape.api import DependencyAPI, ProjectAPI
from ape.api.networks import LOCAL_NETWORK_NAME
from ape.contracts import ContractContainer, ContractInstance, ContractNamespace
from ape.exceptions import ApeAttributeError, APINotImplementedError, ChainError, ProjectError
from ape.logging import logger
Expand Down Expand Up @@ -698,8 +697,7 @@ def track_deployment(self, contract: ContractInstance):
to track as a deployment of the project.
"""

network = self.provider.network.name
if network == LOCAL_NETWORK_NAME or network.endswith("-fork"):
if self.provider.network.is_dev:
raise ProjectError("Can only publish deployments on a live network.")

if not (contract_name := contract.contract_type.name):
Expand Down
2 changes: 1 addition & 1 deletion src/ape_cache/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def database_connection(self):
Returns:
Optional[`sqlalchemy.engine.Connection`]
"""
if self.provider.network.name == LOCAL_NETWORK_NAME:
if self.provider.network.is_local:
return None

if not self.network_manager.active_provider:
Expand Down

0 comments on commit 9fb1920

Please sign in to comment.