Rand Protocol
Dokumentation
Anleitungen

Staking

Registrierung eines Validators, Aufnahme durch die Abstimmung des Sets, Bonding von Stake aus einem Wallet, Unbonding und Auszahlung, die Auszahlungsadresse, Epochen, das Budget für Stake-Einträge und warum es in der Live-Chain kein Slashing gibt.

Verwenden Sie diese Seite, um dem Validator-Set einer laufenden Chain beizutreten, es zu verlassen und das Register auszulesen. Das Konsensgewicht muss öffentlich sein, daher ist das Validator-Register die einzige Tabelle dieser Chain, die Beträge im Klartext enthält; der Stake stammt dennoch aus einer shielded Note und fließt in eine zurück.

Das Register

Eine Zeile pro Validator, der jemals einen Bond eingegangen ist, geführt unter seiner base58-Adresse:

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
}

Jedes Feld ist öffentlich, und das Register fließt in den State Root jedes Blocks ein. Eine Zeile wird nie gelöscht: Ein Validator, der seinen gesamten Stake per Unbond abzieht, behält eine Zeile mit stake = 0.

constantvaluemeaning
MIN_STAKE1,000 RANDStake, den eine Zeile braucht, um überhaupt im Set einer Epoche zu stehen
MAX_VALIDATORS100größtes Set, das eine Epoche haben kann
UNBONDING_EPOCHS2Epochen, die ein per Unbond abgezogener Betrag wartet, bis er abgehoben werden kann
epoch_blocks1,000 on chain 20Blöcke pro Epoche; ein Genesis-Parameter

Epochen

epoch(h) = h / epoch_blocks. Das Set für die Epoche e ≥ 1 wird aus dem Register zum Stand des letzten Blocks der Epoche e − 1 abgeleitet: jede Zeile mit stake ≥ MIN_STAKE, davon die obersten MAX_VALIDATORS nach (stake descending, address ascending), sortiert nach Adresse. Der Leader einer View, die Voter eines Blocks und die Prüfung eines Zertifikats verwenden alle das Set der Epoche, zu der der Block gehört. Daraus folgen zwei Konsequenzen:

  • Ein Bond ist erst an einer späteren Grenze Gewicht. Er steht sofort im Register, doch das Set ändert sich nur an einer Epochengrenze – auf einer Chain ohne staking-Abschnitt an der nächsten, auf Chain 20 später (unten).
  • Ein Unbond kostet Gewicht an der nächsten Grenze, während der Betrag zwei Epochen wartet, bis er abhebbar wird. Ein Validator schlägt lange keine Blöcke mehr vor, bevor er abheben kann.

Ist eine Ableitung leer, wird das vorherige Set fortgeschrieben. Ein mit --validator gestarteter Node, dessen Schlüssel in keinem aktuellen Set steht, beobachtet nur, bis eine Epoche ihn aufnimmt. Ein Validator, der nach dem Genesis einen Bond eingeht, braucht also keinen Neustart; rand_status meldet is_validator (der Schlüssel ist hier) und active_validator (er steht im aktuellen Set) getrennt.

Der staking-Abschnitt

Ein Genesis kann einen staking-Abschnitt enthalten (fullnode v0.5.4, audit v4 STAKE-2). Ohne ihn sind die obigen Regeln die ganze Geschichte, wie bei Chain 14. Jede Chain seit Chain 15 enthält ihn. Bei Chain 20 sind folgende Schalter aktiviert:

