Rand Protocol
Documentación
Guías

Staking

Registro de un validator, admisión por voto del conjunto, bonding de stake desde una wallet, unbonding y retiro, la dirección de pago, las épocas, el presupuesto de entradas de stake y por qué no hay slashing en la cadena en producción.

Usa esta página para unirte al conjunto de validadores de una cadena en funcionamiento, para salir de él y para leer el registro. El peso de consenso tiene que ser público, así que el registro de validadores es la única tabla de esta cadena que guarda importes en claro; aun así, el stake sale de una nota blindada y vuelve a entrar en otra.

El registro

Una fila por cada validador que haya depositado stake alguna vez, identificada por su dirección en 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 los campos son públicos y el registro se incluye mediante hash en la raíz de estado de cada bloque. Una fila nunca se elimina: un validador que retira todo su stake conserva una fila con stake = 0.

constantevalorsignificado
MIN_STAKE1,000 RANDstake que necesita una fila para formar parte del conjunto de una época
MAX_VALIDATORS100tamaño máximo del conjunto de una época
UNBONDING_EPOCHS2épocas que espera un importe retirado del stake antes de poder retirarse
epoch_blocks1,000 en la cadena 20bloques por época; un parámetro de génesis

Épocas

epoch(h) = h / epoch_blocks. El conjunto de la época e ≥ 1 se deriva del registro tal como estaba en el último bloque de la época e − 1: todas las filas con stake ≥ MIN_STAKE, las primeras MAX_VALIDATORS según (stake descending, address ascending), ordenadas por dirección. El líder de una vista, los votantes de un bloque y la verificación de un certificado usan el conjunto de la época a la que pertenece el bloque. De ello se siguen dos consecuencias:

  • Un bond no es peso hasta un límite posterior. Consta en el registro de inmediato, pero el conjunto solo cambia en un límite de época: el siguiente en una cadena sin sección staking, uno posterior en la cadena 20 (más abajo).
  • Un unbond resta peso en el siguiente límite, mientras que el importe espera dos épocas para poder retirarse. Un validador deja de proponer mucho antes de poder retirar.

Si una derivación resulta vacía, se mantiene el conjunto anterior. Un nodo iniciado con --validator cuya clave no está en ningún conjunto vigente se limita a observar hasta que una época lo admite, de modo que un validador que hace bond después del génesis no necesita reiniciar; rand_status informa por separado de is_validator (la clave está aquí) y de active_validator (está en el conjunto vigente).

La sección staking

Un génesis puede incluir una sección staking (fullnode v0.5.4, auditoría v4 STAKE-2). Sin ella, las reglas anteriores son toda la historia, como en la cadena 14. Todas las cadenas desde la cadena 15 la incluyen. La de la cadena 20 activa lo siguiente:

"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…"]
}
campocadena 20regla
bond_activation_epochs2un bond aplicado en la época e se pone en cola para la época e + 1 + 2; tanto un registro como un aumento. Su importe consta en stake de inmediato pero no es peso hasta entonces, y el stake en cola no puede retirarse con unbond
max_stake_entry_bps_per_epoch2,500 (25 %)el máximo de stake, registros y aumentos en conjunto, que se convierte en peso en un límite de época, como fracción del peso ya activo en ese momento: ⌊active · bps / 10,000⌋. Las filas vencidas que exceden esa cifra esperan al siguiente límite, en orden de bond. Con los 26,000 RAND de stake activo de la cadena 20, son 6,500 RAND por límite. La cuota de un nuevo participante tras k límites es como máximo 1 − 1.25^−k a cualquier escala, cerca de un tercio tras dos
admission_by_votetrueun bond que registra una clave nueva se rechaza (NotAdmitted) hasta que los validadores que poseen más de dos tercios del peso de voto hayan votado a favor de la clave (más abajo); un aumento no requiere voto
max_weight_bps3,333el peso de ningún validador supera el 33.33 % del total de su conjunto; el exceso se recorta en el conjunto, el stake del registro no cambia
registration_v2trueun registro firma rand-register-2 sobre el hash de génesis, el identificador de cadena, la dirección del validador y el pago; un registro v1 se rechaza
faucet_budget_per_epoch10,000 RANDel faucet emite como máximo esta cantidad por época (FaucetBudgetExhausted si se excede; el contador se reinicia en la época siguiente)
faucet_recipients16 walletsun Mint del faucet solo paga a estas wallets (FaucetRecipientNotAllowed); esta lista es lo que permite que un faucet conviva con el puente
faucet_minters18 validadoresun Mint del faucet solo puede ser firmado por estas claves de validador (MinterNotAllowed), comprobado en la admisión y al aplicar un bloque, por mucho tiempo que otra clave lleve en bond. En la cadena 20 son 18 de los 26 validadores de génesis. La lista figura en todos los génesis desde la cadena 16
slashingausenteno hay sección de slashing, por lo que ningún validador es penalizado ni encarcelado (Qué es público)

