2021.10.04
EIP-1559のトランザクションガス手数料のメカニズムの変更の調査
こんにちは。次世代システム研究室のL.W.です。
イーサリアム1.0の大規模アップデート「London」は、2021年8月5日12時34分(協定世界時:UTC=日本時間5日21時34分)、ブロック高12965000で無事に実行されました。
今回のアップグレードで実装された中でも特に注目を集めたのは、EIP-1559です。
EIP-1559は、取引手数料を支払うためにイーサリアムのマーケットメカニズムを変更しました。 基本的に、EIP-1559はファーストプライスオークション(first-price auction)を廃止し、固定価格セール(fixed-price sale)に置き換えました。
つまり、「Gas Price」でのオークションの代わりに、予測可能(ブロック毎に固定)の「Base Fee Per Gas 」、チップ機能の「Priority Fee Per Gas」とロックに含めるために出せる最大額の「Max Fee Per Gas」を新たにガスメカニズムに組み込んでいました。
さて、我々開発者に対して、今回の全く新しいガスメカニズムに何を注意したらいいでしょうか。
1. EIP-1559トランザクションのガス手数料のメカニズムの変更
1.1 EIP-1559前のメカニズム – ファーストプライスオークション
Ethereumのトランザクション手数料というと、トランザクションの実行での掛かったガス(Gas Used by Transaction)とユーザーの指定するガスのプライス(Gas Price)をかけ合わせた値となります。このトランザクション手数料は報酬としてマイナーに支払われます。
一つのブロックに収めるトランザクションの数はブロックのGas Limitで限られているので、最高な収益を得られるため、マイナーは基本的がGas Priceの高い順にトランザクションを採用します。この手数料メカニズムはファーストプライスオークションと呼ばれます。
ファーストプライスオークションだとGas Priceの予測が難しく、手数料の過払いやトランザクションの再送が発生してしまう非効率の問題を抱えています。
EIP−1559前のトランザクションはlegacy transactionsとも呼ばれて、Txn Typeは0と表記されています。
1.2 EIP-1559のメカニズム – 固定価格セール
EIP-1559は、イーサリアムのトランザクション手数料の計算方法とそれらの手数料の行き先を変更しました。Gas Limitの変更がありません。
Gas Priceの代わりに、「Base Fee Per Gas」、「Priority Fee Per Gas」、「Max Fee Per Gas」を新たにガスメカニズムに組み込んでいます。
ブロックに含まれるすべてのトランザクションは、そのブロックのBase Fee (Base Fee Per Gas * Gas Used by Transaction)を支払う必要があり、トランザクションを通すために必要最低限のガス代となります。この支払いはマイナーに支払われずBurn(バーン、焼却)されることになります。Burnの仕組みの導入で、ETHにデフレ性が帯びることになるかと思われます。
トランザクションを送信する人々が、次のブロックに含まれる明示的な「Base Fee」があるため、必要なガスの量について多くを推測する必要がなくなりますね。 トランザクションに優先順位を付けたいユーザーまたはアプリケーションの場合、マイナーに支払うための「ヒント」を追加できたと言えます。
ブロックは、ターゲットブロックサイズ(target block size)の2倍まで大きくすることができます。 たとえば、15Mガスをターゲットとする場合、最大ブロックサイズは30Mガスになれます。
Base Fee Per Gasはそのブロックで固定値となり、ブロックごとに調整され、ターゲットよりも大きいブロックでは増加し、ターゲットよりも小さいブロックでは減少します。
トランザクションの混雑具合で変化。例えば:
・ 前のブロックがちょうど50%満たされている場合、Base Fee Per Gasは変更されません。
・ 前のブロックが100%一杯だった場合、Base Fee Per Gasは次のブロックで最大12.5%増加します。
・ 前のブロックが50%を超え、100%未満の場合、Base Fee Per Gasの増加は12.5%未満になります。
・ 前のブロックが0%いっぱいだった場合、つまり空の場合、Base Fee Per Gasは次のブロックの最大12.5%を減らします。
・ 前のブロックが0%を超え、50%未満の場合、Base Fee Per Gasは12.5%未満減少します。
需要が急速に増加している期間やブロック内の特定の位置に即座に含めるなどの特別な扱いを求めるユーザーは、Priority Fee(Priority Fee Per Gas * Gas Used by Transaction)を、トランザクション手数料のチップとして、マイナーに支払います。つまり、マイナーに支払われる承認作業への対価とされています。Priority Fee Per Gasを高く設定することでトランザクションの優先度が高まるようになっています。
Max Fee Per Gasは、トランザクションをブロックに含めるためにガスの単位あたりに支払うことのできる絶対的な最大額です。
Base Fee Per Gas + Priority Fee Per Gas > Max Fee Per Gasの場合、Max Fee Per Gasの値でガス手数料を計算します。
手数料 = Max Fee Per Gas * Gas Used by Transaction
Base Fee Per Gas + Priority Fee Per Gas <= Max Fee Per Gasの場合、
手数料 =(Base Fee Per Gas + Priority Fee Per Gas) * Gas Used by Transaction
EIP−1559前のトランザクションのTxn Typeは2と表記されています。
2. EIP-1559 JSON RPC の変更
EIP-1559は、新しいトランザクションタイプ(0x02)を導入し、ブロックヘッダーに新規に(baseFeePerGas)フィールドを追加しました。 大まかに言えば、トランザクションまたはブロックのいずれかを返すものはすべて、EIP-1559の影響を受けています。
主なAPIの変更は以下の通りです。
2.1 eth_feeHistory
新規追加されたAPIです。
各ブロックのBase Fee Per Gasの履歴データを返します。
2.2 eth_call
EIP-1559 transactionのcall対応で、maxPriorityFeePerGasとmaxFeePerGasが追加されました。
2.3 eth_getBlockBy*
新しいフィールドbaseFeePerGasが追加されました。
2.4 eth_getRawTransaction*
RLP encoded EIP-1559 transactionsも追加されました。
2.5 eth_getTransactionBy*
EIP-1559トランザクションには、maxPriorityFeePerGasとmaxFeePerGasの2つの新しいフィールドが追加されました。gasPriceフィールドが廃棄されました。
2.6 eth_getTransactionReceipt
新しいフィールドeffectiveGasPriceがレシートに追加されました。
Pre-Londonでは、トランザクションのgasPriceと同じです。
ロンドン後、支払われた実際のガス価格に等しい。
この計算は、トランザクションがEIP-1559トランザクションであるかどうかによって異なります。
2.7 eth_getUncleBy*
新しいフィールドbaseFeePerGasが追加されました。
2.8 eth_sendTransaction
EIP-1559トランザクションフィールドmaxPriorityFeePergasおよびmaxFeePerGasがサポートされるようになりました。
これらのフィールドが省略されている場合、EIP-1559トランザクションとされて、クライアントはこれらのフィールドの妥当な値を計算します。
レガシートランザクション(legacy transactions)は、gasPriceを指定することで引き続き送信できます。
2.9 eth_estimateGas
gasPriceまたはmaxFeePerGasとmaxPriorityFeePerGasのいずれかが必要になりました。
以前は、0で埋めることができたため、gasPriceを省略しても問題ありませんでした。
現在、baseFeePerGasを考慮に入れる必要があり、手数料の支払いはbase feeをカバーするのに十分な大きさである必要があります。
2.10 eth_sendRawTransaction
RLP encoded EIP-1559がサポートされています。
3. Web3.js
デモでは”web3″: “^1.5.2″でトランザクションを投げてみました。テスト用ソースは以下の通りです。
const Web3 = require('web3'); const network = 'rinkeby'; async function main() { // Configuring the connection to an Ethereum node const web3 = new Web3(new Web3.providers.HttpProvider('https://rinkeby.infura.io/v3/<YOUR INFURA ID>')); // Creating a signing account from a private key const signer = web3.eth.accounts.privateKeyToAccount('<YOUR PRIVATE KEY>'); web3.eth.accounts.wallet.add(signer); // gasPrice infro const gp = await web3.eth.getGasPrice(); console.log(`getGasPrice result is => ${gp} wei`); // Creating the transaction object const tx = { from: signer.address, to: '0x8f072ebbc05278ff6a39e05c02da65887d14e8f3', value: '1234', data: '0x', //gasPrice: '9000000000', //type: 0, //maxPriorityFeePerGas: '2000000000', //maxFeePerGas: '10000000000', }; // Assigning the right amount of gas tx.gas = await web3.eth.estimateGas(tx); console.log(`gasLimit => ${tx.gas.toString()}`); // Sending the transaction to the network const receipt = await web3.eth.sendTransaction(tx).once('transactionHash', txhash => { console.log(`Mining transaction ...`); console.log(`https://${network}.etherscan.io/tx/${txhash}`); }); // The transaction is now on chain! console.log(`Mined in block ${receipt.blockNumber}`); console.log(`effectiveGasPrice is => ${receipt.effectiveGasPrice}`); //block info const bl = await web3.eth.getBlock('pending'); console.log(`baseFeePerGas of the block => ${bl.baseFeePerGas}`); } main();
3.1 レガシートランザクションを投げる
// Creating the transaction object const tx = { from: signer.address, to: '0x8f072ebbc05278ff6a39e05c02da65887d14e8f3', value: '1234', data: '0x', gasPrice: '9000000000', };
getGasPrice result is => 1000000010 wei gasLimit => 21000 Mining transaction ... https://rinkeby.etherscan.io/tx/0xc57c873c5c02425828e6ebd02b7589c791b44808e59d0a42458e70928e11b178 Mined in block 9405842 effectiveGasPrice is => 0x218711a00 baseFeePerGas of the block => 0xb
テストネットでのBase Fee Per Gasが11 wei (0.000000011 Gwei)ですが、実際に支払ったのは設定したGas Price:9000000000wei (9 Gwei)となりましたね。
3.2 EIP-1559トランザクションを投げる
3.2.1 デフォルト
transaction objectで、gasPrice、maxPriorityFeePerGas、maxFeePerGasをいずれも指定しないデフォルトの場合EIP-1559トランザクションとなります。
// Creating the transaction object const tx = { from: signer.address, to: '0x8f072ebbc05278ff6a39e05c02da65887d14e8f3', value: '1234', data: '0x', };
結果は以下の通りです。
getGasPrice result is => 1000000010 wei gasLimit => 21000 Mining transaction ... https://rinkeby.etherscan.io/tx/0xb0adcb48c87f34ee9de5b45723f3dc7e4aac42dc204c7e02abff7edd19c69f9c Mined in block 9405893 effectiveGasPrice is => 0x3b9aca0a baseFeePerGas of the block => 0xa
maxPriorityFeePerGas、maxFeePerGasは適切な値で入れられましたね。
Gas Price = Base Fee Per Gas + Max Priority Fee Per Gasとなったことを確認できました。
これで過払いを防げるかと思っています。

3.2.2 maxPriorityFeePerGasとmaxFeePerGasを指定
transaction objectで、maxPriorityFeePerGasとmaxFeePerGasを指定する場合、type: 2を省略しても、EIP-1559トランザクションとなります。
// Creating the transaction object const tx = { from: signer.address, to: '0x8f072ebbc05278ff6a39e05c02da65887d14e8f3', value: '1234', data: '0x', maxPriorityFeePerGas: '2000000000', maxFeePerGas: '10000000000', };
結果は以下の通りです。
getGasPrice result is => 1000000010 wei gasLimit => 21000 Mining transaction ... https://rinkeby.etherscan.io/tx/0xb685b3479e12502c277645258fbfda2f3bd2b1f3332e0f524078987b0d7e3332 Mined in block 9405927 effectiveGasPrice is => 0x7735940a baseFeePerGas of the block => 0xa
maxPriorityFeePerGasとmaxFeePerGas指定の通りに反映されましたね。
Gas Price = Base Fee Per Gas + Max Priority Fee Per Gasとなったことを確認できました。
そして、Base Fee Per Gas + Max Priority Fee Per Gas < Max Fee Per Gasとなったことも確認できました。

では、transaction objectで、maxPriorityFeePerGasはmaxFeePerGasより大きい場合、どのようなEIP-1559トランザクションとなれますか。
// Creating the transaction object const tx = { from: signer.address, to: '0x8f072ebbc05278ff6a39e05c02da65887d14e8f3', value: '1234', data: '0x', maxPriorityFeePerGas: '90000000000', maxFeePerGas: '10000000000', };
結果は以下の通りです。
Error: Returned error: err: max priority fee per gas higher than max fee per gas:
4. Ethers.js
const { ethers } = require('ethers'); async function main() { // Configuring the connection to an Ethereum node const network = 'rinkeby'; const provider = new ethers.providers.InfuraProvider(network, '<YOUR INFURA ID>'); // Creating a signing account from a private key const signer = new ethers.Wallet('<YOUR PRIVATE KEY>', provider); // Creating and sending the transaction object const tx = await signer.sendTransaction({ to: '0x8f072ebbc05278ff6a39e05c02da65887d14e8f3', value: '1234', data: '0x', gasLimit: '0x5208', //gasPrice: '8000000000', //type: 2, //maxPriorityFeePerGas: '2000000000', //maxFeePerGas: '10000000000', }); console.log('Mining transaction...'); console.log(`https://${network}.etherscan.io/tx/${tx.hash}`); // Waiting for the transaction to be mined const receipt = await tx.wait(); // The transaction is now on chain! console.log(`Mined in block ${receipt.blockNumber}`); } main();
4.1 レガシートランザクションを投げる
transaction objectで、gasPriceを指定し、type: 0を省略する場合、レガシートランザクションとなれますか。
// Creating and sending the transaction object const tx = await signer.sendTransaction({ to: '0x8f072ebbc05278ff6a39e05c02da65887d14e8f3', value: '1234', data: '0x', gasPrice: '8000000000', });
結果は以下の通りです。
Mining transaction... https://rinkeby.etherscan.io/tx/0x09b961871be145d319702ea19a8de4b7fcb5f97225ac2ca30db784394a793258 Mined in block 9406005
なんとEIP-1559トランザクションとなりました。
ただし、Gas Priceは指定の通りでした。そして、Max Priority Fee Per Gas = Max Fee Per Gas = Gas Priceとなったことも確認できました。

さて、transaction objectで、gasPriceを指定し、type: 0も指定する場合、レガシートランザクションとなれますか。
// Creating and sending the transaction object const tx = await signer.sendTransaction({ to: '0x8f072ebbc05278ff6a39e05c02da65887d14e8f3', value: '0x1234', data: '0x', gasPrice: '0x218711a00', gasLimit: '0x5208', type: 0, });
結果は以下の通りです。
Mining transaction... https://rinkeby.etherscan.io/tx/0xd8335ca2562be49733bfbc7005a94c07fb23aa8879cee57c0dd22087c43c2774 Mined in block 9406096
今回はレガシートランザクションとなりました。Gas Priceも指定の通りでした。

4.2 EIP-1559トランザクションを投げる
transaction objectで、maxPriorityFeePerGasとmaxFeePerGasを指定する場合、type: 2を省略しても、EIP-1559トランザクションとなります。
// Creating and sending the transaction object const tx = await signer.sendTransaction({ to: '0x8f072ebbc05278ff6a39e05c02da65887d14e8f3', value: '0x1234', data: '0x', maxPriorityFeePerGas: '2000000000', maxFeePerGas: '10000000000', });
結果は以下の通りです。
Mining transaction... https://rinkeby.etherscan.io/tx/0x9c3a01da496fe76080e9953d2f854cd07d7770f612a1944e9d165f1bcbeb3503 Mined in block 9406121
maxPriorityFeePerGasとmaxFeePerGas指定の通りに反映されましたね。
Gas Price = Base Fee Per Gas + Max Priority Fee Per Gasとなったことを確認できました。
そして、Base Fee Per Gas + Max Priority Fee Per Gas < Max Fee Per Gasとなったことも確認できました。

5. まとめ
このサイトからすると、EIP-1559対応バージョンのクライアントの採用率は既に99%を超えていますね。マイナーがEIP-1559への認可が高いと裏付けています。
このサイトからすると、EIP-1559の採用以来、既に422063ETH以上のイーサが焼却されていましたね。ユーザはEIP-1559への認可が高いと裏付けています。
節ガス代かつ効率よくトランザクションを発送するためには、maxPriorityFeePerGasとmaxFeePerGasはどのように指定したらいいでしょうか。
MetaMaskの設定に参照して頂くのは良い方法ではないかと思っています。
そして、このサイトにも参照して、ガスの見積もりも可能ではないかと思います。
我々利用者には、EIP-1559は本当により効率が良いかについて、DeFiとNFTでネットワークの混雑は常態化となる現状のイーサリアムには依然として判断し難いでしょう。
これから動向も見守っていきます。
6. 最後に
次世代システム研究室では、グループ全体のインテグレーションを支援してくれるアーキテクトを募集しています。アプリケーション開発者の方、次世代システム研究室にご興味を持って頂ける方がいらっしゃいましたら、ぜひ募集職種一覧からご応募をお願いします。
皆さんのご応募をお待ちしています。
グループ研究開発本部の最新情報をTwitterで配信中です。ぜひフォローください。
Follow @GMO_RD