"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…"]
}
fieldchain 20rule
bond_activation_epochs2ein in Epoche e angewandter Bond wird für Epoche e + 1 + 2 eingereiht; eine Registrierung ebenso wie ein Top-up. Sein Betrag steht sofort in stake, ist aber bis dahin kein Gewicht, und eingereihter Stake kann nicht per Unbond abgezogen werden
max_stake_entry_bps_per_epoch2,500 (25 %)der meiste Stake, Registrierungen und Top-ups zusammen, der an einer Grenze zu Gewicht wird, als Bruchteil des dort bereits aktiven Gewichts: ⌊active · bps / 10,000⌋. Fällige Zeilen darüber hinaus warten bis zur nächsten Grenze, in der Reihenfolge der Bonds. Bei den 26,000 RAND aktivem Stake von Chain 20 sind das 6,500 RAND pro Grenze. Der Anteil eines Neueinsteigers nach k Grenzen beträgt bei jeder Größenordnung höchstens 1 − 1.25^−k, nach zwei etwa ein Drittel
admission_by_votetrueein Bond, der einen neuen Schlüssel registriert, wird abgelehnt (NotAdmitted), bis Validatoren mit mehr als zwei Dritteln des Stimmgewichts den Schlüssel hineingewählt haben (unten); ein Top-up braucht keine Abstimmung
max_weight_bps3,333das Gewicht keines Validators übersteigt 33.33 % der Gesamtsumme seines Sets; der Überschuss wird im Set gekappt, der stake im Register bleibt unverändert
registration_v2trueeine Registrierung signiert rand-register-2 über den Genesis-Hash, die Chain-ID, die Validator-Adresse und die Auszahlung; eine v1-Registrierung wird abgelehnt
faucet_budget_per_epoch10,000 RANDder Faucet prägt höchstens diesen Betrag pro Epoche (FaucetBudgetExhausted darüber hinaus; der Zähler beginnt mit der nächsten Epoche neu)
faucet_recipients16 walletsein Faucet-Mint zahlt nur an diese Wallets (FaucetRecipientNotAllowed); diese Liste ermöglicht es, einen Faucet neben der Bridge zu betreiben
faucet_minters18 validatorsein Faucet-Mint darf nur von diesen Validator-Schlüsseln signiert werden (MinterNotAllowed), geprüft bei der Zulassung und beim Anwenden eines Blocks, gleichgültig, wie lange ein anderer Schlüssel bereits gebondet ist. Auf Chain 20 sind das 18 der 26 Genesis-Validatoren. Die Liste steht seit Chain 16 in jedem Genesis
slashingabsentkein Slashing-Abschnitt, also wird kein Validator geslasht oder gesperrt (What is public)

Unter dem Abschnitt werden der Faucet-Zähler und die Bond-Warteschlange an den State Root angehängt, unter der Domain rand-state-5, und das Validator-Blatt ist rand-validator-leaf-4. Chain 20 hat außerdem dynamische Gaspreise und den RPL-2-Programmzustand, die danach angehängt werden und seine Domain auf rand-state-8 verschieben; die Zulassung per Abstimmung umhüllt diesen Root ein weiteres Mal als H("rand-state-admitted-1", root ‖ admitted_root). Chain 15 hatte keine faucet_minters-Liste: Dort nahm jeder Node einen Faucet-Mint nur in den Pool auf und leitete ihn nur weiter, wenn ihn ein Genesis-Validator signiert hatte (MinterNotAllowed, als Node-Richtlinie), damit ein nach dem Genesis gebondeter Validator das Budget nicht über den Pool abschöpfen konnte. Mit der Liste ist dieselbe Ablehnung eine Gültigkeitsregel.

Verlauf: Die Chains 15 bis 19 hatten kein admission_by_vote, sodass sich jeder Schlüssel mit 1,000 RAND registrieren konnte, und sie legten das Eintrittsbudget als festes max_stake_entry_per_epoch von 10,000 RAND pro Grenze fest. Eine feste Zahl ist auf ein einzelnes Genesis zugeschnitten; der Bruchteil hält den Anteil eines Neueinsteigers bei jedem Stake gleich. Die beiden Felder können nicht zugleich gesetzt sein.

Zulassung per Abstimmung

