コインベース成熟スケジューリング

Parallaxが、決定的かつ正規の状態更新を用いて、マイナー補助金を成熟までエスクローする仕組み。

成熟パラメータ
パラメータ記号値 / 出典備考
コインベース成熟ブロック数
M
100ブロック
Bitcoin型のコインベース成熟
ロックボックスアドレス
0x0000000000000000000000000000000000000042
予約済みのシステムアカウント
キープレフィックス
"maturity:addr:"、"maturity:amt:"
Keccak256(prefix || ビッグエンディアンの高さ)
概要
Parallaxはブロック補助金を固定の成熟高までエスクローし、ステートトライ内のロックボックスから決定的にリリースします。
  • 各ブロックは、自らのコインベース支払いを高さ + M ブロック (M = 100ブロック) に予約します。
  • 専用のロックボックスアカウントは、"いつ" (高さ) → (アドレス、金額) のペアを状態スロットとして保持します。
  • 各高さ h において、プロトコルは h に支払い予定があるかを確認し、記録されたアドレスに送金します。
  • これにより未確定発行と利用可能供給が分離され、リオーグ時の扱いもスムーズになります。
高レベルの流れ
疑似コード
Finalize(header, state):
  h = header.number
  R = calcBlockReward(h)
  M = 100 blocks
  if R > 0:
    putScheduledPayout(state, h + M, header.coinbase, R)
  if due(h):
    (addr, amt) = popDuePayout(state, h)
    state.AddBalance(addr, amt)
  header.Root = state.IntermediateRoot(...)
状態キーとロックボックスアドレス
予約されたシステムアドレス下で、アンロック高ごとに2つのストレージスロット。
  • 所与のアンロック高 H について: アドレスを schedKeyAddr(H) に、金額を schedKeyAmt(H) に格納します。
  • 両キーは固定のASCIIプレフィックスに、ビッグエンディアンuint64でエンコードしたHを連結し、keccak256で導出されます。
  • 存在判定は金額スロット側で行い、支払い完了時は両スロットをクリアしてtrieのスペースを回収します。
キー導出 (consensus.goより)
疑似コード
schedKeyAddr(H):
  b = bigEndianUint64(H)
  return keccak256("maturity:addr:" || b)

schedKeyAmt(H):
  b = bigEndianUint64(H)
  return keccak256("maturity:amt:" || b)
支払いのライフサイクル
ブロック取り込みから、成熟後の送金まで。
  • ブロック N がマイニングされる → 半減期スケジュールから報酬 R_N が計算されます。
  • アンロック高 U = N + M で (addr=coinbase_N, amt=R_N) を予約します。
  • 高さ U において、ノードは (addr_U, amt_U) を読み、amt_U ≠ 0 ならクレジットしてスロットをクリアします。
  • ジェネシス (高さ0) には利用可能な補助金はありません。
タイムライン (テキスト図)
疑似コード
N:   mine block, schedule payout for U=N+M
...
U-1: pending only
U:   popDuePayout → AddBalance(addr_U, amt_U) → clear slots
U+1: nothing due for U anymore
検証とセキュリティ特性
決定的なアンロック、リプレイ安全、リオーグ耐性。
  • アンロックロジックは正規の高さから計算され、署名やオフチェーンのトリガーは不要です。
  • 金額はプロトコルが計算 (calcBlockReward) するため、ピアが支払いを水増しすることはできません。
  • 状態キーの存在が『支払い期限到来』の指標であり、クレジット後のクリアで二重支払いを防ぎます。
  • 成熟期間はマイナーの利用可能性を遅らせ、手数料+補助金を即時に奪うための先端付近のリオーグのインセンティブを減らします。
期限到来の検知とクリア
疑似コード
popDuePayout(state, H):
  rawAmt  = state.Get(lockbox, schedKeyAmt(H))
  if rawAmt == 0: return (zero, 0, false)
  rawAddr = state.Get(lockbox, schedKeyAddr(H))
  state.Set(lockbox, schedKeyAmt(H), 0)
  state.Set(lockbox, schedKeyAddr(H), 0)
  return (Address(rawAddr), BigInt(rawAmt), true)
リオーグ時の振る舞い
アンロック高付近でチェーンが再編されたときに何が起きるか。
  • リオーグ時には、新しい正規チェーンから状態が再計算されます。予約エントリは正規のブロック列を反映します。
  • 旧ティップで高さHの支払いが発生しても、新しいチェーン上では発生しない場合、状態の再実行は新しい履歴で支払い期限のあるもののみをクレジットします。
  • エントリは高さをキーにしているため、"幻の入金" はリオーグを生き延びられません — クリア済み/未クリアの状態は正規の実行に従います。
  • これは、ブロックの再実行時に残高やレシートが再計算されるのと同じ振る舞いです。
概念的なリオーグ擬似コード
疑似コード
ReorgTo(newTip):
  rewind state → parent of fork
  for block in pathTo(newTip):
    Execute(block) // schedules & payouts naturally recompute
設定
成熟パラメータがどこで定義され、クライアントがどう扱うか。
  • 成熟期間 M は100ブロックと定義されています
  • クライアントは、マイナー向けに『保留中 (予約済み)』と『利用可能』の両方の残高を表示する必要があります。
  • エクスプローラーは、ゼロ以外の schedKeyAmt(h) をスキャンすることで、今後のアンロックを表示できます。
  • ウォレットは、報酬のUTXO (アカウントクレジット) が高さ U まで利用できないことをマイナーに警告すべきです。
エクスプローラーヒント (擬似RPC)
疑似コード
for h in [current..current+K]:
  if getState(lockbox, schedKeyAmt(h)) != 0:
    // list upcoming payout at height h