Con la sección presente, el contador del faucet y la cola de bonds se anexan a la raíz de estado, bajo el dominio rand-state-5, y la hoja del validador es rand-validator-leaf-4. La cadena 20 tiene además precios de gas dinámicos y estado de programas RPL-2, anexados a continuación, lo que traslada su dominio a rand-state-8, y la admisión por voto envuelve esa raíz una vez más como H("rand-state-admitted-1", root ‖ admitted_root). La cadena 15 no tenía lista faucet_minters: allí, cada nodo ponía en su pool y reenviaba un Mint del faucet solo cuando lo firmaba un validador de génesis (MinterNotAllowed, como política del nodo), para que un validador con bond posterior al génesis no pudiera agotar el presupuesto a través del pool. Con la lista, el mismo rechazo es una regla de validez.

Historia: las cadenas 15 a 19 no tenían admission_by_vote, de modo que cualquier clave con 1,000 RAND podía registrarse, y fijaban el presupuesto de entrada como un max_stake_entry_per_epoch fijo de 10,000 RAND por límite. Una cifra fija está dimensionada para un solo génesis; la fracción mantiene la cuota de un nuevo participante en la misma proporción sea cual sea el stake. Los dos campos no pueden estar definidos a la vez.

Admisión por voto

Novedad de fullnode v0.6.8 (auditoría v6, STAKE-2) y activa en la cadena 20. Con 26 claves de 1,000 RAND cada una, un tercio de bloqueo del conjunto equivale a unos 13,000 RAND de stake comprado, y nada se penaliza con slashing. Bajo esta regla, el conjunto decide qué claves nuevas pueden registrarse.

  • El voto es una acción, AdmitValidator { candidate, signatures }, sin bundle y sin comisión. Cada firma es una firma Dilithium2 de un validador sobre blake3("rand-admit-validator-1" ‖ genesis hash ‖ candidate address), en orden ascendente de las direcciones de los votantes. Los votantes deben poseer estrictamente más de dos tercios del peso del conjunto votante, la aritmética de un certificado de quórum: en la cadena 20, 18 de los 26 validadores. Un voto queda ligado a un único génesis y no necesita nonce: una admisión se consume con el registro que permite.
  • El conjunto votante es la propia derivación del registro para la época vigente. Un bond en cola no cuenta como peso en él, y un unbond lo abandona de inmediato. Un candidato no puede votarse a sí mismo.
  • El conjunto admitido es estado de consenso, de como máximo 256 claves a la espera de registrarse (AdmissionSetFull si se excede). rand_getAdmitted lo sirve; rand_getLimits.admission_by_vote indica si una cadena exige el 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

Después el bond continúa como se describe a continuación. rand_getGenesisHash devuelve el hash que necesita admit sign.

Los cuatro comandos

Qué binario posee cada acción se deduce de qué clave la firma: un bond gasta notas, así que es un comando de la wallet; un unbond y un withdraw los firma la clave Dilithium2 del validador, que solo guarda el nodo.

accióncomandose apoya enpaga
registerrand-node registernada: imprime un blobnada
Bondrand bondun bundle cuyo burn_r es el stake0.001 RAND de las notas de la wallet
Unbondrand-node unbondnada (bundle: null)nada
Withdrawrand-node withdrawnada (bundle: null)0.001 RAND del importe retirado

Register

