如何使本地HardHat网络运行速度

发布于 2025-02-04 21:02:04 字数 410 浏览 1 评论 0 原文

我有一个本地的硬汉节点,简单地呈现为:

npx hardhat节点

我有一个智能合约代码,我想测试,该代码以简单的循环测试100个NFT。该代码基本上将地址添加到地图中,并以相当标准的方式调用openzeppelin的 _safemint 函数。

但是,在我的测试中,运行此功能需要几秒钟的时间,对于循环而言,这有点太多了。我已经尝试从Hardhat配置启用/禁用自动启动,但似乎并没有改变任何内容。

我需要在测试中为许多迭代(10000)运行此功能,因此呼叫的持续时间是不可接受的。我也使用M1 Max,所以我怀疑是我的CPU是100次循环的瓶颈,这可能需要一些纳秒。

如何使HardHat更快地执行合同代码?

(固体 ^0.8.0,HardHat 2.8.2)

I have a local hardhat node spawned simply as:

npx hardhat node

I have a smart contract code that I want to test that mints 100 NFTs with a simple loop. The code basically adds the address to a map and calls OpenZeppelin's _safeMint function in a pretty standard manner.

However, in my tests it takes a few seconds to run this function, which is a bit too much for 100-iteration for loop. I've tried enabling/disabling autominting from Hardhat config but doesn't seem to change anything.

I will need to run this function for many iterations (10000) in my tests, so the duration of the call is unacceptable. I'm also on M1 Max so I doubt it's my CPU that's the bottleneck for a 100-iteration for loop which should probably take a few nanoseconds.

How can I make hardhat execute contract code faster?

(Solidity ^0.8.0, hardhat 2.8.2)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

卸妝后依然美 2025-02-11 21:02:04

下面的解决方案是一个黑客攻击,但是我在测试中大量使用了它的循环,最多可以进行5000次迭代,并且可以运行良好(仅需几分钟而不是使用自动化时)。它的要旨是禁用自动化和间隔开采,而是按需手动挖掘块。

// enable manual mining
await network.provider.send("evm_setAutomine", [false]);
await network.provider.send("evm_setIntervalMining", [0]);

// create the transactions, which will be pending
await Promise.all(promises); // promises consists of all the txs

// mine the needed blocks, below we mine 256 blocks at once (how many blocks to
// mine depends on how many pending transactions you have), instead of having 
// to call `evm_mine` for every single block which is time consuming
await network.provider.send("hardhat_mine", ["0x100"]);

// re-enable automining when you are done, so you dont need to manually mine future blocks
await network.provider.send("evm_setAutomine", [true]);

The solution below is a hack, but I've used it extensively in my tests with for loops up to 5000 iterations and it can run just fine (takes only a couple minutes instead of hours when using automining). The gist of it is to disable automining and interval mining and instead manually mine the blocks on demand.

// enable manual mining
await network.provider.send("evm_setAutomine", [false]);
await network.provider.send("evm_setIntervalMining", [0]);

// create the transactions, which will be pending
await Promise.all(promises); // promises consists of all the txs

// mine the needed blocks, below we mine 256 blocks at once (how many blocks to
// mine depends on how many pending transactions you have), instead of having 
// to call `evm_mine` for every single block which is time consuming
await network.provider.send("hardhat_mine", ["0x100"]);

// re-enable automining when you are done, so you dont need to manually mine future blocks
await network.provider.send("evm_setAutomine", [true]);
傻比既视感 2025-02-11 21:02:04

我将尝试以广义的方式回答您的问题,但是以与您的要求相关的顺序列出了这些方法。这是有关如何加快硬汉测试运行的指南:

1。使用更快的本地以太坊节点

hardhat提供的默认本地节点使用Ethereumjs EVM,这可以相对较慢。考虑切换到更快的本地节点以提高性能。

Anvil:高性能本地节点

anvil”> anvil 是本地为开发设计的以太坊节点。值得注意的是,它使用 revm ,一种在Rust中实现的EVM,以其性能而闻名。请参阅 ziyadedher/evm-bench 有关不同EVM实现的性能比较。

