-
-
Notifications
You must be signed in to change notification settings - Fork 15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: properly detect, specify, and handle connecting forked network to non-fork [APE-1393] #156
Changes from 6 commits
278bb6c
8604db9
bf8249c
61555ba
ff1f4c6
a58e8c5
af384ce
13f4712
da0a40c
b0d1474
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,7 +33,7 @@ | |
from eth_utils import is_0x_prefixed, is_hex, to_hex | ||
from evm_trace import CallType | ||
from evm_trace import TraceFrame as EvmTraceFrame | ||
from evm_trace import get_calltree_from_geth_trace | ||
from evm_trace import create_trace_frames, get_calltree_from_geth_trace | ||
from hexbytes import HexBytes | ||
from pydantic import BaseModel, Field | ||
from semantic_version import Version # type: ignore | ||
|
@@ -207,6 +207,54 @@ class Config: | |
extra = "allow" | ||
|
||
|
||
class ForkedNetworkMetadata(BaseModel): | ||
""" | ||
Metadata from the RPC ``hardhat_metadata``. | ||
""" | ||
|
||
chain_id: int = Field(alias="chainId") | ||
""" | ||
The chain ID of the network being forked. | ||
""" | ||
|
||
fork_block_number: int = Field(alias="forkBlockNumber") | ||
""" | ||
The number of the block that the network forked from. | ||
""" | ||
|
||
fork_block_hash: str = Field(alias="forkBlockHash") | ||
""" | ||
The hash of the block that the network forked from. | ||
""" | ||
|
||
|
||
class NetworkMetadata(BaseModel): | ||
""" | ||
Metadata from the RPC ``hardhat_metadata``. | ||
""" | ||
|
||
client_version: str = Field(alias="clientVersion") | ||
""" | ||
A string identifying the version of Hardhat, for debugging purposes, | ||
not meant to be displayed to users. | ||
""" | ||
|
||
instance_id: str = Field(alias="instanceId") | ||
""" | ||
A 0x-prefixed hex-encoded 32 bytes id which uniquely identifies an | ||
instance/run of Hardhat Network. Running Hardhat Network more than | ||
once (even with the same version and parameters) will always result | ||
in different instanceIds. Running hardhat_reset will change the | ||
instanceId of an existing Hardhat Network. | ||
""" | ||
|
||
forked_network: Optional[ForkedNetworkMetadata] = Field(None, alias="forkedNetwork") | ||
""" | ||
An object with information about the forked network. This field is | ||
only present when Hardhat Network is forking another chain. | ||
""" | ||
|
||
|
||
def _call(*args): | ||
return call([*args], stderr=PIPE, stdout=PIPE, stdin=PIPE) | ||
|
||
|
@@ -368,6 +416,17 @@ def hardhat_config_file(self) -> Path: | |
|
||
return path.expanduser().absolute() | ||
|
||
@property | ||
def metadata(self) -> NetworkMetadata: | ||
""" | ||
Get network metadata, including an object about forked-metadata. | ||
If the network is not a fork, it will be ``None``. | ||
This is a helpful way of determing if a Hardhat node is a fork or not | ||
when connecting to a remote Hardhat network. | ||
""" | ||
metadata = self._make_request("hardhat_metadata", []) | ||
return NetworkMetadata.parse_obj(metadata) | ||
|
||
@cached_property | ||
def _test_config(self) -> TestConfig: | ||
return cast(TestConfig, self.config_manager.get_config("test")) | ||
|
@@ -396,15 +455,6 @@ def package_is_plugin(package: str) -> bool: | |
|
||
return plugins | ||
|
||
@property | ||
def gas_price(self) -> int: | ||
# TODO: Remove this once Ape > 0.6.13 | ||
result = super().gas_price | ||
if isinstance(result, str) and is_0x_prefixed(result): | ||
return int(result, 16) | ||
|
||
return result | ||
|
||
def _has_hardhat_plugin(self, plugin_name: str) -> bool: | ||
return next((True for plugin in self._hardhat_plugins if plugin == plugin_name), False) | ||
|
||
|
@@ -749,8 +799,7 @@ def get_transaction_trace(self, txn_hash: str) -> Iterator[TraceFrame]: | |
def _get_transaction_trace(self, txn_hash: str) -> Iterator[EvmTraceFrame]: | ||
result = self._make_request("debug_traceTransaction", [txn_hash]) | ||
frames = result.get("structLogs", []) | ||
for frame in frames: | ||
yield EvmTraceFrame(**frame) | ||
yield from create_trace_frames(frames) | ||
|
||
def get_call_tree(self, txn_hash: str) -> CallTreeNode: | ||
receipt = self.chain_manager.get_receipt(txn_hash) | ||
|
@@ -918,28 +967,15 @@ def _upstream_provider(self) -> ProviderAPI: | |
def connect(self): | ||
super().connect() | ||
|
||
# Verify that we're connected to a Hardhat node with mainnet-fork mode. | ||
self._upstream_provider.connect() | ||
|
||
try: | ||
upstream_genesis_block_hash = self._upstream_provider.get_block(0).hash | ||
except ExtraDataLengthError as err: | ||
if isinstance(self._upstream_provider, Web3Provider): | ||
logger.error( | ||
f"Upstream provider '{self._upstream_provider.name}' " | ||
f"missing Geth PoA middleware." | ||
) | ||
self._upstream_provider.web3.middleware_onion.inject(geth_poa_middleware, layer=0) | ||
upstream_genesis_block_hash = self._upstream_provider.get_block(0).hash | ||
else: | ||
raise HardhatProviderError(f"Unable to get genesis block: {err}.") from err | ||
|
||
self._upstream_provider.disconnect() | ||
|
||
if self.get_block(0).hash != upstream_genesis_block_hash: | ||
logger.warning( | ||
"Upstream network has mismatching genesis block. " | ||
"This could be an issue with hardhat." | ||
if not self.metadata.forked_network: | ||
# This will fail when trying to connect hardhat-fork to | ||
# a non-forked network. | ||
raise HardhatProviderError( | ||
"Network is no a fork. " | ||
"Hardhat is likely already running on the local network. " | ||
"Try using config:\n\n(ape-config.yaml)\n```\nhardhat:\n " | ||
"host: auto\n```\n\nso that multiple processes can automatically " | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this message should prevent the user from being misled so much, and even has a suggestion for how to fix. |
||
"use different ports." | ||
) | ||
|
||
def build_command(self) -> List[str]: | ||
|
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,7 +8,6 @@ | |
from ape.api.accounts import ImpersonatedAccount | ||
from ape.contracts import ContractContainer | ||
from ape.exceptions import ContractLogicError | ||
from ape.pytest.contextmanagers import RevertsContextManager as reverts | ||
from ape.types import CallTreeNode, TraceFrame | ||
from evm_trace import CallType | ||
from hexbytes import HexBytes | ||
|
@@ -176,26 +175,6 @@ def test_contract_revert_custom_exception(owner, get_contract_type, accounts): | |
assert err.value.inputs == {"addr": accounts[7].address, "counter": 123} | ||
|
||
|
||
def test_contract_dev_message(owner, get_contract_type): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unfortunately, we can longer run this test without installing |
||
sub_ct = get_contract_type("sub_reverts_contract") | ||
sub = owner.deploy(ContractContainer(sub_ct)) | ||
container = ContractContainer(get_contract_type("reverts_contract")) | ||
contract = owner.deploy(container, sub) | ||
|
||
with reverts(dev_message="dev: one"): | ||
contract.revertStrings(1, sender=owner) | ||
with reverts(dev_message="dev: error"): | ||
contract.revertStrings(2, sender=owner) | ||
with reverts(dev_message="dev: such modifiable, wow"): | ||
contract.revertStrings(4, sender=owner) | ||
with reverts(dev_message="dev: foobarbaz"): | ||
contract.revertStrings(13, sender=owner) | ||
with reverts(dev_message="dev: great job"): | ||
contract.revertStrings(31337, sender=owner) | ||
with reverts(dev_message="dev: sub-zero"): | ||
contract.subRevertStrings(0, sender=owner) | ||
|
||
|
||
def test_transaction_contract_as_sender(contract_instance, connected_provider): | ||
# Set balance so test wouldn't normally fail from lack of funds | ||
connected_provider.set_balance(contract_instance.address, "1000 ETH") | ||
|
@@ -216,7 +195,7 @@ def test_set_balance(connected_provider, owner, convert, amount): | |
def test_set_code(connected_provider, contract_instance): | ||
provider = connected_provider | ||
code = provider.get_code(contract_instance.address) | ||
assert type(code) == HexBytes | ||
assert type(code) is HexBytes | ||
assert provider.set_code(contract_instance.address, "0x00") is True | ||
assert provider.get_code(contract_instance.address) != code | ||
assert provider.set_code(contract_instance.address, code) is True | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so we had an issue with the genesis block on certain networks, which is why we had to switch to logging as an error instead, with hopes to someday having a better way. Now, hardhat has a metadata RPC that we can use to deduce whether the network is a fork, so we can put back this error.
See this comment for historical reference: #32 (comment)