이 페이지는 실행 중인 체인에서 검증자 집합에 참여하고, 집합에서 나가고, 레지스터를 다시 읽는 데 사용합니다. 합의 가중치는 공개되어야 하므로, 검증자 레지스터는 이 체인에서 금액을 평문으로 담는 유일한 테이블입니다. 그래도 스테이크는 실드 노트에서 나오고 다시 실드 노트로 돌아갑니다.
레지스터
본딩한 적이 있는 검증자마다 한 행이 있으며, 키는 base58 주소입니다.
ValidatorEntry {
public_key, // Dilithium2: signs blocks, and this validator's staking actions
stake: u64, // bonded: the weight an epoch's set gives it
pending: Vec<(u64, u64)>, // unbonding, as (release_epoch, amount), oldest first
rewards: u64, // fees earned as a block proposer, unpaid
payout: ShieldedAddress, // rand1… — where a withdraw pays
nonce: u64, // replay protection for its signed actions
activation_epoch: u64, // under a `staking` section: the first epoch this row may be in a set
}
모든 필드는 공개이며, 레지스터는 모든 블록의 상태 루트에 해시로 반영됩니다. 행은 삭제되지 않습니다. 모든 금액을 언본딩한 검증자도 stake = 0인 행을 유지합니다.
| 상수 | 값 | 의미 |
|---|---|---|
MIN_STAKE | 1,000 RAND | 행이 에포크의 집합에 들어가기 위해 필요한 스테이크 |
MAX_VALIDATORS | 100 | 에포크가 가질 수 있는 집합의 최대 크기 |
UNBONDING_EPOCHS | 2 | 언본딩된 금액이 출금 가능해지기까지 기다리는 에포크 수 |
epoch_blocks | chain 20에서 1,000 | 에포크당 블록 수이며, 제네시스 파라미터입니다 |
에포크
epoch(h) = h / epoch_blocks. 에포크 e ≥ 1의 집합은 에포크 e − 1의 마지막 블록 시점의 레지스터에서 도출됩니다. stake ≥ MIN_STAKE인 모든 행 중 (stake 내림차순, address 오름차순) 기준 상위 MAX_VALIDATORS개를 골라 주소순으로 정렬합니다. 뷰의 리더, 블록의 투표자, 인증서의 검증은 모두 해당 블록이 속한 에포크의 집합을 사용합니다. 여기서 두 가지 결과가 따릅니다.
- 본딩은 이후 경계가 오기 전에는 가중치가 아닙니다. 본딩은 즉시 레지스터에 반영되지만 집합은 경계에서만 바뀝니다.
staking섹션이 없는 체인에서는 다음 경계이고, chain 20에서는 그보다 나중입니다(아래). - 언본딩은 다음 경계에서 가중치를 잃게 합니다. 반면 금액은 출금 가능해지기까지 두 에포크를 기다립니다. 검증자는 출금할 수 있게 되기 훨씬 전에 제안을 멈춥니다.
도출 결과가 비어 있으면 이전 집합이 그대로 이어집니다. --validator로 시작한 노드는 키가 현재 어느 집합에도 없으면 에포크가 이를 받아들일 때까지 관찰만 하므로, 제네시스 이후에 본딩하는 검증자는 재시작할 필요가 없습니다. rand_status는 is_validator(키가 여기에 있음)와 active_validator(현재 집합에 있음)를 별도로 보고합니다.
staking 섹션
제네시스에는 staking 섹션이 있을 수 있습니다(fullnode v0.5.4, audit v4 STAKE-2). 섹션이 없으면 위의 규칙이 전부이며, chain 14가 그렇습니다. chain 15 이후의 모든 체인에는 이 섹션이 있습니다. chain 20이 켠 항목은 다음과 같습니다.
"staking": {
"faucet_budget_per_epoch": "10000000000000", "bond_activation_epochs": 2,
"max_weight_bps": 3333, "max_stake_entry_bps_per_epoch": 2500,
"admission_by_vote": true, "registration_v2": true,
"faucet_recipients": ["rand1…", "…16 wallets…"],
"faucet_minters": ["<validator address>", "…18 validators…"]
}
| 필드 | chain 20 | 규칙 |
|---|---|---|
bond_activation_epochs | 2 | 에포크 e에 적용된 본딩은 에포크 e + 1 + 2에 대기열로 들어갑니다. 등록과 추가 본딩 모두 같습니다. 그 금액은 즉시 stake에 반영되지만 그때까지는 가중치가 아니며, 대기 중인 스테이크는 언본딩할 수 없습니다 |
max_stake_entry_bps_per_epoch | 2,500 (25 %) | 한 경계에서 가중치가 될 수 있는 스테이크(등록과 추가 본딩의 합)의 최대치이며, 그 시점에 이미 활성인 가중치에 대한 비율입니다. ⌊active · bps / 10,000⌋. 이를 넘는 만기 행은 본딩 순서대로 다음 경계를 기다립니다. chain 20의 활성 스테이크 26,000 RAND에서는 경계당 6,500 RAND입니다. k번의 경계 후 신규 진입자의 몫은 어떤 규모에서도 최대 1 − 1.25^−k이며, 두 번 후에는 약 3분의 1입니다 |
admission_by_vote | true | 새로운 키를 등록하는 본딩은 투표 가중치의 3분의 2를 넘게 보유한 검증자들이 그 키에 찬성 투표를 할 때까지 거부됩니다(NotAdmitted)(아래). 추가 본딩에는 투표가 필요하지 않습니다 |
max_weight_bps | 3,333 | 어떤 검증자의 가중치도 소속 집합 전체의 33.33 %를 넘지 않으며, 초과분은 집합 안에서 잘려 나가고 레지스터의 stake는 바뀌지 않습니다 |
registration_v2 | true | 등록은 제네시스 해시, 체인 id, 검증자 주소, 지급 주소에 대해 rand-register-2로 서명하며, v1 등록은 거부됩니다 |
faucet_budget_per_epoch | 10,000 RAND | 파우셋은 에포크당 이 금액까지만 발행합니다(초과 시 FaucetBudgetExhausted, 카운터는 다음 에포크에 다시 시작) |
faucet_recipients | 지갑 16개 | 파우셋 Mint는 이 지갑에만 지급할 수 있습니다(FaucetRecipientNotAllowed). 이 목록 덕분에 파우셋이 브리지와 나란히 존재할 수 있습니다 |
faucet_minters | 검증자 18개 | 파우셋 Mint는 이 검증자 키만 서명할 수 있으며(MinterNotAllowed), 다른 키가 얼마나 오래 본딩되어 있었든 관계없이 수락 시점과 블록 적용 시점에 검사됩니다. chain 20에서는 제네시스 검증자 26개 중 18개입니다. 이 목록은 chain 16 이후 모든 제네시스에 있습니다 |
slashing | 없음 | slashing 섹션이 없으므로 슬래싱되거나 제재(jail)되는 검증자는 없습니다(공개되는 정보) |
이 섹션이 있으면 파우셋 카운터와 본딩 대기열이 rand-state-5 도메인으로 상태 루트에 추가되고, 검증자 리프는 rand-validator-leaf-4가 됩니다. chain 20에는 동적 가스 가격과 RPL-2 프로그램 상태도 그 뒤에 추가되어 도메인이 rand-state-8로 바뀌며, 투표에 의한 진입은 이 루트를 H("rand-state-admitted-1", root ‖ admitted_root)로 한 번 더 감쌉니다. chain 15에는 faucet_minters 목록이 없었습니다. 그곳에서는 모든 노드가 제네시스 검증자가 서명한 파우셋 Mint만 풀에 모으고 전달했으며(노드 정책으로서의 MinterNotAllowed), 그래서 제네시스 이후에 본딩한 검증자가 풀을 통해 예산을 소진할 수 없었습니다. 목록이 있으면 같은 거부가 유효성 규칙이 됩니다.
이력: chain 15부터 19까지는 admission_by_vote가 없었으므로 1,000 RAND를 보유한 키는 누구나 등록할 수 있었고, 진입 예산도 경계당 10,000 RAND의 고정값 max_stake_entry_per_epoch로 정했습니다. 고정값은 하나의 제네시스에 맞춰 정해지지만, 비율은 스테이크가 얼마이든 진입자의 몫을 같게 묶어 둡니다. 두 필드는 함께 설정할 수 없습니다.
투표에 의한 진입
fullnode v0.6.8(audit v6, STAKE-2)에서 새로 도입되었고 chain 20에서 켜져 있습니다. 1,000 RAND 키가 26개일 때 집합의 차단 가능한 3분의 1은 매수한 스테이크 약 13,000 RAND이며 슬래싱은 없습니다. 이 규칙 아래에서는 어떤 새 키가 등록할 수 있는지를 집합이 결정합니다.
- 투표는 하나의 액션이며
AdmitValidator { candidate, signatures }입니다. 번들이 없고 수수료도 없습니다. 각 서명은blake3("rand-admit-validator-1" ‖ genesis hash ‖ candidate address)에 대한 검증자의 Dilithium2 서명이며, 투표자 주소의 오름차순으로 나열합니다. 투표자는 투표 집합 가중치의 3분의 2를 엄격히 초과해 보유해야 하며, 이는 쿼럼 인증서와 같은 산술입니다. chain 20에서는 검증자 26개 중 18개입니다. 투표는 하나의 제네시스에 묶여 있고 nonce가 필요 없습니다. 진입 허가는 그것이 허용한 등록에서 소비됩니다. - 투표 집합은 현재 에포크에 대해 레지스터 자체가 도출한 집합입니다. 대기 중인 본딩은 여기서 가중치가 아니며, 언본딩은 즉시 이 집합에서 빠집니다. 후보 자신은 자기 자신에게 투표할 수 없습니다.
- 허용된 집합은 합의 상태이며, 등록을 기다리는 키는 최대 256개입니다(초과 시
AdmissionSetFull).rand_getAdmitted가 이를 제공하며,rand_getLimits.admission_by_vote는 체인에 투표가 필요한지를 알려 줍니다.
# each voting validator, offline
rand-node admit sign --candidate <registration hex or public key> --key node.key.json --genesis-hash <hash>
# whoever collects the lines
rand-node admit submit --candidate <…> --signature <line> --signature <line> … --rpc http://127.0.0.1:8545
그다음 본딩은 아래와 같이 진행됩니다. rand_getGenesisHash는 admit sign에 필요한 해시를 반환합니다.
네 가지 명령
어떤 바이너리가 어떤 액션을 맡는지는 어떤 키가 서명하는지에 따라 정해집니다. 본딩은 노트를 소비하므로 지갑 명령이고, 언본딩과 출금은 노드만 가지고 있는 검증자의 Dilithium2 키로 서명합니다.
| 액션 | 명령 | 실리는 곳 | 비용 |
|---|---|---|---|
| 등록 | rand-node register | 없음 — blob을 출력합니다 | 없음 |
Bond | rand bond | burn_r이 스테이크인 번들 | 지갑 노트에서 0.001 RAND |
Unbond | rand-node unbond | 없음(bundle: null) | 없음 |
Withdraw | rand-node withdraw | 없음(bundle: null) | 출금액에서 0.001 RAND |
등록
rand-node register --key node.key.json --payout "$(rand --key payout.key.json address)" --v2 [--rpc http://127.0.0.1:8545]
검증자의 주소와 16진수 Registration을 출력하며, 여기에는 공개 키, 지급 주소, 서명이 들어 있습니다. chain 20이 요구하는 --v2를 쓰면 서명은 체인의 제네시스 해시, 체인 id, 검증자 주소, 지급 주소에 대한 rand-register-2 아래에서 만들어집니다. 쓰지 않으면 v1 형식인 blake3("rand-register" ‖ bincode(chain_id, payout))이 되며 chain 20은 이를 거부합니다(chain 20과 같은 binding_domain: 1 체인에서는 등록 메시지가 어느 경우든 v2 형식입니다). 체인 id를, 그리고 --v2에서는 제네시스 해시를 RPC에서 읽어 오므로, 한 체인용으로 서명한 등록은 다른 체인에서 거부됩니다. 지급 주소는 지갑 주소이며 노드 키가 아닙니다. 또한 이후의 추가 본딩으로 바꿀 수 없는 유일한 필드입니다. 이 16진수는 스테이크를 보유한 사람에게 전달합니다.
본딩
rand bond <validator base58> <amount in RAND> [--registration <hex>] [--fee <RAND>] [--no-wait] [--cuda]
차폐 트랜잭션의 일반적인 형태입니다. 지갑은 chain 14 이후 모든 트랜잭션이 담아 온 4-in-4-out 숨김 자산 번들을 증명하며, 스테이킹 금액은 RAND 소각분인 burn_r에 들어갑니다. 원장은 burn_r == amount일 때만 본드를 받아들입니다. chain 20에서 번들은 chain 17 이후 모든 번들과 마찬가지로 두 개의 증명을 담습니다. tier-14 번들 증명과, 지출 키에 대한 tier-10 승인 증명입니다. 두 증명 모두 기본적으로 로컬에서 생성되며, Apple Silicon 노트북에서 약 2분이 걸립니다(증명 메모리). 지갑의 --prover 옵션을 쓰면 번들 증명은 짝을 이룬 증명자가 대신 생성하고, 승인 증명은 지갑의 머신에 남습니다. --registration은 검증자가 아직 레지스터에 없을 때에만 필요하며, 이미 있을 때는 거부됩니다. 지갑이 먼저 레지스터를 조회합니다. chain 20에서는 검증자 집합이 해당 키를 투표로 먼저 승인하지 않으면 등록 본드가 NotAdmitted로 거부됩니다(투표에 의한 승인). 추가 본드(top-up)에는 투표가 필요하지 않습니다. 등록 본드는 최소 1,000 RAND이며, 추가 본드는 0보다 큰 어떤 금액이든 가능합니다. 체인이 알게 되는 것은 이 검증자의 스테이크가 이만큼 늘었다는 사실뿐입니다. 어떤 노트가 비용을 냈는지, 그 노트가 누구의 것이었는지는 일반 전송과 마찬가지로 숨겨지므로, 금액 외에는 아무것도 드러내지 않고 누구나 검증자에게 스테이킹할 수 있습니다.
submitted bond 8c3f…e1
0 RAND out, 1000 RAND burned, 0.999 RAND change, fee 0.001 RAND, anchored at height 412
2nRdFC…: stake 1000 RAND, counting as consensus weight from epoch 69
지갑이 출력하는 에포크는 다음 경계입니다. 여기에는 활성화 지연이 포함되지 않습니다. chain 20에서 에포크 68에 커밋된 본드는 일러야 에포크 71부터 가중치가 되며, 경계마다 활성 가중치의 4분의 1인 진입 한도가 소진되었다면 그보다 늦어집니다.
Unbond
rand-node unbond <amount in RAND> --key node.key.json [--rpc …] [--no-wait]
amount를 stake에서 pending으로 옮기고 release_epoch = epoch + 2를 부여하며 nonce를 1 증가시킵니다. 수수료가 없고 번들도 없으며, chain 14부터 19까지는 blake3("rand-unbond" ‖ bincode(chain_id, validator, amount, nonce))에 대해, chain 20에서는 blake3("rand-unbond-2" ‖ bincode(genesis_hash, chain_id, validator, amount, nonce))에 대해 서명합니다(genesis binding_domain: 1). 블록 한 개의 시간 안에 커밋됩니다. rand-node v0.6.8은 접속한 체인에 맞는 형식을 선택합니다. 스테이크보다 많은 금액이나 0을 unbond하는 요청은 거부되며, 한 에포크 안의 여러 unbond는 하나의 pending 행으로 합쳐집니다.
Withdraw
rand-node withdraw <amount in RAND> --key node.key.json [--rpc …] [--no-wait]
해제된 가치, 즉 rewards와 release_epoch가 도래한 pending 행들을 오래된 것부터 차례로 모아, 레지스터의 payout 주소로 예치 노트에 지급합니다. 서명은 검증자가 하며(chain 20에서는 unbond와 마찬가지로 genesis 해시를 앞세운 rand-withdraw-2 아래에서 서명합니다) 번들은 없지만 무료는 아닙니다. 기본 수수료 0.001 RAND가 금액에서 차감되어 해당 트랜잭션을 적용하는 블록의 제안자에게 돌아갑니다. 따라서 1,000 RAND를 인출하면 999.999짜리 노트가 만들어지고, 기본 수수료를 감당하지 못하는 금액은 거부됩니다. 노트는 체인이 직접 계산합니다.
cm = H_CM(payout.pk, from = 0, amount − 0.001 RAND, asset 0, time, r)
time은 명령을 실행한 시점의 헤드 높이로(출력되며 서명에 바인딩됩니다), 번들 윈도(chain 20에서는 1,024 블록이며 genesis의 proof_window_blocks로 정해지고, 이전 체인에서는 256 블록) 안의 어느 시점이든 허용됩니다. r은 공개되므로 체인은 외부에서 들어온 커밋먼트를 받아들이지 않습니다. 이후 payout 지갑은 아무 정보도 전달받지 않고 스캔만으로 해당 노트를 찾습니다.
rand --key payout.key.json balance # balance: 999.999 RAND
참여와 이탈
# on the joining validator's machine
rand-node register --key node.key.json --payout "$(rand --key payout.key.json address)" --v2
rand-node run --datadir ./data --key node.key.json --validator --bootstrap /ip4/…/p2p/…
# it syncs and observes; rand_status says is_validator true, active_validator false
# on chain 20: validators holding more than two thirds of the weight vote the key in
rand-node admit sign --candidate <hex from above> --key <their node.key.json> --genesis-hash <hash>
rand-node admit submit --candidate <hex from above> --signature <line> … --rpc http://127.0.0.1:8545
# on the machine holding the stake
rand bond <validator base58> 1000 --registration <hex>
rand validators # the new row, active false until it activates
# from its activation epoch on (on chain 20, three boundaries after the bond at the earliest)
rand-node status # active_validator: true
rand-node unbond 1000 --key node.key.json # out of the set at the next boundary
# wait two epochs
rand-node withdraw 1000 --key node.key.json # a note worth 999.999 RAND at the payout address
unbond하기 전에 정족수 계산을 확인하십시오. n개로 이루어진 집합은 스테이크의 2/3 초과가 온라인 상태여야 합니다. chain 20에서는 26명의 검증자 모두가 정확히 MIN_STAKE에 있으므로, 조금이라도 unbond하면 해당 노드는 다음 에포크의 집합에서 빠지며, 26명 중 18명이 계속 가동되어야 합니다. 가동 중이지만 투표하지 않는 검증자도 정족수에 불리하게 계산됩니다. chain 14 이후 검증자는 자신의 시계보다 15초를 넘게 앞선 블록에는 투표를 보류하므로, 시계가 느리게 가는 검증자는 오류 없이 투표를 멈춥니다. NTP를 실행하십시오(검증자 운영).
Genesis는 --validator <key or hex pubkey>,<stake>,<payout> 하나당 항목 하나씩 레지스터를 직접 시딩하며, 최소값보다 낮은 스테이크는 거부합니다. chain 20의 genesis는 chain 19와 같은 키로 각각 1,000 RAND인 26개 행을 시딩합니다.
v0.5.11부터 빌드에는 genesis 베스팅도 포함됩니다. 이는 시간 잠금이 걸린 할당의 공개 레지스터로, 취소할 수 없는 항목은 여기에서 잠긴 RAND를 검증자의 스테이크로 본딩하고(rand-node vesting bond) 되찾을 수 있습니다(rand-node vesting unbond). genesis에 vesting 섹션이 있어야 하는데, chain 20을 포함해 어떤 체인에도 이 섹션이 없으므로 해당 동작은 승인 단계에서 거부됩니다. (v0.6.8에서는 취소 가능한 베스팅 항목도 고정된 트레저리에 대한 임계 서명자 수만이 취소할 수 있도록 하며, 이 역시 vesting 섹션이 있을 때에만 적용됩니다.)
공개되는 것
| 공개 | 비공개 | |
|---|---|---|
| 레지스터 | 모든 행: 주소, 스테이크, unbonding 큐, 미지급 보상, payout 주소, nonce | 없음 |
Bond | 검증자, 금액, 등록이 첨부되었는지 여부, 번들의 burn_r | 어떤 노트가 비용을 냈는지, 누가 소유하는지, 거스름돈 |
AdmitValidator (chain 20) | 후보와 서명이 포함된 모든 투표자 | 그 밖에 존재하는 것은 없음 |
Unbond | 검증자, 금액, nonce | 그 밖에 존재하는 것은 없음 |
Withdraw | 검증자, 금액, nonce, 노트의 time과 r | 누가 노트를 열 수 있는지, 그리고 이후 그 노트의 모든 지출 |
보상은 검증자가 제안한 블록의 수수료입니다. 블록 보상과 인플레이션은 없으며, chain 20에서는 검증자에 대한 슬래싱과 제재(jailing)도 없습니다. 잘못 행동한 검증자도 스테이크를 잃지 않으며, 그에 대한 대응은 운영자의 몫입니다. Fullnode v0.6.8에는 리더 이중 서명(equivocation)에 대한 슬래싱 규칙이 실제로 들어 있습니다(감사 v6, STAKE-1: 한 키가 하나의 뷰에 대해 서명한 두 개의 블록 헤더는 스테이크의 equivocation_bps만큼을 소멸시키고 해당 키를 jail합니다). 다만 이는 genesis에 staking.slashing 섹션이 있을 때에만 적용되며, chain 20에는 이 섹션이 없습니다(rand_getLimits.slashing은 null입니다). fullnode는 스테이크를 둘 이상의 운영자가 보유하게 된 뒤에야 이를 켜도록 권장합니다. (블록 집계 코드에는 같은 nonce에서 서로 다른 두 집계에 서명한 집계자를 위한 SlashAggregator 동작도 있으나, 집계 섹션이 없는 chain 20에서는 활성화되어 있지 않습니다.)
다시 읽기
rand validators # the whole register
rand status # is_validator / active_validator for the node asked
→ {"method":"rand_getValidators","params":[]}
← [{ "address": "2nRdFC…", "stake": "1000000000000",
"pending": [{ "release_epoch": 71, "amount": "1000000000000" }],
"rewards": "4000000", "payout": "rand1…", "nonce": 3, "active": true, "jailed_until": null }]
→ {"method":"rand_getAdmitted","params":[]}
← { "admission_by_vote": true, "admitted": [], "max": 256 }
→ {"method":"rand_getEpoch","params":[]}
← { "epoch": 69, "epoch_blocks": 1000, "next_set": ["2nRdFC…", "ByDkxs…"] }
금액은 단위의 십진 문자열입니다. active는 해당 행이 현재 에포크를 실행 중인 집합에 속하는지를 나타내고, jailed_until은 슬래싱 섹션이 있을 때를 제외하면 null입니다. admitted는 투표로 승인되었지만 아직 등록하지 않은 키의 목록이며, next_set은 경계 이전의 모든 bond와 unbond에 따라 달라지는 예상치입니다. 탐색기의 GET /validators는 여기에 share_percent, blocks_proposed, 마지막으로 제안한 높이를 더해 제공합니다.