rand-node register --key node.key.json --payout "$(rand --key payout.key.json address)" --v2 [--rpc http://127.0.0.1:8545]

Imprime la dirección del validador y una Registration en hexadecimal: su clave pública, la dirección de pago y una firma. Con --v2, que la cadena 20 exige, la firma se hace bajo rand-register-2 sobre el hash de génesis de la cadena, el identificador de cadena, la dirección del validador y el pago; sin él, se usa la forma v1, blake3("rand-register" ‖ bincode(chain_id, payout)), que la cadena 20 rechaza (en una cadena con binding_domain: 1, como la cadena 20, el mensaje de registro es en cualquier caso el de v2). Se consulta el RPC para obtener el identificador de cadena y, con --v2, el hash de génesis, de modo que un registro firmado para una cadena se rechaza en otra. La dirección de pago es una dirección de wallet, no una clave de nodo, y es el único campo que un aumento posterior no puede cambiar. Entrega el hexadecimal a quien posea el stake.

Bond

rand bond <validator base58> <amount in RAND> [--registration <hex>] [--fee <RAND>] [--no-wait] [--cuda]

Una transacción blindada ordinaria: la wallet prueba el paquete de activos ocultos de 4 entradas y 4 salidas que toda transacción lleva desde la chain 14, con el monto apostado en su quema de RAND, burn_r, y el libro mayor admite un bono solo cuando burn_r == amount. En la chain 20 el paquete lleva dos pruebas, como todo paquete desde la chain 17: la prueba del paquete de nivel 14 y una prueba de autorización de nivel 10 sobre la clave de gasto. Ambas se prueban localmente por defecto, en unos dos minutos en un portátil Apple Silicon (Proving memory). Con la opción --prover de la wallet, la prueba del paquete la genera en cambio un prover emparejado, y la prueba de autorización permanece en la máquina de la wallet. --registration se exige exactamente cuando el validador aún no está en el registro, y se rechaza cuando ya está; la wallet consulta primero el registro. En la chain 20, un bono de registro se rechaza con NotAdmitted a menos que el conjunto haya votado antes la clave (Admission by vote); un aumento no necesita voto. El registro exige un bono de al menos 1,000 RAND; un aumento puede ser de cualquier monto superior a cero. Lo que la cadena averigua es que la participación de este validador creció en esta cantidad; qué notes pagaron, y de quién eran, queda oculto como en una transferencia: cualquiera puede apostar a favor de un validador sin revelar nada más que el monto.

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

La época que imprime la wallet es el próximo límite. No cuenta el retraso de activación: en la chain 20, un bono confirmado en la época 68 pesa a partir de la época 71 como pronto, y más tarde si se agota el presupuesto de entrada, una cuarta parte del peso activo por límite.

Unbond

rand-node unbond <amount in RAND> --key node.key.json [--rpc …] [--no-wait]

Mueve amount de stake a pending con release_epoch = epoch + 2 e incrementa el nonce. Es gratuito, sin paquete, y se firma sobre blake3("rand-unbond" ‖ bincode(chain_id, validator, amount, nonce)) en las chains 14 a 19 y sobre blake3("rand-unbond-2" ‖ bincode(genesis_hash, chain_id, validator, amount, nonce)) en la chain 20 (genesis binding_domain: 1); se confirma en el tiempo de un bloque. rand-node v0.6.8 elige la forma correspondiente a la cadena con la que habla. Se rechaza retirar de la apuesta más de lo que hay, o cero; varios unbonds en una misma época se fusionan en una sola fila de pending.

Withdraw

rand-node withdraw <amount in RAND> --key node.key.json [--rpc …] [--no-wait]

Paga el valor liberado —rewards más las filas de pending cuyo release_epoch ya llegó, las más antiguas primero— en una deposit note en la dirección payout del registro. Lo firma el validador (en la chain 20 bajo rand-withdraw-2, sobre el genesis hash primero, igual que el unbond), no lleva paquete, pero no es gratuito: la base de 0.001 RAND va al proponente del bloque que lo aplica, descontada del monto, de modo que un retiro de 1,000 RAND crea una note de 999.999 y se rechaza un monto que no alcance a cubrir la base. La cadena calcula la note por sí misma:

cm = H_CM(payout.pk, from = 0, amount − 0.001 RAND, asset 0, time, r)

time es la altura de la cabeza cuando se ejecutó el comando (se imprime y queda ligada por la firma), admitida en cualquier punto dentro de la ventana del paquete (1,024 bloques en la chain 20, cuyo genesis fija proof_window_blocks; 256 en las cadenas anteriores); r se publica para que la cadena nunca tome un compromiso de la red. Después, la wallet de pago encuentra la note escaneando, sin que se le diga nada:

rand --key payout.key.json balance      # balance: 999.999 RAND

Joining and leaving

# 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 la aritmética del quórum antes de retirar una apuesta: un conjunto de n necesita más de 2/3 de su stake en línea. En la chain 20, los 26 validadores están exactamente en MIN_STAKE, así que cualquier unbond hace que ese nodo salga del conjunto de la próxima época, y 18 de los 26 deben permanecer activos. Un validador que está activo pero no vota también cuenta en contra del quórum: desde la chain 14 un validador retiene su voto de un bloque que esté más de 15 s por delante de su propio reloj, de modo que un validador con el reloj atrasado deja de votar sin ningún error. Ejecute NTP en él (Running a validator).

El genesis siembra el registro directamente —una entrada por cada --validator <key or hex pubkey>,<stake>,<payout>— y rechaza una participación inferior al mínimo. El genesis de la chain 20 siembra 26 filas de 1,000 RAND cada una, las mismas claves que las de la chain 19.

Desde v0.5.11, la compilación incluye además el vesting de genesis: un registro público de asignaciones sujetas a bloqueo temporal, a partir del cual una entrada irrevocable puede vincular RAND bloqueados como participación de un validador (rand-node vesting bond) y recuperarlos (rand-node vesting unbond). Requiere una sección vesting en el genesis. Ninguna cadena la tiene, la chain 20 incluida, y sin ella esas acciones se rechazan en la admisión. (v0.6.8 también permite que una entrada de vesting revocable sea revocada solo por un umbral de firmantes hacia una tesorería fija, de nuevo solo bajo una sección vesting.)

What is public

públicaoculta
el registrocada fila: dirección, stake, cola de unbonding, recompensas no pagadas, dirección de pago, noncenada
Bondel validador, el monto, si se adjuntó un registro, el burn_r del paquetequé notes pagaron, quién las posee, el cambio
AdmitValidator (chain 20)el candidato y cada votante con su firmano existe nada más
Unbondel validador, el monto, el nonceno existe nada más
Withdrawel validador, el monto, el nonce, el time y el r de la notequién puede abrir la note, y cada gasto posterior de ella

Las recompensas son las comisiones de los bloques que un validador propuso. No hay recompensas de bloque ni inflación, y en la chain 20 no hay slashing ni jailing de validadores: un validador que se comporta mal no le cuesta nada a su stake, y el remedio queda en manos de los operadores. El fullnode v0.6.8 sí incluye una regla de slashing para la equivocación del líder (auditoría v6, STAKE-1: dos encabezados de bloque firmados para una misma vista por una misma clave destruyen equivocation_bps del stake y encarcelan la clave), pero solo bajo una sección staking.slashing del genesis, y la chain 20 no tiene ninguna (rand_getLimits.slashing es null); el fullnode lo recomienda solo cuando el stake lo mantenga más de un operador. (El código de agregación de bloques también incluye una acción SlashAggregator para agregadores que firman dos agregados distintos con un mismo nonce, y no está activa en la chain 20, que no tiene sección de agregación.)

Reading it back

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…"] }

Los montos son cadenas decimales de unidades. active indica si la fila pertenece al conjunto que ejecuta la época actual; jailed_until es null salvo bajo una sección de slashing; admitted lista las claves votadas que aún no se han registrado; next_set es una proyección que mueve cada bond y unbond previo al límite. El GET /validators del explorador añade share_percent, blocks_proposed y la última altura propuesta.

Verificado por última vez con el commit c0878c3f del fullnode (cadena 21) el 2026-10-12. Cuando el código y esta página no coinciden, el código tiene razón; avísenos.