はじめに

『DApps開発入門』という本や色々記事を書いているかるでねです。

今回は、AvalancheのP-Chainに動的な手数料メカニズムを導入し、ネットワーク混雑時には手数料が上がり、空いているときには下がる仕組みを提案しているACP103についてまとめていきます!

以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。

他にも様々なEIP・BIP・SLIP・CAIP・ENSIP・RFC・ACPについてまとめています。

概要

ACP103は、Avalanche の P-Chain(プラットフォーム管理を担うチェーン。サブネットの作成・バリデータの登録やステーキングを扱います)に動的手数料メカニズム(ネットワークの混雑度合い=資源需要に応じて手数料単価を上下させる仕組み)を導入し、将来的には多次元手数料メカニズム(計算・保存・帯域といった複数の資源種類ごとに手数料を分けて課す考え方)への移行を見据えるものです。 同様の仕組みはC-Chain(EVM 互換スマートコントラクト用チェーン)でのネットワークアップグレード「Apricot Phase 3(高負荷時に手数料単価を上げ、低負荷時に下げる調整を導入した段階的改良)」で既に実績があり、需要が過熱すればコスト上昇により利用が抑制され、需要が落ち着けばコスト低下で再び利用が促進されるため、取引の実行・包含コストが市場均衡価格(需給が釣り合う価格)に近づくよう作用します。 現在P-Chainは固定手数料(ネットワーク状況に関係なく一定額を課す方式)で運用されていますが、ACP77(Reinventing Subnets の改善群)に伴う負荷の急増が想定されるため、混雑耐性とスパム・DoS(サービス妨害)耐性を高める目的で、P-Chainを動的手数料へ移行することを提案しています。 なお、X-Chain(アセットの発行・送付に特化したチェーン)は現状の利用度が比較的低く、新機能の投入も行っていないため、余計な技術的複雑さを避ける観点から動的化は後続のACPに先送りします。

動機

ブロックチェーンは計算リソース、ストレージ、ネットワーク帯域などが限られた環境であり、取引(トランザクション)の実行とブロックへの包含には手数料が発生します。 手数料メカニズムは、この資源の供給(ノードが提供できる処理能力)と需要(ユーザーが使用する取引量)のバランスに合わせて価格シグナルを出し、スパム投稿や DoS 攻撃の抑止に機能する必要があります。

固定手数料方式は、ユーザーにとって「わかりやすく、予測しやすい」利点がある一方で、混雑や資源制約を価格に反映できないという欠点があります。 需要が高騰してもコストは一定のため、ユーザーは送付を控える動機にならず、市場均衡価格から乖離したまま取引が積み上がり混雑が解消しにくくなります。 結果として、手数料は「いまの需要に見合った水準」へ調整されず、ネットワーク健全性やユーザー体験(遅延・失敗率)に悪影響を及ぼします。

これに対し、C-ChainはApricot Phase 3で動的手数料メカニズムを採用し、需要が高いときは単価を上げて低いときは下げることで、利用の自然な抑制・促進を通じてネットワーク利用率を適正化してきました。 価格が上がり過ぎれば利用は落ち着き、それに伴って価格も下がるというフィードバックにより、実行・包含コストは市場均衡価格へ収れんしやすくなります。

P-Chain は現状、固定手数料です。 しかし、ACP77によるサブネット周りの改善・拡張により一時的な負荷スパイクが発生する可能性があります。 P-Chain はバリデータとサブネットのライフサイクル管理という基盤機能を担っており、このレイヤが混雑で滞るとエコシステム全体に影響が出ます。 よって、需要変動に応じて価格を調整できる動的手数料へ移行し、スパムやDoSを価格面でも抑止しながら、ピーク負荷時の堅牢性を高めることが適切です。

一方、X-Chainは現状の利用度が低く、新機能の追加も予定していないため、今このタイミングで手数料を動的化すると、運用・実装の複雑が増加する可能性があります。 そこで、X-Chain の動的化は別のACPで段階的に検討し、P-Chainの優先度を上げる判断が提案されています。

