僕もよく拝見している、やさしいDeFiチャンネルの「やさしいEthereumガスの解説&きびしいEIP1559猛勉強」の動画内でこんな疑問があがっていた。「マイナーはUsedGasを見てBlockに含めるTransactionを決定しているのか?」この辺りは自分も明確に調べたことがなかったので、今一度整理し直してみる。動画のなかで、自分の投稿をネタにしてもらえたのでそのお礼記事でもある。
今回は、仕様(YellowPaper)、実装、実データの3点から整理してみた。
結論
結論だけ先に述べる。
- Minerの戦略としては基本的に priorityFeePerGas でTransactionを選別していると思われる。MEVも積極的に考慮してると思われる。
- Ethereumの使用上、BlockのgasLimitのチェックはTransactionで実際に消費されたgas量がlimitを越えなければよい。つまりtransactionのgasUsedの総合計 < block.gasLimitとなる。
- ただし、transaction実行前には、transactionのgasLimitを現在消費したgasUsedの合計と加算したときにblock.gasLimitを超えないかをチェックしているので、transactionのgasLimitは小さいほうがよりブロックに取り込まれやすいといえる。
block.gasLimitはtx.gasLimitの総計が超えないようにしてるのかとずっと勘違いしていた。この点は今回の調査をもって訂正する。
まず前提としてEIP-1559以降のTransactionについてBlockとTransactionのgasLimitとusedGasの関係について調査する。
Minerの戦略については、Miner独自にマイニングプログラムを組むことで自由に決定できるので、ある意味どのような戦略もとれる。そのため、今回はEthereumの仕様上、BlockのgasLimitへの判定はTransactionのgasLimitで判定されるのか、usedGasで判定されるのかを調査する。
仕様
BlockのgasLimitとTransactionのgasLimitについてはYellow PaperのP12に計算方法が記載されていた。
「TransactionのgasLimitの総計がBlockのgasLimitを超えてはいけない。」
the sum of the transaction’s gas limit, Tg, Tgで定義されたtransactionのgasLimitと、 and the gas utilised in this block prior, given by `(BR)u, また、前のブロックで使用されたガスを加算した結果が must be no greater than the block’s gasLimit BlockのgasLimitを超えてはいけない。
上記より、transactionはtransaction gasLimit + total gasUsedがblock.gasLimitを超えていなければそのtransactionはblockに取り込まれることが分かった。
今まで自分はtransaction.gasLimitの総計がblock.gasLimitを超えてはいけないのではと勘違いしていた。。。
実装
いつも通り、go-ethereumのソースコードを見ていく。
gasLimitに関するチェックは以下の箇所で行われている。ソース内コメントの3番のチェックがそれである。
https://github.com/ethereum/go-ethereum/blob/v1.10.21/core/state_transition.go#L275-L289
func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { // First check this message satisfies all consensus rules before // applying the message. The rules include these clauses // // 1. the nonce of the message caller is correct // 2. caller has enough balance to cover transaction fee(gaslimit * gasprice) // 3. the amount of gas required is available in the block // 4. the purchased gas is enough to cover intrinsic usage // 5. there is no overflow when calculating intrinsic gas // 6. caller has enough balance to cover asset transfer for **topmost** call // Check clauses 1-3, buy gas if everything is correct if err := st.preCheck(); err != nil { return nil, err }
上記箇所から、preCheck() -> buyGas() -> GasPool#subGas()の順に呼び出されており、
https://github.com/ethereum/go-ethereum/blob/v1.10.21/core/state_transition.go#L214
https://github.com/ethereum/go-ethereum/blob/v1.10.21/core/state_transition.go#L192
最後の、GasPool#subGas()の中でチェックが行われている。
https://github.com/ethereum/go-ethereum/blob/v1.10.21/core/gaspool.go#L39-L45
transactionの実行が完了すると残ったgasをrefundGas()でGasPoolに戻している。これにより、gasUsed分のみが最終的に差し引かれた状態となる。
https://github.com/ethereum/go-ethereum/blob/v1.10.21/core/state_transition.go#L336-L342
if !rules.IsLondon { // Before EIP-3529: refunds were capped to gasUsed / 2 st.refundGas(params.RefundQuotient) } else { // After EIP-3529: refunds are capped to gasUsed / 5 st.refundGas(params.RefundQuotientEIP3529) }
実装からもYellowPaperと同様のチェックが行われていることが分かった。
なお、このメソッドは StateProccessor#Process()から呼び出されている。
Process() -> applyTransaction() -> ApplyMessage()の順で呼び出されている。
https://github.com/ethereum/go-ethereum/blob/v1.10.21/core/state_processor.go#L82
https://github.com/ethereum/go-ethereum/blob/v1.10.21/core/state_processor.go#L101
https://github.com/ethereum/go-ethereum/blob/671094279e8d27f4b4c3c94bf8b636c26b473976/core/state_transition.go#L181
実データ
ここまでで答えは出ているが、最後に実際のデータを見てみる。
以下は15,416,601~32ブロックのtransaction.gasLimitの総計とblock.gasLimitとblock.gasUsedをプロットしたグラフである。
グラフからもわかる通り、gasUsedが多いときはblockに含まれるtxのgasLimitの総計が30Mを超えている。
以上のことから、blockに含めることができるtransactionは実際に消費された量がblock.gasLimitを超えない分だけ含めることができることが分かった。
補足:マイナーのtransaction選別戦略について
以下の5項目についてチェックしてみた
- gasLimit
- gasUsed
- maxFeePerGas
- tx type=2以外のものはgasPriceに置き換え
- maxPriorityFeePerGas
- tx type=2以外のものはgasPrice - baseFeePerGasに置き換え
- actualPriorityFeePerGas
- 実際に取得できるpriorityFeePerGas
- actual_fee
20のminerが生成したblockに含まれるtransaction順序通りに並べたときのグラフを掲載する。
数が多いので先に考察を記載する。基本的にmaxPriorityFeePerGasのみを優先的に見て、それ以外は特に見てないように思われる。maxPriforityFeePerGasにも若干波が見られるのはMEV(やfront-running)も含まれるからなのかもしれない。
MEVについては自分もあまり理解していない。以下のブログが詳しいので参照してほしい。
vividot-de.fi
あと、maxPriorityFeePerGas=0のtransactionも結構多く含まれていることが分かった。
以下、解析グラフを掲載する。