Use esta página para entrar no conjunto de validadores de uma chain em funcionamento, para sair dele e para ler o registro de volta. O peso no consenso precisa ser público, de modo que o registro de validadores é a única tabela desta chain que guarda valores às claras; a participação (stake) ainda sai de uma note shielded e volta para uma.
O registro
Uma linha por validador que já fez bond, indexada por seu endereço em 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
}
Todos os campos são públicos e o registro entra no hash da raiz de estado de cada bloco. Uma linha nunca é apagada: um validador que faz unbond de tudo mantém uma linha com stake = 0.
| constante | valor | significado |
|---|---|---|
MIN_STAKE | 1,000 RAND | stake de que uma linha precisa para figurar no conjunto de uma época |
MAX_VALIDATORS | 100 | maior conjunto que uma época pode ter |
UNBONDING_EPOCHS | 2 | épocas que um valor desvinculado espera antes de poder ser sacado |
epoch_blocks | 1,000 na chain 20 | blocos por época; um parâmetro do genesis |
Épocas
epoch(h) = h / epoch_blocks. O conjunto da época e ≥ 1 é derivado do registro tal como estava no último bloco da época e − 1: toda linha com stake ≥ MIN_STAKE, os MAX_VALIDATORS primeiros por (stake descending, address ascending), ordenados por endereço. O líder de uma view, os votantes de um bloco e a verificação de um certificado usam todos o conjunto da época a que o bloco pertence. Duas consequências:
- Um bond só vira peso em uma fronteira posterior. Ele entra no registro de imediato, mas o conjunto só muda em uma fronteira: a seguinte, em uma chain sem seção
staking; mais adiante, na chain 20 (abaixo). - Um unbond custa peso na próxima fronteira, enquanto o valor espera duas épocas para poder ser sacado. Um validador deixa de propor muito antes de poder sacar.
Se uma derivação resultar vazia, o conjunto anterior é mantido. Um nó iniciado com --validator cuja chave não está em nenhum conjunto atual apenas observa até que uma época o admita, de modo que um validador que faz bond após o genesis não precisa de reinício; rand_status informa is_validator (a chave está aqui) e active_validator (ela está no conjunto atual) separadamente.
A seção staking
Um genesis pode trazer uma seção staking (fullnode v0.5.4, audit v4 STAKE-2). Sem ela, as regras acima são toda a história, como na chain 14. Toda chain desde a chain 15 a traz. A da chain 20 ativa:
"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…"]
}
| campo | chain 20 | regra |
|---|---|---|
bond_activation_epochs | 2 | um bond aplicado na época e entra na fila para a época e + 1 + 2; vale tanto para um registro quanto para um top-up. Seu valor está em stake de imediato, mas só vira peso então, e o stake em fila não pode sofrer unbond |
max_stake_entry_bps_per_epoch | 2,500 (25 %) | o máximo de stake, registros e top-ups somados, que vira peso em uma fronteira, como fração do peso já ativo nela: ⌊active · bps / 10,000⌋. As linhas vencidas além desse limite esperam a fronteira seguinte, na ordem dos bonds. Com os 26,000 RAND de stake ativo da chain 20, são 6,500 RAND por fronteira. A parcela de um entrante após k fronteiras é no máximo 1 − 1.25^−k em qualquer escala, cerca de um terço após duas |
admission_by_vote | true | um bond que registra uma chave nova é recusado (NotAdmitted) até que validadores com mais de dois terços do peso de voto tenham votado pela chave (abaixo); um top-up não precisa de voto |
max_weight_bps | 3,333 | o peso de nenhum validador excede 33.33 % do total do seu conjunto; o excesso é limitado no conjunto, e o stake do registro não muda |
registration_v2 | true | um registro assina rand-register-2 sobre o hash do genesis, o chain id, o endereço do validador e o payout; um registro v1 é recusado |
faucet_budget_per_epoch | 10,000 RAND | o faucet cunha no máximo esse valor por época (FaucetBudgetExhausted além dele; o contador reinicia na época seguinte) |
faucet_recipients | 16 wallets | um Mint do faucet paga apenas a essas wallets (FaucetRecipientNotAllowed); essa lista é o que permite a um faucet conviver com a bridge |
faucet_minters | 18 validadores | um Mint do faucet só pode ser assinado por essas chaves de validador (MinterNotAllowed), verificado na admissão e quando um bloco é aplicado, por mais tempo que outra chave esteja em bond. Na chain 20 são 18 dos 26 validadores do genesis. A lista consta em todo genesis desde a chain 16 |
slashing | absent | sem seção de slashing, logo nenhum validador sofre slashing nem jail (O que é público) |
Com a seção, o contador do faucet e a fila de bonds são acrescentados à raiz de estado, sob o domínio rand-state-5, e a folha do validador é rand-validator-leaf-4. A chain 20 tem ainda preços de gas dinâmicos e estado de programas RPL-2, acrescentados depois deles, o que leva seu domínio a rand-state-8, e a admissão por voto envolve essa raiz mais uma vez como H("rand-state-admitted-1", root ‖ admitted_root). A chain 15 não tinha lista faucet_minters: nela, todo nó colocava no pool e repassava um Mint do faucet somente quando um validador do genesis o assinava (MinterNotAllowed, como política do nó), para que um validador que fez bond após o genesis não pudesse esgotar o orçamento pelo pool. Com a lista, a mesma recusa passa a ser uma regra de validade.
Histórico: as chains 15 a 19 não tinham admission_by_vote, de modo que qualquer chave com 1,000 RAND podia se registrar, e fixavam o orçamento de entrada como um max_stake_entry_per_epoch fixo de 10,000 RAND por fronteira. Um valor fixo é dimensionado para um único genesis; a fração mantém um entrante na mesma parcela, seja qual for o stake. Os dois campos não podem ser definidos juntos.
Admissão por voto
Novidade do fullnode v0.6.8 (audit v6, STAKE-2) e ativa na chain 20. Com 26 chaves de 1,000 RAND cada, um terço de bloqueio do conjunto equivale a cerca de 13,000 RAND de stake comprado, e nada sofre slashing. Sob esta regra, o conjunto decide quais chaves novas podem se registrar.
- O voto é uma ação,
AdmitValidator { candidate, signatures }, sem bundle e sem taxa. Cada assinatura é uma assinatura Dilithium2 de um validador sobreblake3("rand-admit-validator-1" ‖ genesis hash ‖ candidate address), em ordem crescente dos endereços dos votantes. Os votantes devem deter estritamente mais de dois terços do peso do conjunto votante, a aritmética de um quorum certificate: na chain 20, 18 dos 26 validadores. Um voto fica preso a um único genesis e dispensa nonce: uma admissão é consumida pelo registro que ela permite. - O conjunto votante é a própria derivação do registro para a época atual. Um bond em fila não é peso nele, e um unbond o retira de imediato. Um candidato não pode votar em si mesmo.
- O conjunto admitido é estado de consenso, com no máximo 256 chaves aguardando registro (
AdmissionSetFullalém disso).rand_getAdmittedo serve;rand_getLimits.admission_by_voteinforma se uma chain exige o voto.
# 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
Em seguida o bond prossegue como abaixo. rand_getGenesisHash devolve o hash de que admit sign precisa.
Os quatro comandos
Qual binário detém qual ação decorre de qual chave a assina: um bond gasta notes, então é um comando da wallet; um unbond e um withdraw são assinados pela chave Dilithium2 do validador, que só o nó possui.
| ação | comando | viaja em | paga |
|---|---|---|---|
| register | rand-node register | nada — imprime um blob | nada |
Bond | rand bond | um bundle cujo burn_r é o stake | 0.001 RAND das notes da wallet |
Unbond | rand-node unbond | nada (bundle: null) | nada |
Withdraw | rand-node withdraw | nada (bundle: null) | 0.001 RAND do valor sacado |
Register
rand-node register --key node.key.json --payout "$(rand --key payout.key.json address)" --v2 [--rpc http://127.0.0.1:8545]
Imprime o endereço do validador e uma Registration em hex: sua chave pública, o endereço de payout e uma assinatura. Com --v2, exigido pela chain 20, a assinatura é feita sob rand-register-2 sobre o hash do genesis da chain, o chain id, o endereço do validador e o payout; sem ele, vale a forma v1, blake3("rand-register" ‖ bincode(chain_id, payout)), que a chain 20 recusa (em uma chain com binding_domain: 1, como a chain 20, a mensagem de registro é de qualquer modo a v2). O RPC é consultado para obter o chain id e, com --v2, o hash do genesis, de modo que um registro assinado para uma chain é recusado em outra. O endereço de payout é um endereço de wallet, não uma chave de nó, e é o único campo que um top-up posterior não pode alterar. Entregue o hex a quem detém o stake.
Bond
rand bond <validator base58> <amount in RAND> [--registration <hex>] [--fee <RAND>] [--no-wait] [--cuda]
Uma transação blindada comum: a carteira prova o pacote de ativos ocultos de 4 entradas e 4 saídas que toda transação carrega desde a chain 14, com o valor em stake na sua queima de RAND, burn_r, e o ledger só admite um bond quando burn_r == amount. Na chain 20 o pacote carrega duas provas, como todo pacote desde a chain 17: a prova do pacote de tier 14 e uma prova de autorização de tier 10 sobre a chave de gasto. Ambas são provadas localmente por padrão, em cerca de dois minutos num laptop Apple Silicon (Memória de prova). Com a opção --prover da carteira, a prova do pacote é feita por um provador pareado, e a prova de autorização permanece na máquina da carteira. --registration é exigido exatamente quando o validador ainda não está no registro, e recusado quando já está; a carteira consulta o registro primeiro. Na chain 20, um bond de registro é recusado com NotAdmitted a menos que o conjunto tenha votado antes pela admissão da chave (Admissão por voto); um aporte adicional não precisa de voto. O registro exige bond de pelo menos 1.000 RAND; um aporte adicional pode ser de qualquer valor acima de zero. O que a chain aprende é que o stake deste validador aumentou neste valor; quais notas pagaram, e de quem eram, permanece oculto como numa transferência — qualquer pessoa pode fazer stake em um validador sem revelar nada além do valor.
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
A época que a carteira exibe é o próximo limite. Ela não conta o atraso de ativação: na chain 20, um bond confirmado na época 68 passa a ter peso a partir da época 71, no mínimo, e mais tarde se o orçamento de entrada, um quarto do peso ativo por limite, estiver esgotado.
Desvinculação
rand-node unbond <amount in RAND> --key node.key.json [--rpc …] [--no-wait]
Move amount de stake para pending com release_epoch = epoch + 2 e incrementa o nonce. É gratuita, sem pacote, assinada sobre blake3("rand-unbond" ‖ bincode(chain_id, validator, amount, nonce)) nas chains 14 a 19 e sobre blake3("rand-unbond-2" ‖ bincode(genesis_hash, chain_id, validator, amount, nonce)) na chain 20 (genesis binding_domain: 1); é confirmada no tempo de um bloco. O rand-node v0.6.8 escolhe a forma correspondente à chain com a qual se comunica. Desvincular mais do que o stake, ou zero, é recusado; várias desvinculações na mesma época se fundem em uma única linha de pending.
Saque
rand-node withdraw <amount in RAND> --key node.key.json [--rpc …] [--no-wait]
Paga o valor liberado — rewards mais as linhas de pending cujo release_epoch já chegou, das mais antigas às mais recentes — em uma nota de depósito no endereço payout do registro. É assinado pelo validador (na chain 20 sob rand-withdraw-2, sobre o hash do genesis primeiro, como a desvinculação) e sem pacote, mas não é gratuito: a base de 0,001 RAND vai para o proponente do bloco que o aplica, descontada do valor, de modo que um saque de 1.000 RAND cria uma nota de 999,999 e um valor que não cubra a base é recusado. A própria chain calcula a nota:
cm = H_CM(payout.pk, from = 0, amount − 0.001 RAND, asset 0, time, r)
time é a altura da cabeça da chain quando o comando foi executado (exibida e vinculada pela assinatura), admitida em qualquer ponto dentro da janela do pacote (1.024 blocos na chain 20, cujo genesis define proof_window_blocks; 256 nas chains anteriores); r é publicado para que a chain nunca receba um compromisso vindo da rede. Depois disso, a carteira de pagamento encontra a nota por varredura, sem que ninguém lhe diga nada:
rand --key payout.key.json balance # balance: 999.999 RAND
Entrada e saída
# 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
Verifique a aritmética do quórum antes de desvincular: um conjunto de n precisa de mais de 2/3 do seu stake online. Na chain 20, os 26 validadores estão exatamente em MIN_STAKE, portanto qualquer desvinculação já tira esse nó do conjunto da próxima época, e 18 dos 26 precisam permanecer ativos. Um validador que está ativo mas não vota também conta contra o quórum: desde a chain 14, um validador retém o voto em um bloco que esteja mais de 15 s à frente do seu próprio relógio, de modo que um validador com o relógio atrasado deixa de votar sem emitir erro. Execute NTP nele (Operar um validador).
O genesis alimenta o registro diretamente — uma entrada por --validator <key or hex pubkey>,<stake>,<payout> — e recusa um stake abaixo do mínimo. O genesis da chain 20 semeia 26 linhas de 1.000 RAND cada, com as mesmas chaves da chain 19.
Desde a v0.5.11, o build também traz vesting no genesis: um registro público de alocações com bloqueio temporal, a partir do qual uma entrada irrevogável pode fazer bond de RAND bloqueado como stake de um validador (rand-node vesting bond) e retirá-lo de volta (rand-node vesting unbond). Isso exige uma seção vesting no genesis. Nenhuma chain tem uma, a chain 20 incluída, e sem ela essas ações são recusadas na admissão. (A v0.6.8 também permite que uma entrada de vesting revogável seja revogada somente por um limiar de signatários para um tesouro fixo, novamente apenas sob uma seção vesting.)
O que é público
| público | oculto | |
|---|---|---|
| o registro | todas as linhas: endereço, stake, fila de desvinculação, recompensas não pagas, endereço de pagamento, nonce | nada |
Bond | o validador, o valor, se um registro foi anexado, o burn_r do pacote | quais notas pagaram, quem as possui, o troco |
AdmitValidator (chain 20) | o candidato e cada votante com sua assinatura | nada mais existe |
Unbond | o validador, o valor, o nonce | nada mais existe |
Withdraw | o validador, o valor, o nonce, o time e o r da nota | quem pode abrir a nota, e cada gasto posterior dela |
As recompensas são as taxas dos blocos que um validador propôs. Não há recompensas de bloco nem inflação, e na chain 20 não há slashing nem jailing de validadores: um validador que se comporta mal não perde nada do seu stake, e o remédio cabe aos operadores. O fullnode v0.6.8 de fato traz uma regra de slashing para equivocação de líder (auditoria v6, STAKE-1: dois cabeçalhos de bloco assinados para uma mesma view por uma mesma chave destroem equivocation_bps do stake e aplicam jail à chave), mas somente sob uma seção staking.slashing do genesis, e a chain 20 não tem nenhuma (rand_getLimits.slashing é null); o fullnode a recomenda apenas quando o stake estiver nas mãos de mais de um operador. (O código de agregação de blocos também traz uma ação SlashAggregator para agregadores que assinam dois agregados diferentes no mesmo nonce, e ela não está ativa na chain 20, que não tem seção de agregação.)
Lendo de volta
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…"] }
Os valores são strings decimais de unidades. active indica se a linha está no conjunto que executa a época atual; jailed_until é null, exceto sob uma seção de slashing; admitted lista as chaves votadas para entrar que ainda não se registraram; next_set é uma projeção que cada bond e desvinculação antes do limite desloca. O GET /validators do explorador acrescenta share_percent, blocks_proposed e a última altura proposta.