Neu in fullnode v0.6.8 (audit v6, STAKE-2) und für Chain 20 aktiviert. Bei 26 Schlüsseln zu je 1,000 RAND entsprechen einem sperrenden Drittel des Sets etwa 13,000 RAND gekauften Stakes, und nichts wird geslasht. Nach dieser Regel entscheidet das Set, welche neuen Schlüssel sich überhaupt registrieren dürfen.

  • Die Abstimmung ist eine Action, AdmitValidator { candidate, signatures }, ohne Bundle und gebührenfrei. Jede Signatur ist die Dilithium2-Signatur eines Validators über blake3("rand-admit-validator-1" ‖ genesis hash ‖ candidate address), in aufsteigender Reihenfolge der Adressen der Voter. Die Voter müssen strikt mehr als zwei Drittel des Gewichts des Abstimmungs-Sets halten, die Arithmetik eines Quorum-Zertifikats: auf Chain 20 sind das 18 der 26 Validatoren. Eine Abstimmung ist an ein Genesis gebunden und braucht keine Nonce: Eine Zulassung wird durch die Registrierung verbraucht, die sie erlaubt.
  • Das Abstimmungs-Set ist die eigene Ableitung des Registers für die aktuelle Epoche. Ein eingereihter Bond ist darin kein Gewicht, und ein Unbond scheidet sofort daraus aus. Ein Kandidat kann sich nicht selbst hineinwählen.
  • Das Zulassungs-Set ist Konsenszustand, höchstens 256 Schlüssel, die auf ihre Registrierung warten (AdmissionSetFull darüber hinaus). rand_getAdmitted liefert es; rand_getLimits.admission_by_vote gibt an, ob eine Chain die Abstimmung verlangt.
# 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

Danach geht der Bond wie unten beschrieben weiter. rand_getGenesisHash liefert den Hash, den admit sign braucht.

Die vier Befehle

Welches Binary welche Action übernimmt, ergibt sich daraus, welcher Schlüssel sie signiert: Ein Bond verbraucht Notes, ist also ein Wallet-Befehl; ein Unbond und ein Withdraw werden vom Dilithium2-Schlüssel des Validators signiert, den nur der Node besitzt.

actioncommandrides onpays
registerrand-node registernichts – gibt einen Blob ausnichts
Bondrand bondein Bundle, dessen burn_r der Stake ist0.001 RAND aus den Notes der Wallet
Unbondrand-node unbondnichts (bundle: null)nichts
Withdrawrand-node withdrawnichts (bundle: null)0.001 RAND aus dem abgehobenen Betrag

Registrieren

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

Gibt die Adresse des Validators und eine hex-kodierte Registration aus: seinen öffentlichen Schlüssel, die Auszahlungsadresse und eine Signatur. Mit --v2, das Chain 20 verlangt, liegt die Signatur unter rand-register-2 über dem Genesis-Hash der Chain, der Chain-ID, der Adresse des Validators und der Auszahlung; ohne es entsteht die v1-Form, blake3("rand-register" ‖ bincode(chain_id, payout)), die Chain 20 ablehnt (auf einer binding_domain: 1-Chain wie Chain 20 ist die Registrierungsnachricht ohnehin die v2-Nachricht). Der RPC wird für die Chain-ID und, mit --v2, den Genesis-Hash abgefragt, sodass eine für eine Chain signierte Registrierung auf einer anderen abgelehnt wird. Die Auszahlungsadresse ist eine Wallet-Adresse, kein Node-Schlüssel, und sie ist das eine Feld, das ein späteres Top-up nicht ändern kann. Geben Sie den Hex-Wert an den weiter, der den Stake hält.

Bond

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

