코인베이스 성숙 스케줄링

Parallax가 결정론적이고 정규화된 상태 업데이트를 통해 채굴자 보조금을 성숙 시점까지 어떻게 에스크로하는지 설명합니다.

성숙 파라미터
파라미터기호값 / 출처비고
코인베이스 성숙 블록 수
M
100 블록
Bitcoin 스타일 코인베이스 성숙 기간
락박스 주소
0x0000000000000000000000000000000000000042
예약된 시스템 계정
키 접두사
"maturity:addr:", "maturity:amt:"
Keccak256(prefix || 빅엔디안 height)
개요
Parallax는 블록 보조금을 고정된 성숙 높이까지 에스크로하고, 상태 트라이의 락박스에서 결정론적으로 해제합니다.
  • 각 블록은 자신의 코인베이스 지급을 height + M 블록 뒤에 예약합니다(M = 100 블록).
  • 특수한 락박스 계정은 "언제"(height) → (주소, 금액) 쌍을 상태 슬롯으로 저장합니다.
  • 각 높이 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(...)
상태 키 & 락박스 주소
예약된 시스템 주소 아래, 해제 높이마다 두 개의 저장 슬롯.
  • 주어진 해제 높이 H에 대해: schedKeyAddr(H)에 주소를, schedKeyAmt(H)에 금액을 저장합니다.
  • 두 키 모두 고정된 ASCII 접두사와 빅엔디안 uint64로 인코딩된 H의 keccak256으로 파생됩니다.
  • 존재 여부는 금액 슬롯으로 판단하며, 지급 시 두 슬롯을 모두 비워 트라이 공간을 회수합니다.
키 파생 (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 블록으로 정의됨
  • 클라이언트는 채굴자에게 '대기 중'(예약된) 잔액과 '사용 가능' 잔액을 모두 표시해야 합니다.
  • 익스플로러는 0이 아닌 schedKeyAmt(h)를 스캔하여 예정된 해제를 보여줄 수 있습니다.
  • 지갑은 보상 UTXO(계정 입금)가 높이 U 전까지 사용할 수 없음을 채굴자에게 경고해야 합니다.
익스플로러 힌트 (유사 RPC)
의사코드
for h in [current..current+K]:
  if getState(lockbox, schedKeyAmt(h)) != 0:
    // list upcoming payout at height h