また、ACP103は将来的な多次元手数料メカニズムへの導入でもあります。 多次元とは、単一の「ガス単価」だけでなく、計算・ストレージ・帯域など資源ごとに価格を分け、需要に応じて各次元を独立に調整する考え方です。 現時点では将来的な導入にとどめ、まずは単一次元の動的化でP-Chainの耐性と効率を上げて、その後に必要性と費用対効果を見極めながら多次元化へ拡張する提案をしています。

固定手数料と動的手数料の違い(整理)

観点 固定手数料(現行の P-Chain/X-Chain) 動的手数料(C-Chain で実績、P-Chain へ導入提案)
価格の変化 常に一定。需要の影響を受けない 需要が高いと上昇、低いと下降(自動調整)
混雑時の挙動 送付抑制が働きにくく、詰まりが解消しづらい 価格上昇で自然に需要が抑制され、詰まりを緩和
スパム/DoS 耐性 価格面の抑止力が弱い 価格上昇が抑止力として機能
市場均衡価格との乖離 乖離しやすい 収れんしやすい
実装・運用の複雑さ 低い 調整ロジックが必要(ただし C-Chain に実績)
将来の多次元化 直接はつながりにくい 段階的に多次元(資源別)へ拡張しやすい

用語

  • P-Chain Avalancheのプラットフォーム管理用チェーン。 サブネット作成、バリデータ管理、ステーキングなど基盤運用を扱います。
  • C-Chain EVM互換のスマートコントラクト実行用チェーン。 Apricot Phase 3で動的手数料を導入済みです。
  • X-Chain 主にアセットの発行・送付に使われるチェーン。 現状は固定手数料で、今回の動的化は後回しにします。
  • 市場均衡価格 需要と供給が釣り合う価格。 手数料がこの水準に近いほど、ネットワーク資源が効率よく配分されます。
  • 多次元手数料 複数の資源種類ごとに価格を分ける考え方。 まずは単一次元の動的化で基盤を整え、その後の段階で検討します。

仕様

ACP103は、P-Chainに動的手数料を導入するための仕組みと、そのレート制御(どれだけの取引資源をどの速度で消費できるか)を決める仕組みを定義します。 手数料の算定は一時的に「単一次元(1 本のガス指標)」に統合しつつも、将来的には多次元(資源別)の計量へ移行できるような形で設計されています。 さらに、ネットワーク資源の瞬間的な過剰利用を防ぐために、トークンバケット(一定速度で補充されるトークンを消費できる「通信制御アルゴリズム」)を併用します。 これにより、各ブロックで処理可能なガス量の最大値がトークンバケットの状態によって決まる仕組みとなり、許容範囲を超える取引を含むブロックは無効として扱われます。

ガスの次元とガス消費量の定義

P-Chain上のトランザクションが消費する「ガス」は、以下の4種類の資源使用量を基準に計算されます。 ここでいう「ガス」とは、ネットワークが処理を行う際に必要となる計算・データ操作・通信帯域といった資源の使用量を、数値に換算したコストの指標です。

記号 次元(資源) 意味 単位/設定
BB Bandwidth(帯域) トランザクションのブロードキャストで使うネットワーク帯域 トランザクションのバイトサイズ
RR Reads(読み出し) 実行中に行うステート/データベースの読み取り回数** 回数(整数)
WW Writes(書き込み) 実行中に行う状態/データベースの書き込み回数 回数(整数)
CC Compute(計算) 検証・実行で使うCPU 計算量 マイクロ秒(1 マイクロ秒 = 100 万分の 1 秒)

4次元を1つのガスに合算する式は以下です。

G=B+1000R+1000W+4CG = B + 1000R + 1000W + 4C
  • 読み出し・書き込みは1回あたり 1000 ガス、計算は 1 マイクロ秒あたり 4 ガス、トランザクションのバイトは 1 バイトあたり 1 ガスとして加算します。
  • 将来のACPでは、この4項目をひとまとめにして扱う方式をやめて、それぞれの資源の消費量ごとに別々のガス価格を設定して計算する「多次元メカニズム」に拡張できるように設計されています。

手数料調整メカニズム

このメカニズムの目的は、1秒あたりの目標ガス消費量 TT を保つことです。 実際の消費が目標を超えれば価格を上げ、下回れば価格を下げることで、平均的に TT ガス/秒に張り付くように調整します。

