アルゴリズムとかオーダーとか

仕事で勉強したことなどをまとめてます

BlockのgasLimitとgasUsedの関係とマイナーの戦略

www.youtube.com

僕もよく拝見している、やさしい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を超えてはいけない。」

yello_paper_p12
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をプロットしたグラフである。

gasLimit and gasUsed graph

グラフからもわかる通り、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も結構多く含まれていることが分かった。


以下、解析グラフを掲載する。