スマコンデプロイできない問題を解決する
はじめに
スマコンがデプロイできないみたいなことが、結構あったりします。ネットワークの調子が悪いとか、デプロイに必要なガス代が足りないとかいろいろありますが、その中でも今回はエラーが出てこない時の解消法について説明します。
tx: トランザクション
結論 nonce が問題であることが多い
結論ですが、nonceというものが問題であることが多いです。
ブロックチェーン上でtx発行する際、nonce(ナンス)の設定が重要になります。nonceはアカウントが特定のトランザクションを行うたびにインクリメントされる数値で、二重支払いやtxの再実行を防ぐために使用されます。txがブロックチェーンに受け入れられるためには、正しいnonce値を持っている必要があります。
なので、txを実行できていない問題としてnonceが要因としてあげられますが、その解決の前にどのようなフローでtxが実行されブロックの中に取り込まれるのかの概要を理解します。
txがブロックに追加されるまで
1. txの送信
まず、ユーザーがtxを生成し、ネットワークに送信します。このtxには、送信元アドレス(from)、受信先アドレス(to)、送信額(value)、ガス価格(gas price)、ガスリミット (gas limit)、そしてnonce値が含まれます。
2. 初期検証
txがネットワークに送信されると、ノードは検証を行います。この段階でnonceの正当性(例えば、アカウントの現在のnonce値に対して正しいインクリメントがされているかどうか)やtxの形式(署名の有効性など)が検証されます。
3. プールへの格納
txは、ネットワークのノードによって検証された後、mempoolに追加されます。なのでnonce値が不正確な場合は、2. 初期検証によって弾かれるのでこのmempoolにすら入っていない状態です。txはブロックに取り込まれるまで待機します。
4. バリデータによる検証
バリデーターは、mempoolからtxを選択し、新しいブロックを形成します。txは、提案されたガス代に基づいて選択されることが多いです。高いガス価格を設定することで、txが早く処理される可能性が高くなります。
5. ブロックチェーンへの追加
バリデーターによって新しいブロックが正常に作成されると、そのブロックはブロックチェーンに追加され、txは確定されます。
nonceを解決してネットワークに検証してもらう
なのでnonceで引っ掛かっている場合は適切なnonce値に設定してtxを作成しネットワークに送る必要があります。
まずは現在のnonce値を確認します。
(hardhatを使用している前提で解説を続けます。)
import { ethers } from "hardhat";
async function main() {
const [deployer] = await ethers.getSigners();
const deployerAddress = await deployer.getAddress();
// 現在のnonceを取得
let currentNonce = await ethers.provider.getTransactionCount(deployerAddress, 'latest');
console.log(`Current deployerAddress: ${deployerAddress}`);
console.log(`Current nonce: ${currentNonce}`);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
上記のコードを hardhat run
コマンドで叩くとCurrent nonce
としてdeployerAddress
の現在のnonceを見ることができます。私の環境では107
がnonceになります。
(このdeployerAddress
はhardhat.config.ts
でセットしておく必要があります。)
nonceを明示的に指定してtx作成
上記の例では107が出てきたので素直にそれを指定してtxを発行します。nonceのセットの仕方はこのようになります。
import { ethers } from "hardhat";
async function main() {
const [deployer] = await ethers.getSigners();
const deployerAddress = await deployer.getAddress();
let currentNonce = await ethers.provider.getTransactionCount(deployerAddress, 'latest');
console.log(`Current deployerAddress: ${deployerAddress}`);
console.log(`Current nonce: ${currentNonce}`);
const SampleContract = await ethers.getContractFactory("SampleContract");
// 先ほど調べたnonceを下記のように設定。
const sampleContract = await SampleContract.deploy({
nonce: 107
});
await sampleContract.deployed();
const receipt = await ethers.provider.getTransactionReceipt(sampleContract.deployTransaction.hash);
console.log("Deployment transaction receipt:", receipt);
console.log(
`SampleContract deployed to ${sampleContract.address}`
);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
これで通常できるようになるはずですが、私の場合はまだ立ちはだかる壁がありました。
注目すべきはここですね。
Error: replacement fee too low
同じnonce番号があると入れ替える必要がある
ネットワークに同じnonceを送ろうとしている場合どちらのnonceが正しいのかがネットワークがわからず受け入れられません。今回明示的にnonceを設定したtxをネットワークに識別してもらいたい場合、上記エラーにもあるように入れ替えるためにgas代を高くしてネットワークに送る必要があります。
具体的には下記のようにします。
import { ethers } from "hardhat";
async function main() {
const [deployer] = await ethers.getSigners();
const deployerAddress = await deployer.getAddress();
let currentNonce = await ethers.provider.getTransactionCount(deployerAddress, 'latest');
console.log(`Current deployerAddress: ${deployerAddress}`);
console.log(`Current nonce: ${currentNonce}`);
//今回はgas代を10%高くする
const gasPrice = await ethers.provider.getGasPrice();
const increasedGasPrice = gasPrice.mul(110).div(100);
//それを明示的に入れ込む
const sampleContract = await SampleContract.deploy({
nonce: 150,
gasPrice: increasedGasPrice
});
await sampleContract.deployed();
const receipt = await ethers.provider.getTransactionReceipt(sampleContract.deployTransaction.hash);
console.log("Deployment transaction receipt:", receipt);
console.log(
`SampleContract deployed to ${sampleContract.address}`
);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
これでコントラクトデプロイや、他txの発行がうまくいくはずです!(うまくいくことを祈っています!)
Discussion