超過ガス消費 xx の定義と初期化

  • メカニズム有効化前の初期値
x=0x = 0
  • ブロック bb の構築/実行を開始する時点で、直前のブロックからの経過秒数 Δt\Delta t を用いて減少させます。
x=max⁡(x−T⋅Δt, 0)x = \max\left( x - T \cdot \Delta t,\ 0 \right)
text
ここで $\Delta t$ は、ブロック $b$ のタイムスタンプと親ブロックのタイムスタンプの差(秒)です。
目標消費量 $T$ に対応する分だけ $x$ の値を減算し、もし負の値になる場合は `0` に切り上げます。

ブロック bb のガス価格

ブロック bb に適用されるガス価格は、最小価格 MM を底に持つ指数関数的な関数です。

gas_price(b)=M⋅exp⁡ ⁣(xK)\text{gas\_price}(b) = M \cdot \exp\!\left( \frac{x}{K} \right)
  • MM 最小ガス価格。 この値が下限となり、ガス価格はこれ未満には下がりません。
  • KK ガス価格の変化速度を調整する定数。 超過ガス量 xx が KK だけ増えると、価格はおよそ e≈2.718e \approx 2.718 倍になります。 つまり、KK の値が大きいほど価格の上昇は緩やかになり、小さいほど敏感に上昇します。
  • exp⁡(⋅)\exp(\cdot) 指数関数です。 実装ではEIP4844の仕様に従い、計算負荷を抑えるためにテイラー展開(級数展開)を用いた近似アルゴリズムで値を求めます(下記コードを参照)。

EIP4844については以下の記事を参考にしてください。

python
# EIP-4844 流儀の近似: factor * e ** (numerator / denominator)
def fake_exponential(factor: int, numerator: int, denominator: int) -> int:
    i = 1
    output = 0
    numerator_accum = factor * denominator
    while numerator_accum > 0:
        output += numerator_accum
        numerator_accum = (numerator_accum * numerator) // (denominator * i)
        i += 1
    return output // denominator

ブロック処理後の xx の更新

ブロック bb の処理が終わると、そのブロックで実際に消費した合算ガス GG が確定します。 これを xx に加算します。

x=x+Gx = x + G

この仕組みにより、ブロックで大量のガスが消費されると xx の値が大きくなり、その次のブロックから適用されるガス価格が指数関数的に上昇します。 価格が高くなると利用者は取引を送信しにくくなり、平均的なガス消費量が抑制されます。 一方で、時間経過に応じて Δt\Delta t に基づき xx は減算され、価格は徐々に下がります。 この価格調整の仕組みにより、ガス消費量は平均的に目標値 TT に近い水準に保たれます。

レート制御:トークンバケット

トークンバケットは、1秒あたりに補充される「トークン」(= ここでは消費可能ガス量)の範囲でのみ処理を許可する仕組みです。 瞬間的な消費ピークをバケット容量 CC の範囲に抑えつつ、補充速度 RR によって平常時の処理スループットを決めます。

  • メカニズム有効化前の初期値。
r=0r = 0

rr は現在のバケット内ガス量です。

  • ブロック bb の処理開始時に、親ブロックからの経過時間 Δt\Delta t だけ補充。
r=min⁡ ⁣(r+R⋅Δt, C)r = \min\!\left( r + R \cdot \Delta t,\ C \right)
  • ブロック bb の処理後、ブロックの実ガス消費 GG が確定。
G>rG > r

上記であればそのブロックは不正(invalid)です。

r=r−Gr = r - G

妥当であれば消費分を差し引きます。

この仕組みにより、明示的な「ブロックガス上限」パラメータは不要です。 許容可能な最大量は、その時点の rr と Δt\Delta t による補充 R⋅ΔtR \cdot \Delta t により決まります(上界は C+R⋅ΔtC + R \cdot \Delta t)。

初期パラメータ(アクティベーション時)

パラメータ P-Chain 設定値
TT(1 秒あたり目標ガス) 50,000
MM(最小ガス価格) 1 nAVAX
KK(価格変化の定数) 2,164,043
CC(バケット容量) 1,000,000
RR(毎秒補充ガス量) 100,000