要将Anvil与Hardhat集成,有一个名为。建议设置启动:false 和spawn anvil 在单独的外壳中手动使用以下命令进行操作:

anvil --prune-history --order fifo --code-size-limit 4294967296 -m "test test test test test test test test test test test junk" --gas-limit 100000000000

对于所有 Anvil 选项请参见: https://book.get.get.getfoundry.sh/reference/reference/anvil/anvil/#options

此外,您可以通过使用 eth_sendunsignedtransaction 方法来节省一些时间,该方法接受与 eth_call

await network.provider.send("eth_sendUnsignedTransaction", [{
  data,
  from,
  to,
  gas,
  gasPrice
}]);

2。 peaverally parallel test测试 相同的参数。执行

执行。并行运行测试可以大大减少总体执行时间,尤其是如果您拥有多核CPU。

步骤:

  • 使用 - 并行标志:

  • 另外,在Hardhat Config的摩卡部分中设置并行:true

Mocha的并行执行同时在单独的文件中运行测试。因此,为了最大化此功能,如果您对给定的测试功能有多个测试用例(例如,基于属性的测试),则可以在多个文件上分发/划分这些测试用例。

步骤:

  • 创建多个重复测试文件,命名 test-foo- {i} .spec.ts 其中 {i} 是文件编号。

  • 在这些文件中平均将测试用例划分。为此自动化:

      import {getTestCases}从'./getTestCases';
    
    const testcases = getTestCases(); //如果您有70个测试用例,那么这些测试用例将分为每个文件10
    const totalfiles = 7; //如果Mocha能够同时处理7个文件,则可以创建同一文件的7个重复项
    
    函数getFileIndex():number {
      const fileName = __fileName.split(' - ')。pop()?split('。')[0] '1';
      返回parseint(文件名,10);
    }
    
    const totalcases = testcases.length;
    const caseperfile = Math.ceil(totalcase / totalfiles);
    const startIndex =(getFileIndex() -  1) * caseperfile;
    const endIndex = Math.min(StartIndex + CasePerfile,tocul caseperfile,tocul castercase);
    const filetestcases = testcases.slice(startIndex,endIndex); //该文件处理的测试用例
    
    //现在使用这些情况进行测试
     

确保您的测试用例得到了优化:

  • 避免冗余:确保没有重叠或重复的测试逻辑。
  • 在可能的情况下模拟外部呼叫以减少外部依赖关系和网络调用。不要不必要地使用HardHat网络叉。

NB:如果您使用的是hardhat local节点(例如 Anvil )以外的本地节点单独的帐户集,以避免非CE碰撞。当使用 hardhat -parallel 时,这不是问题,而硬窃是配置的网络,因为HardHat可能会为每个测试脚本催生多个硬式节点,或者可能不检查交易nonces。

3。将测试逻辑卸载到坚固性:

而不是在打字稿中实现复杂的测试逻辑,而是考虑使用Helper Solidity合同来封装和执行该逻辑。

步骤 创建助手合同:编写固体测试合同,以模拟您要测试的操作。例如,如果您要测试令牌传输,则测试合同可以在一个功能中管理多个转移。

b。 与助手相互作用:在您的打字稿测试中,与此助手合同进行部署和互动,而不是发送多个单独的交易。

c。 批处理操作:使用助手合同将多个操作分解为单个交易。

好处:

  • 效率:与打字稿中的多个单独交易相比,坚固性通常可以更快地执行批量操作。
  • Simplicity :打字稿测试变得更加干净,着重于高级操作。

4。指定 Gaslimit GASPRICE

通过手动指定这些值,您可以避免 eth_estimategas eth_gas_gasprice 呼叫。为了避免由于太低而导致的交易失败,请谨慎设置这些值足够高。

用法:

await contract.functionName(args, {
  gasLimit: 10_000_000,
  gasPrice: ethers.utils.parseUnits("1", "gwei")
});

