Skip to content

Commit

Permalink
fix(pg): remove logic that fast forwards block number on forked local…
Browse files Browse the repository at this point in the history
… nodes
  • Loading branch information
sam-goldman committed Jan 8, 2024
1 parent 3f6f20c commit 9e587b9
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 6 deletions.
6 changes: 6 additions & 0 deletions .changeset/kind-pans-train.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@sphinx-labs/plugins': patch
'@sphinx-labs/core': patch
---

Remove logic that fast forwards block number on forked local nodes
21 changes: 21 additions & 0 deletions packages/core/src/networks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,27 @@ export type SupportedNetworkName =
| SupportedTestnetNetworkName
| SupportedLocalNetworkName

/**
* Data returned by the `anvil_metadata` and `hardhat_metadata` RPC methods.
*
* @param forkedNetwork Info about the network that the local node is forking, if it exists. If the
* local node isn't forking a network, this field can be `undefined` or `null` depending on whether
* the network is an Anvil or Hardhat node.
*/
export type LocalNetworkMetadata = {
clientVersion: string
chainId: number
instanceId: string
latestBlockNumber: number
latestBlockHash: string
forkedNetwork?: {
chainId: number
forkBlockNumber: number
forkBlockHash: string
} | null
snapshots?: Record<string, unknown>
}

// This is the same as the `Network` enum defined in Solidity, which is used in the Foundry plugin.
// The fields in the two enums must be kept in sync, and the order of the fields must be the same.
export const NetworkEnum = {
Expand Down
23 changes: 23 additions & 0 deletions packages/core/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import { SphinxJsonRpcProvider } from './provider'
import { BuildInfo, CompilerOutput } from './languages/solidity/types'
import { getSolcBuild } from './languages'
import {
LocalNetworkMetadata,
SUPPORTED_LOCAL_NETWORKS,
SUPPORTED_NETWORKS,
SupportedChainId,
Expand Down Expand Up @@ -401,6 +402,28 @@ export const isLiveNetwork = async (
return false
}

/**
* Returns `true` if the network is a local node (i.e. Hardhat or Anvil) that's forking a live
* network. Returns `false` if the network is a local node that isn't forking a live network, or if
* the network is a live network.
*/
export const isFork = async (
provider: SphinxJsonRpcProvider | HardhatEthersProvider
): Promise<boolean> => {
try {
// The `hardhat_metadata` RPC method doesn't throw an error on Anvil because the `anvil_`
// namespace is an alias for `hardhat_`. Source:
// https://book.getfoundry.sh/reference/anvil/#custom-methods
const metadata: LocalNetworkMetadata = await provider.send(
`hardhat_metadata`,
[]
)
return !!metadata.forkedNetwork
} catch {
return false
}
}

export const getImpersonatedSigner = async (
address: string,
provider: SphinxJsonRpcProvider | HardhatEthersProvider
Expand Down
7 changes: 5 additions & 2 deletions packages/plugins/src/hardhat/simulate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ import {
ExecutionMode,
MerkleRootStatus,
SphinxJsonRpcProvider,
isLiveNetwork,
getNetworkNameForChainId,
isLiveNetwork,
isFork,
} from '@sphinx-labs/core'
import { ethers } from 'ethers'
import {
Expand Down Expand Up @@ -89,7 +90,9 @@ export const simulate = async (
chainId,
}

if (!(await isLiveNetwork(provider))) {
if (!(await isLiveNetwork(provider)) && !(await isFork(provider))) {
// The network is a non-forked local node.

// Fast forward 1000 blocks. This is necessary to prevent the following edge case that occurs
// when running the simulation against a vanilla Anvil node:
// 1. We deploy the Gnosis Safe and Sphinx contracts.
Expand Down
13 changes: 13 additions & 0 deletions packages/plugins/test/mocha/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
isExecutionArtifact,
getCompilerInputDirName,
getNetworkNameDirectory,
fetchURLForNetwork,
} from '@sphinx-labs/core'
import { ethers } from 'ethers'
import {
Expand Down Expand Up @@ -83,6 +84,18 @@ export const startAnvilNodes = async (chainIds: Array<SupportedChainId>) => {
await sleep(1000)
}

export const startForkedAnvilNodes = async (
chainIds: Array<SupportedChainId>
) => {
for (const chainId of chainIds) {
const forkUrl = fetchURLForNetwork(chainId)
// We must use `exec` instead of `execAsync` because the latter will hang indefinitely.
exec(`anvil --fork-url ${forkUrl} --port ${getAnvilPort(chainId)} &`)
}

await sleep(3000)
}

export const killAnvilNodes = async (chainIds: Array<SupportedChainId>) => {
for (const chainId of chainIds) {
const port = getAnvilPort(chainId)
Expand Down
51 changes: 47 additions & 4 deletions packages/plugins/test/mocha/simulate.spec.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
import chai from 'chai'
import chai, { expect } from 'chai'
import chaiAsPromised from 'chai-as-promised'
import {
ExecutionMode,
ParsedConfig,
SUPPORTED_MAINNETS,
SUPPORTED_TESTNETS,
SphinxJsonRpcProvider,
fetchURLForNetwork,
getNetworkNameForChainId,
isFork,
isLiveNetwork,
toSupportedChainId,
toSupportedNetworkName,
} from '@sphinx-labs/core'
import { ethers } from 'ethers'

import { makeDeployment, makeStandardDeployment } from './common'
import {
getAnvilRpcUrl,
killAnvilNodes,
makeDeployment,
makeStandardDeployment,
startForkedAnvilNodes,
} from './common'
import { simulate } from '../../src/hardhat/simulate'

chai.use(chaiAsPromised)
Expand Down Expand Up @@ -69,7 +78,41 @@ describe('Simulate', () => {
})

// Check that all promises were resolved
chai.expect(results.every((result) => result.status === 'fulfilled')).to.be
.true
expect(results.every((result) => result.status === 'fulfilled')).to.be.true
})

// This test checks that we can simulate a deployment on an Anvil node that's forking Ethereum. We
// added this test because we were previously receiving a `HeadersTimeoutError` originating from
// undici, which is called by Hardhat during the simulation. The error was occurring because we
// were fast-forwarding the block number on forked local nodes. It was only occurring ~50% of the
// time in this situation for an unknown reason.
it(`succeeds on anvil fork of ethereum`, async () => {
const ethereumChainId = 1
await startForkedAnvilNodes([ethereumChainId])

const parsedConfig = parsedConfigArray.find(
({ chainId }) => chainId === ethereumChainId.toString()
)
if (!parsedConfig) {
throw new Error(`Could not find Ethereum ParsedConfig.`)
}

// Get the Anvil RPC url, which is running the Ethereum fork.
const rpcUrl = getAnvilRpcUrl(ethereumChainId)
const provider = new SphinxJsonRpcProvider(rpcUrl)

// Sanity check that the provider is targeting a forked network which isn't a live network.
expect(await isFork(provider)).equals(true)
expect(await isLiveNetwork(provider)).equals(false)

// Run the simulation. If an error is thrown, the test will fail. We don't use `chaiAsPromised`
// here because it truncates the error message if an error occurs.
await simulate(
[parsedConfig],
parsedConfig.chainId,
getAnvilRpcUrl(ethereumChainId)
)

await killAnvilNodes([ethereumChainId])
})
})

0 comments on commit 9e587b9

Please sign in to comment.