KK は、最大供給に張り付き(R=100,000R = 100{,}000 ガス/秒が持続)続けたときに、約 30 秒ごとに手数料が2倍になるように選ばれています(xx が毎秒 100,000 積み上がると、約 30 秒で xx が KK を超え、exp⁡(x/K)≈e\exp(x/K) \approx e 倍)。

ネットワークの処理能力が増えた場合には、これらのパラメータを調整することで、許容されるガス消費レートを上げる余地があります。

指数関数を用いる理由(価格水準に依存しない変化率の確保)

ガス価格を調整する関数は、現在の価格が低い場合も高い場合も同じ割合で変化することが望まれます。 線形関数を使うと、この条件を満たすことができません。

線形調整の例

bn+1=bn+10xb_{n+1} = b_n + 10x

ここで bnb_n は現在のガス価格、xx は超過ガス量とします。 例えば bn=100,x=1b_n = 100, x = 1 の場合、bn+1=110b_{n+1} = 110 で 10% の上昇です。 しかし bn=10,000,x=1b_n = 10{,}000, x = 1 の場合は bn+1=10,010b_{n+1} = 10{,}010 で、上昇率はわずか 0.1% に留まります。 つまり、価格が大きいときほど上昇率が小さくなり、調整の効果が不均一になります。

指数調整の例

bn+1=bn⋅exb_{n+1} = b_n \cdot e^x

この場合、bn=100,x=1b_n = 100, x = 1 なら bn+1≈271.8b_{n+1} \approx 271.8 で約 171% の上昇です。 bn=10,000,x=1b_n = 10{,}000, x = 1 の場合も bn+1≈27,182.8b_{n+1} \approx 27{,}182.8 で、やはり約 171% の上昇となります。 つまり、価格水準に関係なく一定の割合で変化するという特徴があります。

このように、指数関数を用いることで「変化の割合(上昇率)」を一定に保つことができ、ACP103が exp⁡(x/K)\exp(x/K) を採用している理由はこの性質を活かすためです。

ブロック構築手順

P-Chain のトランザクションは、Burn額(Burnされる AVAX)と消費ガスから「そのトランザクションが支払える最大ガス単価」を決めます。 Mempool(未処理トランザクションの待機領域)は、最大単価の高い順に取り出す優先度付きキュー(priority queue)に置き換えます。 これにより、高い手数料を提示するトランザクションが先に含まれるようになります。 なお、FIFO(先入れ先出し)とは違い、到着順ではなく入札価格順で順序付けます。

  1. トランザクションを構築した時点で、Burn額 =∑(AVAX outputs)−∑(AVAX inputs)= \sum(\text{AVAX outputs}) - \sum(\text{AVAX inputs}) を算出します。 ここで outputs/inputs は、トランザクション内での AVAX の出力/入力の総量です。

  2. 同じ時点で、そのトランザクションが消費する合算ガス GG を一意に計算できます。 これは前述の式 G=B+1000R+1000W+4CG = B + 1000R + 1000W + 4C に基づいており、入力が同じであればどのノードでも必ず同じ値が得られます。

  3. 最大支払可能ガス単価は以下で求まります。

maxGasPricePerTx=バーン額G\text{maxGasPricePerTx} = \frac{\text{バーン額}}{G}
  1. Mempoolは maxGasPricePerTx の降順でトランザクションを並べる優先度付きキューを用います。 これにより、ブロック組成時に高単価のものから順に取り込まれます。 従来のFIFO(先入れ先出し)キューは使用しません。

参考:ブロック処理フローの擬似コード

仕様の同義を簡潔に表すための擬似コードです。

py
state x = 0          # 超過ガス消費
state r = 0          # バケット内ガス量

on begin_block(b):
  Δt = timestamp(b) - timestamp(parent(b))
  x = max(x - T * Δt, 0)
  r = min(r + R * Δt, C)
  price = M * exp_approx(x / K)

  # メンプールから price を支払える Tx を maxGasPrice 降順で詰める
  G_block = 0
  for tx in mempool.sorted_by_maxGasPrice_desc():
    if G_block + G(tx) > r:
      break  # これ以上は不正(超過)になる
    include(tx)
    G_block += G(tx)

  if G_block > r:
    reject_block_as_invalid()

on end_block(b):
  r = r - G_block
  x = x + G_block