您也可以考虑在 hardhat网络config 中使用 BlockGaslimit gas gasprice 字段。

5。快照&还原(并使用固定装置):

快照和还原可让您在给定点捕获整个区块链的状态,并随意恢复到该状态。

用法:

  • 在需要一组需要一定设置的测试(例如,部署特定合同和初始化状态)之前,请进行快照。
  • 运行测试后,将其还原为快照。这避免了为每个后续测试重做设置的需要。

如何实施:

const snapshotId = await ethers.provider.send("evm_snapshot", []);
// ... tests or operations ...
await ethers.provider.send("evm_revert", [snapshotId]);

福利:

  • 效率:避免冗余部署和状态初始化。
  • 清洁环境:确保每个测试都以一致的环境开始,从而最大程度地减少副作用。

具有固定装置的自动快照:

使用 loadfixture 自动化和恢复快照的过程。

6.优化打字稿代码:

编写打字稿测试的方式可能会影响测试执行时间。以下是一些优化

:批处理交易:

如果交易顺序并不关键,请同时发送它们。您也可以为 eth_call 请求执行此操作,当调用 view> view 函数或静态函数的状态函数。

await Promise.all([
  contractA.functionA(args),
  contractB.functionB(args),
  // ...
]);

b。缓存结果:

如果您反复从区块链中获取相同的数据,请考虑在变量中缓存。

const tokenName = await tokenContract.name();

c。限制链查询:

将不必要的查询减少到链条。如果可能,请计算或推断结果,而无需其他链查询。

7。连续集成(CI)改进

如果您使用诸如GitHub Action之类的CI/CD系统,请确保:

  • 您的CI环境已针对速度进行了优化。
  • 使用缓存以减少安装时间。
  • 在可能的情况下并行化CI任务。

I'm going to try to answer your question in a generalised manner, however listing the methods in descending order of relevance to your requirement. Here's a guide on how to speed up your Hardhat test runs:

1. Use a Faster Local Ethereum Node

The default local node provided by Hardhat uses the ethereumjs EVM, which can be relatively slow. Consider switching to a faster local node for improved performance.

Anvil: A High-Performance Local Node

Anvil is a local Ethereum node designed for development. Notably, it uses the revm, an EVM implemented in Rust, which is known for its performance. See ziyadedher/evm-bench for a performance comparison of different EVM implementations.

To integrate Anvil with Hardhat, there's a plugin named hardhat-anvil. It's recommended to set launch: false and spawn anvil manually in a separate shell which you can do using the following command:

anvil --prune-history --order fifo --code-size-limit 4294967296 -m "test test test test test test test test test test test junk" --gas-limit 100000000000

For a complete list of all anvil options see: https://book.getfoundry.sh/reference/anvil/#options

Additionally, you can save some time by disabling transaction signature verification using the eth_sendUnsignedTransaction method that accepts the same parameters as eth_call:

await network.provider.send("eth_sendUnsignedTransaction", [{
  data,
  from,
  to,
  gas,
  gasPrice
}]);

2. Leverage Parallel Test Execution

Hardhat supports Mocha's parallel test execution. Running tests in parallel can considerably reduce the overall execution time, especially if you have a multi-core CPU.

Steps:

  • Use the --parallel flag:
    npx hardhat test --parallel
    
  • Alternatively, set parallel: true in the Mocha section of your Hardhat config.

Mocha's parallel execution runs tests in separate files concurrently. Therefore, to maximize this feature, if you have multiple test cases(e.g. for a kind of property-based test) for a given test function you can distribute/partition these test cases across multiple files.