Eine gewöhnliche abgeschirmte Transaktion: Die Wallet beweist das 4-in-4-out-Hidden-Asset-Bundle, das jede Transaktion seit Chain 14 trägt, wobei der eingesetzte Betrag in ihrem RAND-Burn burn_r steht, und das Ledger lässt einen Bond nur zu, wenn burn_r == amount gilt. Auf Chain 20 trägt das Bundle zwei Beweise, wie jedes Bundle seit Chain 17: den Tier-14-Bundle-Beweis und einen Tier-10-Autorisierungsbeweis über den Spend-Schlüssel. Beide werden standardmäßig lokal bewiesen, in etwa zwei Minuten auf einem Apple-Silicon-Laptop (Proving-Speicher). Mit der Wallet-Option --prover wird der Bundle-Beweis stattdessen von einem gekoppelten Prover erstellt, und der Autorisierungsbeweis bleibt auf dem Rechner der Wallet. --registration ist genau dann erforderlich, wenn der Validator noch nicht im Register steht, und wird abgelehnt, wenn er darin steht; die Wallet fragt zuerst das Register ab. Auf Chain 20 wird ein registrierender Bond mit NotAdmitted abgelehnt, sofern das Set den Schlüssel nicht zuvor per Abstimmung aufgenommen hat (Aufnahme per Abstimmung); ein Top-up benötigt keine Abstimmung. Eine Registrierung bondet mindestens 1.000 RAND; ein Top-up kann jeder Betrag über null sein. Die Chain erfährt, dass der Einsatz dieses Validators um diesen Betrag gewachsen ist; welche Notes bezahlt haben und wem sie gehörten, bleibt verborgen wie bei einer Überweisung – jeder kann auf einen Validator einsetzen, ohne etwas anderes als den Betrag preiszugeben.

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

Die Epoche, die die Wallet ausgibt, ist die nächste Grenze. Sie berücksichtigt die Aktivierungsverzögerung nicht: Auf Chain 20 hat ein in Epoche 68 festgeschriebener Bond frühestens ab Epoche 71 Gewicht, später, wenn das Eintrittsbudget, ein Viertel des aktiven Gewichts pro Grenze, ausgeschöpft ist.

Unbond

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

Verschiebt amount von stake nach pending mit release_epoch = epoch + 2 und erhöht die Nonce. Kostenlos, ohne Bundle, signiert über blake3("rand-unbond" ‖ bincode(chain_id, validator, amount, nonce)) auf den Chains 14 bis 19 und über blake3("rand-unbond-2" ‖ bincode(genesis_hash, chain_id, validator, amount, nonce)) auf Chain 20 (Genesis binding_domain: 1); sie wird innerhalb der Zeit eines Blocks festgeschrieben. rand-node v0.6.8 wählt die Form für die Chain, mit der er spricht. Ein Unbond von mehr als dem Einsatz oder von null wird abgelehnt; mehrere Unbonds in einer Epoche werden zu einer pending-Zeile zusammengeführt.

Withdraw

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

Zahlt freigegebenen Wert – rewards plus die pending-Zeilen, deren release_epoch erreicht ist, die ältesten zuerst – als Deposit-Note an die payout-Adresse des Registers aus. Sie wird vom Validator signiert (auf Chain 20 unter rand-withdraw-2, wie beim Unbond zuerst über den Genesis-Hash), kommt ohne Bundle aus, ist aber nicht kostenlos: Die Basisgebühr von 0,001 RAND geht aus dem Betrag an den Proposer des Blocks, der sie anwendet. Eine Auszahlung von 1.000 RAND erzeugt daher eine Note im Wert von 999,999, und ein Betrag, der die Basisgebühr nicht deckt, wird abgelehnt. Die Chain berechnet die Note selbst:

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

time ist die Head-Höhe, als der Befehl lief (ausgegeben und durch die Signatur gebunden) und an jeder Stelle innerhalb des Bundle-Fensters zulässig (1.024 Blöcke auf Chain 20, deren Genesis proof_window_blocks setzt; 256 auf früheren Chains); r wird veröffentlicht, damit die Chain nie eine Commitment aus der Leitung übernimmt. Anschließend findet die Payout-Wallet die Note durch Scannen, ohne dass ihr etwas mitgeteilt wird:

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

Beitreten und Austreten

# 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

Prüfen Sie die Quorum-Arithmetik, bevor Sie einen Unbond ausführen: Ein Set aus n Validatoren benötigt mehr als 2/3 seines Einsatzes online. Auf Chain 20 liegen alle 26 Validatoren genau bei MIN_STAKE, sodass jeder Unbond diesen Node aus dem Set der nächsten Epoche fallen lässt und 18 der 26 online bleiben müssen. Ein Validator, der online ist, aber nicht abstimmt, zählt ebenfalls gegen das Quorum: Seit Chain 14 hält ein Validator seine Stimme für einen Block zurück, der mehr als 15 s seiner eigenen Uhr voraus ist, sodass ein Validator mit nachgehender Uhr ohne Fehlermeldung aufhört abzustimmen. Betreiben Sie NTP auf ihm (Betrieb eines Validators).

