Hardhat中connect()切换账户签名交易在Rinkeby测试网异常问题
多账户调用合约测试网部署异常排查
开发背景
- 实现功能:开发支持多用户发送链上留言的智能合约,合约会将用户提交的留言内容
message、区块时间戳block.timestamp、发送者地址三类数据,存储到链上的结构体数组中。 - 脚本逻辑:部署脚本
scripts/deploy.js通过调用getSigners()获取签名账户发起交易,核心代码为const [_, randomPerson,addr1, addr2] = await hre.ethers.getSigners(),其中返回数组第一个元素为默认部署账户(owner),代码中用下划线_指代。
相关代码
WavePortal.sol
// SPDX-License-Identifier: UNLICENSED pragma solidity >=0.8.0 <0.9.0; import "hardhat/console.sol"; contract WavePortal { uint256 totalWaves; event NewWave(address indexed from, uint timestamp, string message); struct Wave{ address waver; string message; uint timestamp; } Wave[] waves; constructor() { console.log("Yo yo, I am a contract and I am smart"); } function wave(string memory _message) public { totalWaves += 1; console.log("%s waved with message %s", msg.sender, _message); waves.push(Wave(msg.sender, _message, block.timestamp)); emit NewWave(msg.sender, block.timestamp, _message); } function getAllWaves() public view returns (Wave[] memory){ return waves; } function getTotalWaves() public view returns (uint256) { console.log("We have %d total waves!", totalWaves); return totalWaves; } }
scripts/deploy.js
const hre = require("hardhat"); const main = async () => { const waveContractFactory = await hre.ethers.getContractFactory("WavePortal"); const waveContract = await waveContractFactory.deploy(); await waveContract.deployed(); console.log("Contract address:", waveContract.address); let waveCount; waveCount = await waveContract.getTotalWaves(); console.log(waveCount.toNumber()); let waveTxn = await waveContract.wave("A message!"); await waveTxn.wait(); const [_, randomPerson, addr1, addr2] = await hre.ethers.getSigners(); console.log("Address of _ is: ",_.address); console.log("Random Person: ", randomPerson); console.log("Address of Random Person is: ", randomPerson.address); waveTxn = await waveContract.connect(randomPerson).wave("Another message!"); await waveTxn.wait(); let allWaves = await waveContract.getAllWaves(); console.log(allWaves); }; const runMain = async () => { try { await main(); process.exit(0); } catch (error) { console.log(error); process.exit(1); } }; runMain();
问题现象
- 本地环境运行正常:执行
npx hardhat node启动本地区块链节点后,另一终端执行npx hardhat run scripts/deploy.js --network localhost部署,所有功能运行正常,可正常获取_、randomPerson的地址,通过connect()切换账户调用合约方法无异常。 - 测试网部署异常:通过Alchemy或Infura接入Rinkeby测试网,执行
npx hardhat run scripts/deploy.js --network rinkeby部署时,仅默认owner账户_可正常使用,randomPerson、addr1、addr2等其余解构获取的账户均为undefined,执行console.log("randomPerson is: ", randomPerson)输出undefined,无法通过connect()方法切换上述账户调用合约方法,仅能使用owner账户发起交易,无法完成测试网部署。已确认hardhat.config.js中Rinkeby、Ropsten等网络的RPC、链ID配置正确,多次尝试未解决问题。
故障原因
本地启动hardhat node时,Hardhat会自动预生成10个预置了测试ETH的开发账户,全部注入signer列表,因此解构多个账户时可以正常拿到对应实例。
连接远程测试网时,Hardhat不会自动生成测试账户,只会读取hardhat.config.js中对应网络配置下accounts字段内配置的私钥,生成对应的signer实例。如果配置中只填入了1个部署账户的私钥,getSigners()返回的数组长度就为1,解构位置靠后的randomPerson、addr1、addr2自然会返回undefined。
修复方案
- 提前准备3-4个独立的测试钱包,记录好私钥,先到Rinkeby测试网水龙头领取足够的测试ETH用于支付交易gas费。
- 打开
hardhat.config.js,找到Rinkeby网络的配置项,将所有测试账户的私钥按脚本中解构的顺序添加到accounts数组中,参考配置如下:
module.exports = { solidity: "0.8.0", networks: { rinkeby: { url: "你的Alchemy/Infura Rinkeby节点RPC地址", accounts: [ "部署账户(对应代码中_)的私钥", "randomPerson对应测试账户的私钥", "addr1对应测试账户的私钥", "addr2对应测试账户的私钥" ] } } };
- 配置完成后重新执行部署脚本,即可正常获取多个signer实例,通过
connect()切换账户发起交易。
安全提示:不要直接使用本地hardhat node默认生成的测试账户私钥连接公网测试网/主网,这类账户私钥是公开的,存入的资产会被立即盗取。
内容的提问来源于stack exchange,提问作者Aviral Choudhary
相关产品推荐
相关产品推荐