Steps:

  • Create multiple duplicate test files, for example, named test-foo-{i}.spec.ts where {i} is the file number.

  • Partition your test cases equally among these files. To automate this:

    import { getTestCases } from './getTestCases';
    
    const testCases = getTestCases(); // if you have 70 test cases then these test cases will be split 10 per file
    const totalFiles = 7; // if mocha is able to process 7 files concurrently then you can create 7 duplicates of the same file
    
    function getFileIndex(): number {
      const fileName = __filename.split('-').pop()?.split('.')[0] ?? '1';
      return parseInt(fileName, 10);
    }
    
    const totalCases = testCases.length;
    const casesPerFile = Math.ceil(totalCases / totalFiles);
    const startIndex = (getFileIndex() - 1) * casesPerFile;
    const endIndex = Math.min(startIndex + casesPerFile, totalCases);
    const fileTestCases = testCases.slice(startIndex, endIndex); // the test cases for this file to process
    
    // Now run the tests using these cases
    

Ensure that your test cases are optimized:

  • Avoid redundancy: Ensure there's no overlapping or duplicated test logic.
  • Mock external calls where possible to reduce external dependencies and network calls. Don't use a hardhat network fork unnecessarily.

NB: If you're using a local node other than the hardhat local node(e.g. anvil) then when using hardhat --parallel you must ensure that each file is using a separate set of accounts to avoid nonce collisions. This isn't an issue when using hardhat --parallel with hardhat being the configured network as hardhat likely spawns multiple hardhat nodes for each test script or maybe doesn't check the transaction nonces.

3. Offloading Test Logic to Solidity:

Instead of implementing complex test logic in TypeScript, consider using helper Solidity contracts to encapsulate and execute that logic.

Steps:

a. Create Helper Contract: Write Solidity test contract/s that mimics the operations you want to test. For example, if you're testing token transfers, the test contract could manage multiple transfers in one function.

b. Interact with Helper: In your TypeScript tests, deploy and interact with this helper contract instead of sending multiple individual transactions.

c. Batch Operations: Use the helper contract to batch multiple operations into single transactions.

Benefits:

  • Efficiency: Solidity can often perform batch operations faster than multiple individual transactions in TypeScript.
  • Simplicity: TypeScript tests become cleaner, focusing on high-level operations.

4. Specifying gasLimit and gasPrice:

By manually specifying these values, you can avoid the overhead of eth_estimateGas and eth_gasPrice calls. To avoid transactions failing due to a too low override be careful to set these values sufficiently high.

Usage:

await contract.functionName(args, {
  gasLimit: 10_000_000,
  gasPrice: ethers.utils.parseUnits("1", "gwei")
});

You could also consider setting this globally in the hardhat network config using the blockGasLimit, gas and gasPrice fields.

5. Snapshotting & Restoring (and Using Fixtures):

Snapshotting and restoring allow you to capture the entire state of the blockchain at a given point and revert to that state at will.

Usage:

  • Before a set of tests that require a certain setup (e.g., deploying specific contracts and initializing states), take a snapshot.
  • After running the tests, revert to the snapshot. This avoids the need to redo the setup for each subsequent test.

How to Implement:

const snapshotId = await ethers.provider.send("evm_snapshot", []);
// ... tests or operations ...
await ethers.provider.send("evm_revert", [snapshotId]);

Benefits:

  • Efficiency: Avoids redundant deployments and state initializations.
  • Clean Environment: Ensures each test starts with a consistent environment, minimizing side effects.

Automate Snapshots with Fixtures:

Use loadFixture to automate the process of taking and restoring a snapshot.

6. Optimize TypeScript Code:

The way your TypeScript tests are written can impact the test execution time. Here are some optimizations:

a. Batch Transactions:

If the order of transactions isn't critical, send them simultaneously. You can also do this for eth_call requests when calling view functions or staticcalls to stateful functions.

await Promise.all([
  contractA.functionA(args),
  contractB.functionB(args),
  // ...
]);

b. Cache Results:

If you're repeatedly fetching the same data from the blockchain, consider caching it in a variable.

const tokenName = await tokenContract.name();

c. Limit Chain Queries:

Reduce unnecessary queries to the chain. If possible, calculate or infer results without additional chain queries.

7. Continuous Integration (CI) Improvements

If you use a CI/CD system like GitHub Actions, ensure:

  • Your CI environment is optimized for speed.
  • Use caching for dependencies to reduce installation times.
  • Parallelize CI tasks where possible.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文