Genesis befüllt das Register direkt – ein Eintrag pro --validator <key or hex pubkey>,<stake>,<payout> – und lehnt einen Einsatz unterhalb des Minimums ab. Das Genesis von Chain 20 befüllt 26 Zeilen mit je 1.000 RAND, dieselben Schlüssel wie bei Chain 19.

Seit v0.5.11 enthält der Build zudem Genesis-Vesting: ein öffentliches Register zeitgesperrter Zuteilungen, aus dem ein unwiderruflicher Eintrag gesperrte RAND als Einsatz eines Validators bonden (rand-node vesting bond) und wieder zurücknehmen kann (rand-node vesting unbond). Dafür ist ein vesting-Abschnitt im Genesis erforderlich. Keine Chain hat einen, Chain 20 eingeschlossen, und ohne ihn werden diese Aktionen bei der Zulassung abgelehnt. (v0.6.8 erlaubt es außerdem, einen widerrufbaren Vesting-Eintrag nur durch eine Schwellenzahl von Unterzeichnern an eine feste Treasury zu widerrufen, wiederum nur unter einem vesting-Abschnitt.)

Was öffentlich ist

öffentlichverborgen
das Registerjede Zeile: Adresse, Einsatz, Unbonding-Warteschlange, nicht ausgezahlte Rewards, Payout-Adresse, Noncenichts
Bondder Validator, der Betrag, ob eine Registrierung beigefügt war, das burn_r des Bundleswelche Notes bezahlt haben, wem sie gehören, das Wechselgeld
AdmitValidator (Chain 20)der Kandidat und jeder Wähler mit seiner Signatursonst existiert nichts
Unbondder Validator, der Betrag, die Noncesonst existiert nichts
Withdrawder Validator, der Betrag, die Nonce, time und r der Notewer die Note öffnen kann und jede spätere Ausgabe von ihr

Rewards sind die Gebühren der Blöcke, die ein Validator vorgeschlagen hat. Es gibt keine Block-Rewards und keine Inflation, und auf Chain 20 gibt es kein Slashing und kein Jailing von Validatoren: Ein Validator, der sich fehlerhaft verhält, kostet seinen Einsatz nichts, und die Abhilfe liegt bei den Betreibern. Fullnode v0.6.8 enthält zwar eine Slashing-Regel für Leader-Equivocation (Audit v6, STAKE-1: zwei signierte Block-Header für eine View durch einen Schlüssel vernichten equivocation_bps des Einsatzes und sperren den Schlüssel per Jail), aber nur unter einem Genesis-Abschnitt staking.slashing, und Chain 20 hat keinen (rand_getLimits.slashing ist null); der Fullnode empfiehlt sie erst, wenn der Einsatz von mehr als einem Betreiber gehalten wird. (Der Block-Aggregationscode enthält außerdem eine SlashAggregator-Aktion für Aggregatoren, die zwei verschiedene Aggregate bei einer Nonce signieren; sie ist auf Chain 20 nicht aktiv, das keinen Aggregationsabschnitt hat.)

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

Beträge sind Dezimalzeichenfolgen von Einheiten. active gibt an, ob die Zeile zum Set gehört, das die aktuelle Epoche betreibt; jailed_until ist null, außer unter einem Slashing-Abschnitt; admitted listet Schlüssel auf, die aufgenommen wurden, aber noch nicht registriert sind; next_set ist eine Projektion, die jeder Bond und Unbond vor der Grenze verschiebt. Der GET /validators des Explorers fügt share_percent, blocks_proposed und die zuletzt vorgeschlagene Höhe hinzu.

Zuletzt geprüft gegen den fullnode-Commit c0878c3f (Chain 21) am 2026-10-12. Wo Code und diese Seite voneinander abweichen, hat der Code recht; sagen Sie uns Bescheid.