如何从Node.js代码启停Hardhat JSON-RPC服务器实现主网同步
从Node.js代码中启动/终止Hardhat分叉节点的最优方案
针对你用hardhat_reset同步主网时出现节点异常的问题,最优方案是直接在Node.js代码中通过子进程管理来启动、终止Hardhat分叉节点,彻底避免重置命令带来的状态残留问题。
核心思路
用Node.js内置的child_process.spawn创建Hardhat节点的子进程,全程在代码中控制节点的生命周期:
- 需要同步时,先优雅终止旧节点进程
- 重新启动新节点,指定最新的主网区块号进行分叉
- 通过监听进程输出确认节点就绪,避免过早发起交互
实现代码示例
封装节点管理工具
const { spawn } = require('child_process'); const Web3 = require('web3'); class HardhatForkManager { constructor(alchemyApiKey) { this.alchemyUrl = `https://eth-mainnet.alchemyapi.io/v2/${alchemyApiKey}`; this.web3 = new Web3(this.alchemyUrl); this.nodeProcess = null; this.isReady = false; } // 启动Hardhat分叉节点,可指定分叉区块号(默认同步到最新) async start(forkBlockNumber = null) { // 先终止已有进程 if (this.nodeProcess) await this.stop(); const args = ['node', '--fork', this.alchemyUrl]; if (forkBlockNumber) { args.push('--fork-block-number', forkBlockNumber.toString()); } // 启动子进程,继承终端输出方便调试 this.nodeProcess = spawn('npx', args, { stdio: ['inherit', 'pipe', 'inherit'], env: { ...process.env, HARDHAT_NETWORK: 'hardhat' } }); return new Promise((resolve) => { // 监听输出,确认节点启动完成 this.nodeProcess.stdout.on('data', (data) => { const log = data.toString(); if (log.includes('Started HTTP and WebSocket JSON-RPC server')) { this.isReady = true; console.log('Hardhat分叉节点已就绪'); resolve(); } }); // 监听进程异常退出 this.nodeProcess.on('exit', (code) => { console.log(`Hardhat节点进程退出,代码:${code}`); this.isReady = false; this.nodeProcess = null; }); }); } // 优雅终止节点进程 async stop() { if (!this.nodeProcess) return; return new Promise((resolve) => { // 发送SIGINT信号,模拟Ctrl+C终止Hardhat节点 this.nodeProcess.on('exit', resolve); this.nodeProcess.kill('SIGINT'); }); } // 获取主网最新区块号 async getLatestMainnetBlock() { return parseInt(await this.web3.eth.getBlockNumber()); } } // 使用示例 async function runTestCycle(alchemyApiKey) { const manager = new HardhatForkManager(alchemyApiKey); // 第一次启动,同步到最新区块 const latestBlock = await manager.getLatestMainnetBlock(); await manager.start(latestBlock); // 执行你的测试逻辑... console.log('执行测试任务'); // 一段时间后需要重新同步 await new Promise(resolve => setTimeout(resolve, 30000)); // 模拟等待30秒 // 终止旧节点,启动新节点同步最新区块 const newLatestBlock = await manager.getLatestMainnetBlock(); await manager.start(newLatestBlock); // 再次执行测试... console.log('执行新一轮测试任务'); // 测试完成后清理 await manager.stop(); } // 替换为你的Alchemy API Key runTestCycle('YOUR_ALCHEMY_API_KEY');
关键注意事项
- 进程终止方式:必须用
SIGINT信号终止Hardhat进程,而不是SIGKILL,否则会导致Hardhat无法清理临时文件,可能残留僵尸进程。 - 启动就绪判断:必须等待节点输出"Started HTTP and WebSocket JSON-RPC server"日志后再发起交易,避免连接失败。
- 环境变量传递:启动子进程时继承当前环境变量,确保Hardhat能读取到配置文件中的参数。
- 异常处理:可以在代码中添加进程崩溃后的自动重启逻辑,提升稳定性。
这种方案完全替代了hardhat_reset的方式,每次同步都是全新的节点实例,彻底解决了重置导致的状态异常问题。
内容的提问来源于stack exchange,提问作者kfx
相关产品推荐
相关产品推荐