互換性

手数料メカニズムの変更は、ブロックチェーンにおける実行仕様そのものを変える処理であり、これはネットワーク全体で必須のアップグレード(mandatory upgrade)を伴います。 つまり、ノード運用者はアップグレードを適用しなければ、ネットワークの合意に参加できなくなります。

ここで注意しなければならないのは、有効化(activation)の前に新しい挙動を適用してはいけないという点です。 有効化前に挙動がずれると、ノード間で結果が異なり、チェーンが分岐するリスクが生じます。 したがって、開発者や実装者は、有効化のタイミングを正確に管理し、それまでは従来通りの固定手数料ルールで動かす必要があります。

有効化後は、P-Chain上で発行されるすべてのトランザクションが、新しく導入された動的手数料メカニズムに従う必要があります。 ユーザー側は、手数料が混雑によって上昇したときに、自分のトランザクションをより早くブロックに含めたいなら、より高い手数料を支払うようにトランザクションを再構築する責任を持ちます。 具体的には、トランザクションの最大支払可能ガス単価を、その時点のガス価格よりも十分に大きく設定し直すことで、メンプール内の優先度を高める必要があります。

参考実装

ACP103は、公式クライアントであるAvalancheGoにすでに実装されており、アップグレードフラグEtnaで管理されています。 アップグレードフラグとは、特定のブロック高や時刻に達したとき、新しい仕様へ切り替えるためのスイッチのようなものです。 これにより、有効化条件が満たされるまで旧仕様を使い続け、有効化と同時に新仕様へ安全に移行できます。

実装内容の全体像やレビュー履歴は、AvalancheGoのリポジトリでacp103 ラベルが付けられた Pull Request一覧にまとめられています。 そこには、ガス価格の計算(指数近似のアルゴリズム)、トークンバケットによるガス消費制御、Mempoolの優先度付きキューへの変更など、ACP103を反映するための全作業が確認できます。 実装や動作を追跡したい場合、このラベルをたどれば技術的な詳細を時系列で把握できます。

擬似的に書くと、実装は以下のように「有効化前後で分岐するコード」になっています。

py
if now < activation_point("Etna"):
    # 従来の固定手数料ルールを適用
    gas_price = fixed_price
else:
    # 新しい動的手数料ルールを適用
    x = max(x - T * Δt, 0)
    gas_price = M * exp_approx(x / K)
    r = min(r + R * Δt, C)
    validate_tx_with_priority(tx, gas_price, r)

セキュリティ

現在のP-ChainとX-Chainは、どちらも固定手数料で運用されています。 しかし、この方式には大きな弱点があります。 突発的に取引が集中したときや、新機能によって処理需要が急増したときでも、手数料が一定のままなので、スパムやDoS攻撃を価格面で抑制する仕組みが働かないです。 その結果、ブロックに取引が積み残され、全体の処理が滞るリスクが高まります。

特にP-Chainは、サブネット作成やバリデータ管理などAvalancheネットワーク全体の基盤を担っており、ここが詰まるとシステム全体に悪影響が及びます。 例えば、ACP77で導入予定の「サブネット改善」により、P-Chain上の取引需要が一時的に急増する可能性があります。 そこで、動的手数料メカニズムを導入することで、処理能力を超える負荷がかかった際に価格が自動的に上昇し、利用者が取引送信を調整するよう誘導できる仕組みが確立されます。 これにより、追加の負荷は「処理可能な範囲内」に収まり、ネットワーク全体の可用性が守られます。

一方、X-Chainは現時点で利用度が低く、新しい機能追加も予定されていないため、固定手数料でも処理が詰まるリスクは小さいとされています。 そのため、X-Chainへの動的手数料導入は急がず、将来必要になったときに今回の仕組みをそのまま適用できるよう準備しておくという方針が取られています。

最後に

今回は「AvalancheのP-Chainに動的な手数料メカニズムを導入し、ネットワーク混雑時には手数料が上がり、空いているときには下がる仕組みを提案しているACP103」についてまとめてきました! いかがだったでしょうか?

質問などがある方は以下のTwitterのDMなどからお気軽に質問してください!

他の媒体でも情報発信しているのでぜひ他も見ていってください!