はじめに
『DApps開発入門』という本や色々記事を書いているかるでねです。
今回は、Ethereumのステーキングで1バリデーターの上限を2048ETHに拡張し、複数のバリデータを統合可能にする仕組みを提案しているEIP7251についてまとめていきます!
以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。
他にも様々なEIPについてまとめています。
概要
EIP7251では、Ethereumのバリデータが保有できる最大ステーク額「MAX_EFFECTIVE_BALANCE
」の上限を引き上げることが提案されています。
これにより、最小ステーキング額である32ETHは維持しつつも、上限が撤廃または緩和されることで、バリデータの運用に柔軟性と効率性がもたらされます。
具体的には、現在は32ETHを上限としてバリデータ1体あたりの報酬が計算されていますが、上限が引き上げられることで、1つのバリデータにより多くのETHをステークすることが可能になり、報酬も複利的に増やせるようになります。
動機
2023年10月時点で、Ethereumのコンセンサスレイヤーには83万以上のバリデータが存在しており、その数は増加しています。
この背景には、1つのバリデータあたりのステーク上限が32ETHに制限されていることがあり、結果として冗長なバリデータが多数存在する状態になっています。
これらの冗長なバリデータは、実際には同一のオペレーター(例:大規模ステーキング事業者)によって運用されており、同一ノード上で稼働していることもあります。
ただし、それぞれが別個のBLS署名鍵を用いており、独立したバリデータとして扱われています。
この32ETHの上限は、もともとシャーディング構想に由来する技術的負債です。
当初は、サブコミッティ(is_aggregator
に関連する計算に基づく小委員会)が過半数正直であることを前提とした設計となっており、各委員のステーク量が均等であることが望ましいとされていました。
Ethereumのバリデータはエポックごとに 委員会(committee) と呼ばれるランダムなグループに割り当てられ、ブロック提案者が作成したブロックをアテステーション(署名投票)で承認します。
この委員会の中で、さらに サブコミッティ(sub-committee) が選ばれ、役割は以下のとおりです。
- アテステーションを集約(アグリゲート)し、BLS署名を1つにまとめる
- 集約後の署名をブロック提案者(プロポーザ)へ送る
つまりサブコミッティは、ブロックの最終的な合意形成ではなく、通信量と署名検証コストを削減するための中間処理を担います。
しかし、現在のEthereumの設計では、これらのサブコミッティはアグリゲーション(集約)処理のみに使われており、信頼モデルとしては「1/N honesty assumption」に基づいています。
つまり、かつてのようにステーク量の均衡がセキュリティに直結する設計ではなくなっています。
サブコミッティには $N$ 人(例:16 人など)のメンバーがいます。
「1/N honesty assumption」とは、「この $N$ 人のうち最低1人 でも正しくプロトコルを実行していれば、安全に署名を集約できる」という前提条件を指します。
多数決や2/3以上の正直者を必要とする従来のBFTモデルよりもはるかに緩い条件です。
このような背景から、MAX_EFFECTIVE_BALANCE
の上限引き上げには以下のようなメリットがあります。
- バリデータ数の削減によるネットワークのP2P通信負荷の軽減
- 各エポックで必要となるBLS署名の集約量の削減
- BeaconState(ステートデータ)のメモリフットプリントの削減
また、以下のように全てのステーキング参加者に利点があります。
-
大規模ステーキング事業者
- 多数のバリデータを集約でき、ノード運用コストが削減される。
-
個人ステーカー
- 報酬の複利運用が可能になり、例えば「40ETHを1つのバリデータとして運用」するような柔軟なステーキングが可能になる(従来は64ETHで2つ必要だった)
EIP7251は、ネットワーク全体の効率性を高めつつ、セキュリティモデルを損なうことなくスケーラビリティを向上させる意図を持っています。
仕様
Execution layerの定数
Name | Value | Comment |
---|---|---|
CONSOLIDATION_REQUEST_TYPE |
0x02 |
EIP7685で定義されるコンソリデーションリクエスト(CR)のタイププレフィックス |
CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS |
0x0000BBdDc7CE488642fb579F8B00f3a590007251 |
CRメカニズム専用の事前デプロイコントラクトアドレス |
SYSTEM_ADDRESS |
0xfffffffffffffffffffffffffffffffffffffffe |
システムコールを実行する際に使用する特別なアドレス |
EXCESS_CONSOLIDATION_REQUESTS_STORAGE_SLOT |
0 |
余剰 CR 数を格納するストレージスロット |
CONSOLIDATION_REQUEST_COUNT_STORAGE_SLOT |
1 |
登録済みCRの総数を保持するスロット |
CONSOLIDATION_REQUEST_QUEUE_HEAD_STORAGE_SLOT |
2 |
キューの先頭を指すポインタ |
CONSOLIDATION_REQUEST_QUEUE_TAIL_STORAGE_SLOT |
3 |
キューの末尾を指すポインタ |
CONSOLIDATION_REQUEST_QUEUE_STORAGE_OFFSET |
4 |
キュー配列を格納するストレージ開始位置 |
MAX_CONSOLIDATION_REQUESTS_PER_BLOCK |
2 |
1ブロックで取り出せるCRの上限 |
TARGET_CONSOLIDATION_REQUESTS_PER_BLOCK |
1 |
手数料調整アルゴリズムが目標とするCR数 |
MIN_CONSOLIDATION_REQUEST_FEE |
1 |
CR送信に必要な最小手数料 |
CONSOLIDATION_REQUEST_FEE_UPDATE_FRACTION |
17 |
手数料を更新する際の分母(1/17 ずつ変動) |
EXCESS_INHIBITOR |
$2^{256} - 1$ | 初回システムコール時に参照される擬似的な過剰値 |
コンソリデーションリクエスト(CR)
複数のバリデータを1つに集約するリクエストです。
実行レイヤーではキュー方式で管理され、ブロックごとに上限(MAX_CONSOLIDATION_REQUESTS_PER_BLOCK
)まで処理されます。
事前デプロイアドレスとシステムアドレス
CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS
はCR処理用ロジックを格納するコントラクトで、SYSTEM_ADDRESS
はEVM内部でシステムコールをトリガーするための予約アドレスです。
ストレージ設計
キューのヘッド・テイルや件数を固定スロットに置き、キュー配列自体は CONSOLIDATION_REQUEST_QUEUE_STORAGE_OFFSET
から順に格納します。
これによりガス効率と一貫性を両立しています。
手数料調整
手数料はブロックあたりの実際の処理数と目標値 (TARGET_CONSOLIDATION_REQUESTS_PER_BLOCK
) を比較し、CONSOLIDATION_REQUEST_FEE_UPDATE_FRACTION
で定義された割合で増減します。
初回のみ EXCESS_INHIBITOR
を用いて過剰値をシミュレートし、手数料計算を安定化させます。
Consensus layer の定数
Name | Value |
---|---|
COMPOUNDING_WITHDRAWAL_PREFIX |
Bytes1('0x02') |
MIN_ACTIVATION_BALANCE |
Gwei($2^5 × 10^9$) (32 ETH) |
MAX_EFFECTIVE_BALANCE_ELECTRA |
Gwei(211 × 109) (2048 ETH) |
複利引き出しプレフィックス (COMPOUNDING_WITHDRAWAL_PREFIX
)
複利(コンパウンディング)対応の引き出しを示す1バイトの識別子です。
将来のプロトコルアップグレードで特殊な処理を呼び分けるために使用されます。
最小アクティベーションバランス (MIN_ACTIVATION_BALANCE
)
既存と同じく32ETHで維持されます。
ソロステーカーは従来どおり少額でバリデータを起動できます。
最大有効バランス Electra (MAX_EFFECTIVE_BALANCE_ELECTRA
)
EIP7251で2048ETHに拡大されます。
これにより大口事業者は複数のバリデータを統合でき、ネットワーク全体のバリデータ数削減とパフォーマンス向上が期待されます。
Execution layer
コンソリデーションリクエスト
EIP7685リクエスト形式
フィールド | 型 | 説明 |
---|---|---|
source_address |
Bytes20 | バリデータ統合を発行する実行レイヤー側のアドレス |
source_pubkey |
Bytes48 | 統合元バリデータ(ソース)のBLS公開鍵 |
target_pubkey |
Bytes48 | 統合先バリデータ(ターゲット)のBLS公開鍵 |
-
request_type
は定数CONSOLIDATION_REQUEST_TYPE (0x02)
です。 - 実行レイヤーのコントラクトから
dequeue_consolidation_requests()
が返す値をそのままエンコードして提出します。
request_type = CONSOLIDATION_REQUEST_TYPE
request_data = dequeue_consolidation_requests()
コントラクトの概要
コンソリデーションリクエスト(以下 CR)専用コントラクトは1つの事前デプロイアドレス(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS
)に配置され、以下の3つのコードパスを持ちます。
コードパス | 入力条件 | 動作概要 |
---|---|---|
Add Consolidation Request | コールデータ長がちょうど96byte(48 + 48) | CRをキューに追加して手数料を徴収 |
Fee Getter | コールデータ長が0 | 現在のCR手数料を返却 |
System Process | 呼び出し元が SYSTEM_ADDRESS
|
ブロック末尾にキューを取り出して処理して内部状態を更新 |
Add Consolidation Request
def add_consolidation_request(Bytes48: source_pubkey, Bytes48: target_pubkey):
"""
Add consolidation request adds new request to the consolidation request queue, so long as a sufficient fee is provided.
"""
# Verify sufficient fee was provided.
fee = get_fee()
require(msg.value >= fee, 'Insufficient value for fee')
# Increment consolidation request count.
count = sload(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, CONSOLIDATION_REQUEST_COUNT_STORAGE_SLOT)
sstore(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, CONSOLIDATION_REQUEST_COUNT_STORAGE_SLOT, count + 1)
# Insert into queue.
queue_tail_index = sload(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, CONSOLIDATION_REQUEST_QUEUE_TAIL_STORAGE_SLOT)
queue_storage_slot = CONSOLIDATION_REQUEST_QUEUE_STORAGE_OFFSET + queue_tail_index * 4
sstore(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, queue_storage_slot, msg.sender)
sstore(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, queue_storage_slot + 1, source_pubkey[0:32])
sstore(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, queue_storage_slot + 2, source_pubkey[32:48] ++ target_pubkey[0:16])
sstore(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, queue_storage_slot + 3, target_pubkey[16:48])
sstore(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, CONSOLIDATION_REQUEST_QUEUE_TAIL_STORAGE_SLOT, queue_tail_index + 1)
-
前提
-
msg.value
がget_fee()
で計算される手数料以上であること。
-
-
処理手順
- 手数料不足なら
revert
。 -
CONSOLIDATION_REQUEST_COUNT_STORAGE_SLOT
をインクリメント。 - キュー末尾インデックスを読み取り、4つの連続ストレージスロットに下記を格納します。
- スロット 0
-
msg.sender
(リクエスト発行者アドレス)
-
- スロット 1
-
source_pubkey
の前半 32 byte
-
- スロット 2
-
source_pubkey
後半 16 byte +target_pubkey
前半 16 byte
-
- スロット 3
-
target_pubkey
後半 32 byte
-
- スロット 0
- キュー末尾インデックスを +1。
- 手数料不足なら
この一連の操作により、統合リクエストが順序付きキューとしてチェーン上に蓄積されます。
手数料計算 (get_fee()
)
def get_fee() -> int:
excess = sload(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, EXCESS_CONSOLIDATION_REQUESTS_STORAGE_SLOT)
require(excess != EXCESS_INHIBITOR, 'Inhibitor still active')
return fake_exponential(
MIN_CONSOLIDATION_REQUEST_FEE,
excess,
CONSOLIDATION_REQUEST_FEE_UPDATE_FRACTION
)
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
-
EXCESS_CONSOLIDATION_REQUESTS_STORAGE_SLOT
に保存された過剰リクエスト数を読み取り、指数近似関数fake_exponential()
に渡して手数料を算出します。 - 初回起動直後は
EXCESS_INHIBITOR
がセットされており、これを検出するとゼロ初期化します。 - 手数料は
MIN_CONSOLIDATION_REQUEST_FEE
を起点に、過剰度合いに応じて滑らかに増減します(分母はCONSOLIDATION_REQUEST_FEE_UPDATE_FRACTION
)。
Fee Getter
コールデータ長が0の呼び出しでは、上記 get_fee()
の結果のみを返します。
これにより外部エンティティは現在のCR申請コストをガスを消費せずに取得できます。
System Call
ブロック末尾で SYSTEM_ADDRESS
からコントラクトを呼び出すと、以下が実行されます。
-
dequeue_consolidation_requests()
- キュー先頭から最大
MAX_CONSOLIDATION_REQUESTS_PER_BLOCK
件を取り出し、空になればヘッド/テイルをリセット。
- キュー先頭から最大
-
update_excess_consolidation_requests()
- 1 ブロックの目標処理数(
TARGET_CONSOLIDATION_REQUESTS_PER_BLOCK
)を超えた分を過剰値として蓄積。
- 1 ブロックの目標処理数(
-
reset_consolidation_requests_count()
- ブロック内カウンタを0に戻す。
-
戻り値として、取り出したCR一覧をSSZシリアライズして実行レイヤーに返します。
項目 | 内容 |
---|---|
呼び出し先 |
CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS (専用コントラクト) |
呼び出し元 |
SYSTEM_ADDRESS (0xffff...fffe ) |
コールデータ | 空(calldata なし) |
実行時点 | ブロック末尾(すべてのトランザクション処理とブロック検証の後) |
ガス要件
- **専用ガス上限
- 30,000,000**
- この呼び出しで使用されるガスはブロック全体のガス使用量に含まれない
- EIP1559のBurn対象にもならない
-
transfer
も行われない
EIP1559については以下の記事を参考にしてください。
バリデーション要件
コントラクトコードが存在しない場合、または呼び出しの失敗時にブロックが無効になります。
コード
###################
# Public function #
###################
def process_consolidation_requests():
reqs = dequeue_consolidation_requests()
update_excess_consolidation_requests()
reset_consolidation_requests_count()
return ssz.serialize(reqs)
###########
# Helpers #
###########
class ConsolidationRequest(object):
source_address: Bytes20
source_pubkey: Bytes48
target_pubkey: Bytes48
def dequeue_consolidation_requests():
queue_head_index = sload(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, CONSOLIDATION_REQUEST_QUEUE_HEAD_STORAGE_SLOT)
queue_tail_index = sload(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, CONSOLIDATION_REQUEST_QUEUE_TAIL_STORAGE_SLOT)
num_in_queue = queue_tail_index - queue_head_index
num_dequeued = min(num_in_queue, MAX_CONSOLIDATION_REQUESTS_PER_BLOCK)
reqs = []
for i in range(num_dequeued):
queue_storage_slot = CONSOLIDATION_REQUEST_QUEUE_STORAGE_OFFSET + (queue_head_index + i) * 4
source_address = address(sload(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, queue_storage_slot)[0:20])
source_pubkey = (
sload(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, queue_storage_slot + 1)[0:32] + sload(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, queue_storage_slot + 2)[0:16]
)
target_pubkey = (
sload(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, queue_storage_slot + 2)[16:32] + sload(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, queue_storage_slot + 3)[0:32]
)
req = ConsolidationRequest(
source_address=Bytes20(source_address),
source_pubkey=Bytes48(source_pubkey),
target_pubkey=Bytes48(target_pubkey)
)
reqs.append(req)
new_queue_head_index = queue_head_index + num_dequeued
if new_queue_head_index == queue_tail_index:
# Queue is empty, reset queue pointers
sstore(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, CONSOLIDATION_REQUEST_QUEUE_HEAD_STORAGE_SLOT, 0)
sstore(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, CONSOLIDATION_REQUEST_QUEUE_TAIL_STORAGE_SLOT, 0)
else:
sstore(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, CONSOLIDATION_REQUEST_QUEUE_HEAD_STORAGE_SLOT, new_queue_head_index)
return reqs
def update_excess_consolidation_requests():
previous_excess = sload(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, EXCESS_CONSOLIDATION_REQUESTS_STORAGE_SLOT)
# Check if excess needs to be reset to 0 for first iteration after activation
if previous_excess == EXCESS_INHIBITOR:
previous_excess = 0
count = sload(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, CONSOLIDATION_REQUEST_COUNT_STORAGE_SLOT)
new_excess = 0
if previous_excess + count > TARGET_CONSOLIDATION_REQUESTS_PER_BLOCK:
new_excess = previous_excess + count - TARGET_CONSOLIDATION_REQUESTS_PER_BLOCK
sstore(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, EXCESS_CONSOLIDATION_REQUESTS_STORAGE_SLOT, new_excess)
def reset_consolidation_requests_count():
sstore(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, CONSOLIDATION_REQUEST_COUNT_STORAGE_SLOT, 0)
process_consolidation_requests
キュー内のリクエスト処理と状態更新を担当し、SSZ形式でリクエストリストを返す関数。
def process_consolidation_requests():
reqs = dequeue_consolidation_requests()
update_excess_consolidation_requests()
reset_consolidation_requests_count()
return ssz.serialize(reqs)
dequeue_consolidation_requests
実行レイヤーのリクエストキューから、現在のブロックで処理すべきコンソリデーションリクエストを取り出す処理を行う関数。
- キューの先頭と末尾を読み取り、リクエスト数を算出
- 最大
MAX_CONSOLIDATION_REQUESTS_PER_BLOCK
件を取り出す - 各リクエストを4スロットから復元してオブジェクトとして構築
req = ConsolidationRequest(
source_address=Bytes20(…),
source_pubkey=Bytes48(…),
target_pubkey=Bytes48(…)
)
- 取り出した後の queue head を更新
- キューが空なら
head
/tail
を0にリセット
- キューが空なら
update_excess_consolidation_requests
ブロック内で処理されたリクエスト数と目標件数との差分を元に、「過剰リクエスト数(excess)」を更新する関数。
-
EXCESS_CONSOLIDATION_REQUESTS_STORAGE_SLOT
から過剰件数(excess)を取得 - 初回起動時(
EXCESS_INHIBITOR
)なら0に初期化 - 実際の処理件数と
TARGET_CONSOLIDATION_REQUESTS_PER_BLOCK
の差をもとに新しい excess を算出・更新
これにより、次回以降の手数料計算に影響する過剰度メトリクスが反映されます。
reset_consolidation_requests_count
現在のブロックで受け付けたコンソリデーションリクエストの件数カウントをリセットする関数。
-
CONSOLIDATION_REQUEST_COUNT_STORAGE_SLOT
を0に戻すことで、次ブロックでの処理件数カウントを初期化。
バイトコード
以下にまとめられています。
デプロイ方法と合成アドレス
コントラクト自体は通常の CREATE
でデプロイされますが、特殊なアドレス(0x0000BBdD…7251
)に配置するため、トランザクションパラメータを逆計算して合成しています。
これにより、仕様書にハードコードされたアドレスと実際のデプロイ先との整合性が確保されます。
コンセンサスレイヤー
可変サイズバリデータの許可とステーキング下限の維持
EIP7251では、バリデータあたりの最大ステーク量を2048ETHに引き上げます。
これは MAX_EFFECTIVE_BALANCE_ELECTRA
として新たに定義されます。
ただし、この拡張は個人ステーカーを排除する意図ではなく、下限の32ETHを MIN_ACTIVATION_BALANCE
として明示することで、従来どおりソロステーキングも維持されます。
これにより、32ETHのステーキングから始めて報酬を複利で積み上げていく運用も可能になり、柔軟性が高まります。
プロトコルレベルでのバリデータ統合
大量のバリデータを抱えるノードオペレーターにとって、既存のexit → 再エントリの流れは非常に負担の大きいものでした。
EIP7251により、バリデータをプロトコル内で統合できるメカニズムが導入され、再登録なしで管理数を減らすことができます。
これにより、ネットワーク全体のバリデータ数が抑制され、署名集約やメッセージ流量の軽減につながります。
実行レイヤーからの部分引き出し
部分引き出しは、EIP7002に定義された実行レイヤーメッセージによってトリガーされます。
例えば100ETHをステークしたバリデータが、そのうち68ETHを引き出してもバリデータのステータスは維持されたままになります。
これはガス代がかかる操作ではありますが、資金の可動性を維持したまま長期ステーキングが可能になります。
初期スラッシュペナルティの軽減
統合によりバリデータのステーク量が大きくなると、現行仕様ではスラッシュ時に受ける絶対的な損失額も大きくなってしまいます。
EIP7251では、初回スラッシングのペナルティを「有効残高の1/32」から緩和し、ステーク量の大小にかかわらず損失のスケーリングを抑制します。
これにより、大規模ステーカーでも統合をためらう必要がなくなります。
ステートとロジックの変更点
EIP7251の実装にあたって、いくつかの定数やデータ構造が導入・変更されます。
まず、複利型の引き出しクレデンシャルを示す COMPOUNDING_WITHDRAWAL_PREFIX
が導入されます。
また、BeaconState
には新たに pending deposits(入金キュー)や部分引き出しキューが追加され、既存の exit
キューもステーク量ベースで制御されるように改良されます。
入金処理についても、即座に反映されるのではなく PendingDeposit
コンテナに蓄積され、process_pending_deposits
という新設のヘルパー関数で段階的に反映されるようになります。
これにより、大量の入金による突発的なステート変更を防ぐ設計です。
チャーン制御も見直され、これまでのバリデータ数ベースではなく、ステーク量を基準にした「重み付きチャーン制限」となります。これにより、高ステークバリデータが多数存在しても、適切な速度で退出処理が行われるようになります。
チャーン(churn)制限とは、Ethereumのコンセンサスレイヤーにおいて、1エポックあたりにアクティベート(新規参加)またはエグジット(退出)できるバリデータの最大数を制御する仕組みです。
ネットワークの安定性を保つために、バリデータの出入りが一度に大量に行われるのを防ぐ目的で導入されています。
例えば、何万ものバリデータが一斉にエグジットすると、ネットワークのセキュリティや整合性に影響が出かねません。
重み付きチャーン制限(weight-based churn limit)は、従来の「バリデータ数」による計算ではなく、ステークの合計量(validator weight)に基づいて制御する新しいチャーン制限方式です。
この方式では、エポックごとのエントリー・エグジット上限が「人数」ではなく「合計ステーク量」で制限されます。
複利引き出しクレデンシャルと関連ロジック
バリデータの引き出し条件をより細かく制御するために、複利型引き出しクレデンシャル(0x02
)を用いた識別が導入されます。
これにより、バリデータが完全に引き出し可能な状態にあるのか、部分的な余剰があるだけなのかを判定できます。
新たなヘルパー関数により、これらの情報をもとに正しくキュー処理や引き出しの調整が行われます。
複利型引き出しクレデンシャル(Compounding Withdrawal Credential) とは、バリデータの報酬を引き出さずにステークに再投資(複利運用)することを明示的に選択する新しい引き出し設定方式です。
統合処理と退出チャーンの関係
新設された process_consolidation
によって、バリデータの統合処理と複利クレデンシャルへの切り替えが行われます。
退出チャーン(1エポックあたりにバリデータが退出できる上限数)の制限は256ETHに抑えられ、これを超えるリクエストについては統合によって対応します。
これにより、バリデータの退出が殺到するような状況でも、ネットワークの健全性を維持したまま段階的に処理を進めることができます。
互換性
EIP7251は、Ethereumのブロック検証ルールに対して互換性のない変更を含んでいます。
したがって、ネットワークにこの変更を適用するにはハードフォーク(Hard Fork)が必要です。
ハードフォークとは、プロトコルのルールが旧バージョンと互換性を持たない形で変更されることであり、クライアントやノードが対応バージョンへアップグレードしなければ、新しいチェーンに参加できなくなる重大な更新です。
セキュリティ
アテステーション委員会のセキュリティ
最悪のケースとして、すべてのステークが統合された場合を想定しても、委員会が乗っ取られる可能性は依然として低く保たれます。
Ethereumのファイナリティには2/3の誠実なバリデータが必要ですが、統合によって委員会内の多様性が多少減ったとしても、必要なセキュリティ閾値を下回るリスクは現実的には生じません。
アグリゲーター選出の安全性
かつてのシャーディング構想では、アグリゲーション担当のサブコミッティ(subcommittee)に高い誠実性が求められていました。
しかし現在では、委員会内で1人でも誠実なアグリゲーターがいれば十分です。
EIP7251では、アグリゲーターを選出するVRF(擬似ランダム関数)ロジックがバリデータのステーク量(weight)を考慮する方式に変更されるため、現状と比べても安全性は低下しません。
ブロック提案者の選出確率
ブロック提案者(proposer)の選出は、すでに「有効ステーク ÷ MAX_EFFECTIVE_BALANCE
」による重み付けが行われています。
EIP7251によって有効残高が高くなると、次の提案者を決定する際の計算に少し時間がかかる可能性がありますが、セキュリティ上のリスクではなく処理性能の側面に限った影響です。
Sync Committee の安全性
Sync Committeeの選出もすでに有効ステークに基づいており、EIP7251は同期プロトコルそのものには変更を加えません。
ライトクライアントが大多数が署名しているかどうかを判断する処理も、署名の重みとは独立に実行可能なため、プロトコルの正しさや軽量クライアントの動作には影響しません。
チャーンの不変条件と入金の扱い
チャーンの制限条件は、バリデータの「人数」ではなく「合計ステーク量」を基準にするよう変更されますが、チャーンの上限を維持するという原則自体は守られています。
また、有効残高の増額についても、新規入金と同様にステーク量に応じてチャーン制限に応じて段階的に反映されるように設計されています。
統合リクエストにおける手数料と過払いリスク
統合リクエストはシステムコントラクトへの送金を伴い、手数料が固定ではないため、過払いが発生する可能性があります。
スマートコントラクトからリクエストを送る場合は事前に staticcall
により手数料を読み取ることで対策できますが、EOAから直接リクエストする場合は手数料が確定できないため常に過払いになります。
また、統合キューが上限(262,144件)を超えた場合、そのリクエストは破棄される仕様です。
この場合も手数料は返金されないため、ユーザーは送信前にキューの状態を確認することが望まれます。
システムコールの失敗とその影響
CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS
に対するシステムコールが失敗する可能性は低いものの、もし発生した場合はそのブロック全体が無効と見なされます。
これは、EIP7002の仕様に準拠する動作であり、コントラクトコードが未デプロイの状態でEIP7251で提案されている内容が反映されてしまった場合なども含まれます。
引用
mike (@michaelneuder), Francesco (@fradamt), dapplion (@dapplion), Mikhail (@mkalinin), Aditya (@adiasg), Justin (@justindrake), lightclient (@lightclient), Felix Lange (@fjl), "EIP-7251: Increase the MAX_EFFECTIVE_BALANCE," Ethereum Improvement Proposals, no. 7251, June 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7251.
最後に
今回は「Ethereumのステーキングで1バリデーターの上限を2048ETHに拡張し、複数のバリデータを統合可能にする仕組みを提案しているEIP7251」についてまとめてきました!
いかがだったでしょうか?
質問などがある方は以下のTwitterのDMなどからお気軽に質問してください!
他の媒体でも情報発信しているのでぜひ他も見ていってください!