はじめに
『DApps開発入門』という本や色々記事を書いているかるでねです。
今回は、P-ChainとX-Chainに、通信量・DB読み書き・署名検証などの負荷別に手数料率を自動調整し、ベース手数料と優先手数料を分けて混雑時は優先手数料に応じて取り込みに優先度を反映する手数料方式を導入する提案しているACP83についてまとめていきます!
以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。
他にも様々なEIP・BIP・SLIP・CAIP・ENSIP・RFC・ACPについてまとめています。
概要
ACP83は、AvalancheのP-ChainとX-Chainに「動的かつ多面的な手数料方式」を導入するものです。
動的手数料とは、ネットワークの利用状況に応じて手数料が自動的に上下する仕組みであり、混雑して利用率が高まったときには手数料が上がり、余裕があるときには下がることでチェーンの安定性を保ちます。
多面的手数料とは、ネットワークが消費する複数種類のリソースをそれぞれ独立に扱い、それぞれに応じて手数料を計算する方式です。
例えば以下のようなリソースがあります。
リソース | 内容 |
---|---|
帯域幅(Bandwidth) | トランザクションやブロックをネットワーク上で伝送する時の通信量 |
ステート(ChainState) | チェーン上の状態情報の保存・更新による負荷 |
読み書き処理(Read/WriteThroughput) | ステートを読み出す・書き込む際の処理量 |
CPU | トランザクションやスマートコントラクト実行時の計算処理 |
これらを別々に測定して独自に手数料を調整することで、どれか1つのリソース需要が高くても他のリソースにまで不必要に影響が及ばないようにし、より最適に近い利用を実現します。
さらに手数料は2つの部分に分かれます。
-
ベース手数料(base fee)
ネットワークが各ブロックごとに算出するした各リソースの適正価格。 -
優先手数料(priority fee)
ベース手数料を超えて支払われた分。
追加でBurnされ、トランザクションをより早く取り込んでもらうための優先度の指標として使われます。
動機
現状、P-ChainとX-Chainでは手数料が固定されており、場合によってはゼロに設定されています。
固定手数料は予測がしやすい一方で、ネットワークが混雑しても価格が変わらないため、過負荷時に安定性を維持する仕組みとしては不十分です。
これに対し、C-Chainではすでに動的手数料が導入されており、利用が集中しやすい環境でも安定性に寄与しています。
そのため、P-ChainとX-Chainにも同様の仕組みを導入することで、Avalanche全体の安定性と耐性を高めようとするのが本提案の狙いです。
ただし、C-Chainと同じ単一の調整方式を採用するのではなく、ACP83では「多面的手数料方式」を導入します。
各リソースの手数料は独立して設定され、それぞれの利用状況に基づいて指数関数的に更新されます。
この方式は、AvalancheのHyperSDKですでに使われており、学術研究でも効率性が確認されています。
最後に、手数料を「ベース手数料」と「優先手数料」に分けることで、リソース利用に見合った公平な価格設定と、ユーザーによるトランザクション優先度付けを両立させます。
仕様
手数料方式の構成要素
トランザクションの複雑さを表すために、4つのリソース項目を定義します。
それぞれは異なる処理コストを反映しています。
項目 | 説明 |
---|---|
Bandwidth | トランザクションサイズ(バイト単位)。AvalancheGoのコーデックでエンコードされたサイズで測定され、ネットワーク上で配布する際の通信量を表します。 |
Reads | トランザクション検証時に必要なデータベースの読み取り回数。UTXOや関連するステートの読み取りを含みます。 |
Writes | トランザクション検証後に必要なデータベースの書き込み回数。新しいUTXOの生成やステート更新が該当します。 |
Compute | 検証対象となる署名の数。UTXOに関する署名や、特定操作の承認に必要な署名を含みます。 |
それぞれの項目について以下が定義されます。
-
手数料率 $r_i$
各項目の複雑さ $u_i$ に応じて支払うAVAX建ての価格。 -
ベース手数料
トランザクションを受け入れるために最低限必要な手数料。
以下の式で表されます。
$$
base \ fee = \sum_{i=0}^3 r_i \times u_i
$$
-
優先手数料
ベース手数料に加えて任意で支払う追加分。
この追加分はBurnされ、ブロックに優先的に取り込まれるための指標となります。
動的手数料更新の仕組み
手数料率は固定ではなく、ネットワークの利用状況に応じて変動します。
各ブロックは新しい処理負荷をもたらす可能性があり、ブロックに含まれるトランザクションが複雑であったり、ブロックの生成間隔が短かったりすると、混雑度が上がります。
設計上の目標は次のとおりです。
- 負荷が高まったときには手数料をすばやく引き上げる。
- 負荷が減ったときには手数料をすばやく引き下げる。
このときの基準は「目標複雑度率(Target Complexity Rate, $T$)」です。
これは「1秒ごとに複雑度 $T$ のブロックを処理することを理想とする」という基準で、それを超えると混雑と見なして手数料を増やします。
累積超過複雑度と更新式
各リソース項目ごとに「累積超過複雑度」を管理します。
これは、ブロックの生成間隔とその間に発生した負荷を考慮して更新されます。
-
現在のブロック $B_t$
- $t$
タイムスタンプ - $\Delta C_t$
累積超過複雑度
- $t$
-
新しいブロック $B_{t+\Delta T}$
- $t + \Delta T$
新しいブロックのタイムスタンプ - $C_{t+\Delta T}$
新しいブロックの複雑度
- $t + \Delta T$
このとき、手数料率 $r_{t+\Delta T}$ は以下の式で定義されます。
$$
r_{t+\Delta T} = r^{min} \times e^{\frac{max(0, \Delta C_t - T \times \Delta T)}{Denom}}
$$
- $r_{t+\Delta T}$
新しいブロック $B_{t+\Delta T}$ に適用される手数料率。 - $r^{min}$
最小手数料率。
この値より下がることはありません。 - $\Delta C_t$
直前までの「累積超過複雑度」。
つまり、過去のブロックによってどれくらい処理が溜まっているかを表す値です。 - $T \times \Delta T$
経過時間に応じた「処理可能な複雑度」。
もしブロック間隔が長ければ、ネットワークは余裕を持って処理できることを意味します。 - $max(0, \Delta C_t - T \times \Delta T)$
「処理しきれずに残っている複雑度」。0未満にはならないように調整しています。 - $Denom$
正規化のための定数。
どの程度の超過で手数料が増加するかを調整します。
ネットワークが余裕を持って処理できている場合($\Delta C_t \leq T \times \Delta T$)、分子は0となり、$r_{t+\Delta T} = r^{min}$ に収束します。
ネットワークが過負荷状態の場合($\Delta C_t > T \times \Delta T$)、指数関数的に手数料が上昇し、利用者に「混雑しているからもっと払わないと通らない」ことを示します。
新しいブロックが受け入れられた後、累積超過複雑度は次のように更新されます。
- $r^{min}$
最小手数料率 - $T$
目標複雑度率 - $Denom$
正規化のための定数
$$
\Delta C_{t+\Delta T} = max(0, \Delta C_t - T \times \Delta T) + C_{t+\Delta T}
$$
- $\Delta C_{t+\Delta T}$
新しいブロック後に更新された累積超過複雑度。 - $\Delta C_t$
前の時点での累積超過複雑度。 - $T \times \Delta T$
ブロック間の時間に応じて処理できたはずの複雑度。
これを差し引くことで、時間経過による「処理の消化分」を反映します。 - $max(0, \Delta C_t - T \times \Delta T)$
負の値にはならないように調整し、残っている未処理分だけを残します。 - $C_{t+\Delta T}$
新しいブロックに含まれる処理負荷(トランザクションの複雑さ)。
これを追加して累積を更新します。
ブロック間隔が長ければ、その間に処理が進むため「溜まっていた負荷」は小さくなります。
新しいブロックが重い処理を持つと、その分だけ累積超過が増加します。
この値が大きくなると、前の式により手数料が指数的に跳ね上がる仕組みです。
更新式の意味
この方式により、以下の動きが保証されます。
- ブロックが重い処理を伴う場合や、短時間で連続して生成される場合には手数料率が上がります。
- ブロックが軽量であったり、生成間隔が長ければ手数料率は下がり、最小値に近づきます。
各リソース項目ごとにパラメータ($r^{min}$, $T$, $Denom$)を独立して設定できるため、項目ごとに最適な調整が可能です。
ブロック検証ルール
動的な手数料方式が有効化されると、ブロックの処理方法に変更が加わります。
具体的には以下の2点です。
項目 | 内容 |
---|---|
ブロックの複雑さの上限 | 各リソース項目ごとに「最大ブロック複雑度(Max)」を定義します。ブロックの複雑度 $C$ が $Max$ 以下である場合のみ、そのブロックは有効とみなされます。 |
トランザクション手数料の検証 | 各トランザクションを検証するとき、最低限のベース手数料を支払えることを確認します。ベース手数料と、追加で支払われる優先手数料の両方はBurnされます。 |
これにより、過度に重いブロックが無効化され、また手数料を正しく負担するトランザクションだけが処理される仕組みとなります。
ユーザー体験
ウォレットはどのように手数料を見積もるのか
AvalancheGoノードは新しいAPIを提供し、現在の手数料率や次のブロックで予想される手数料率を公開します。
手数料率はブロックごとに変動する可能性があるため、ウォレットはこの情報を使ってトランザクションの手数料を計算し、支払いに使用するUTXOを選択します。
また、AvalancheGoには fees.Calculator
という構造体が用意されており、ウォレットや関連プロジェクトはこれを利用して手数料を計算できます。
ウォレットはどうやって手数料を上げて再送できるのか
ウォレットは単純にトランザクションを再送信すればよい設計になっています。
現在のAvalancheGo実装では、メモリプール(mempool)に存在するトランザクションであっても、手数料率が最新の基準より低いものは破棄されます。
- トランザクションはメモリプールに入った時点で一度有効とみなされます。
- しかし、次のブロックに取り込まれる直前に、最新の基準で再度検証されます。
- その時に手数料が不足していればトランザクションは破棄され、ウォレットは手数料を上乗せして再送信できます。
なお、優先手数料を多めに支払っておくと、手数料率が少し上昇した場合でも有効性を維持できる「バッファ」として機能します。
逆に、ベース手数料のみを支払っているトランザクションは、手数料率が上がった瞬間に無効になりやすい特徴があります。
優先手数料はどのようにブロックへの取り込みを早めるのか
AvalancheGoのメモリプールは、優先手数料の額に基づいてトランザクションを並び替えるように再設計されます。
これにより、優先手数料を支払っているトランザクションは、依存関係(同じUTXOの使用など)を壊さない範囲で、先にブロックへ取り込まれることが保証されます。
つまり、ユーザーが多めに優先手数料を支払うことで、トランザクションを早く処理してもらう可能性が高まります。
互換性
P-ChainとX-Chainの手数料方式を切り替えるには、有効化のための必須アップグレードが必要になります。
アップグレード後は、手数料の計算方法やAPIの振る舞いが変わるため、ウォレット側も新方式に対応する修正が求められます。
対応していないウォレットは、手数料見積もりや再送の挙動で不整合が生じる可能性があります。
参考実装
実装は複数のPR/Issueに分解されています。
進捗や議論は以下で追跡できます。
対象 | 追跡URL |
---|---|
P-Chain | https://github.com/ava-labs/avalanchego/issues/2707 |
X-Chain | https://github.com/ava-labs/avalanchego/issues/2708 |
実装上の重要ポイントは、各チェーン×各手数料項目(Bandwidth/Reads/Writes/Compute)について、更新式のパラメータを適切にチューニングすることです。
更新式のチューニング
基本的な考え方は、すでに受け入れられたブロックの複雑さ(complexity)を計測し、そこから各パラメータを導出することです。
履歴データは以下のリポジトリにまとまっています。
なお、ここでの説明ではP-Chain特有の提案ブロックなどの詳細は一旦省き、履歴処理時に個別事情を織り込む前提としています。
パラメータ | 設定方針 |
---|---|
目標ブロック複雑度率 $T$ | 既存ブロックの複雑さ分布を集計し、十分に高い分位点(quantile)を採用して設定します。 |
最大ブロック複雑度 $Max$ | 設定が最も難しい項目です。過去には参照UTXOが1,000超の大型Txもありました。このような極端なケースまで常に許容するほど高く設定すると、実質的に上限が無いのと同じになります。一方で、UTXOの集約(consolidation)は許容・推奨したいため、下げすぎも望ましくありません。推奨手順は次のとおりです。 1) まず前項の方法で目標複雑度率 $T$を求める。 2) 連続ブロック間の経過時間の中央値を求める。 3) $T$ ×(2で求めた中央値)=目標ブロック複雑度の目安になる。 4) $Max$はその目標値の約50倍に設定する。 |
正規化係数 $Denom$ | 履歴上の最大ピーク(短時間に最大の複雑さが連続した区間)を見つけ、そのピーク時に手数料率が約1万倍に跳ね上がるように調整します。平常時のミリAVAX水準から、ピークでは数十AVAXまで上がるスケール感を想定しています。 |
最小手数料率 $r^{min}$ | 現行の固定手数料と大きく乖離しない水準に置き、平常時の利用コストが急に変わらないようにします。 |
セキュリティ
この新しい手数料方式は、高負荷時に発行を控える経済的誘因を与えることで、ネットワークの安定性向上に寄与することが期待されます。
平常時は一般に低い水準にとどまりますが、突然の負荷増(ブロックがより埋まる)に対しては、動的手数料アルゴリズムが手数料率を引き上げ続けて負荷が下がるまで抑制を働かせます。
負荷低減は、主に次の2つの経路で起こります。
-
手数料率が足りない未確定Txのドロップ
メモリプールにあるTxは、必要手数料を満たさなくなった時点で排除されます。 -
ユーザー側の発行延期
コスト最適化を図るユーザーは、手数料率が下がるまで発行を遅らせる選択をしやすくなります。
さらに、上で述べた指数関数的な手数料更新機構は、ユーザーが戦略的に発行を遅らせて十分低下したところで一気に大量発行するといった行動に対しても頑健であることが示されています(出典:https://ethresear.ch/t/multidimensional-eip-1559/11651)。
この性質により、特定の戦略でネットワーク全体を恒常的に混雑させることの難易度が高まります。
最後に
今回は「P-ChainとX-Chainに、通信量・DB読み書き・署名検証などの負荷別に手数料率を自動調整し、ベース手数料と優先手数料を分けて混雑時は優先手数料に応じて取り込みに優先度を反映する手数料方式を導入する提案しているACP83」についてまとめてきました!
いかがだったでしょうか?
質問などがある方は以下のTwitterのDMなどからお気軽に質問してください!
他の媒体でも情報発信しているのでぜひ他も見ていってください!