Ta strona służy do dołączenia do zbioru walidatorów działającego łańcucha, do jego opuszczenia i do odczytania rejestru. Waga w konsensusie musi być jawna, więc rejestr walidatorów jest jedyną tabelą w tym łańcuchu, która przechowuje kwoty jawnie; stawka mimo to pochodzi z chronionej noty i do chronionej noty wraca.
Rejestr
Jeden wiersz na każdego walidatora, który kiedykolwiek złożył stawkę, z kluczem w postaci jego adresu 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
}
Każde pole jest jawne, a rejestr jest hashowany w korzeniu stanu każdego bloku. Wiersz nigdy nie jest usuwany: walidator, który wycofa całą stawkę, zachowuje wiersz z stake = 0.
| stała | wartość | znaczenie |
|---|---|---|
MIN_STAKE | 1,000 RAND | stawka, której wiersz potrzebuje, by w ogóle znaleźć się w zbiorze danej epoki |
MAX_VALIDATORS | 100 | największy zbiór, jaki może mieć epoka |
UNBONDING_EPOCHS | 2 | liczba epok, które wycofana kwota czeka, zanim będzie można ją podjąć |
epoch_blocks | 1,000 w chain 20 | liczba bloków w epoce; parametr genesis |
Epoki
epoch(h) = h / epoch_blocks. Zbiór dla epoki e ≥ 1 jest wyprowadzany z rejestru w stanie z ostatniego bloku epoki e − 1: każdy wiersz ze stake ≥ MIN_STAKE, czyli MAX_VALIDATORS pierwszych według (stake malejąco, address rosnąco), posortowanych według adresu. Lider widoku, głosujący nad blokiem i weryfikacja certyfikatu korzystają ze zbioru epoki, do której należy blok. Wynikają z tego dwie konsekwencje:
- Złożona stawka nie jest wagą aż do późniejszej granicy. Jest w rejestrze od razu, ale zbiór zmienia się tylko na granicy epoki: najbliższej w łańcuchu bez sekcji
staking, późniejszej w chain 20 (niżej). - Wycofanie stawki kosztuje wagę na najbliższej granicy, podczas gdy kwota czeka dwie epoki, aby można ją było podjąć. Walidator przestaje proponować bloki na długo przed tym, zanim może podjąć środki.
Jeśli wyprowadzenie daje zbiór pusty, przenoszony jest poprzedni zbiór. Węzeł uruchomiony z --validator, którego klucza nie ma w żadnym bieżącym zbiorze, tylko obserwuje, dopóki jakaś epoka go nie przyjmie, więc walidator, który złoży stawkę po genesis, nie potrzebuje restartu; rand_status zgłasza osobno is_validator (klucz jest tutaj) i active_validator (klucz jest w bieżącym zbiorze).
Sekcja staking
Genesis może zawierać sekcję staking (fullnode v0.5.4, audyt v4 STAKE-2). Bez niej powyższe reguły są całością zasad, jak w chain 14. Każdy łańcuch od chain 15 ją zawiera. W chain 20 włączone są:
"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…"]
}
| pole | chain 20 | reguła |
|---|---|---|
bond_activation_epochs | 2 | stawka złożona w epoce e trafia do kolejki na epokę e + 1 + 2; dotyczy to zarówno rejestracji, jak i zwiększenia stawki. Jej kwota jest w stake od razu, ale nie jest wagą do tego czasu, a stawki w kolejce nie można wycofać |
max_stake_entry_bps_per_epoch | 2,500 (25 %) | najwięcej stawki, łącznie z rejestracjami i zwiększeniami, jaka staje się wagą na jednej granicy, jako ułamek wagi już aktywnej w tym miejscu: ⌊active · bps / 10,000⌋. Wiersze, które przekraczają ten limit, czekają na następną granicę, w kolejności złożenia stawek. Przy 26,000 RAND aktywnej stawki w chain 20 daje to 6,500 RAND na granicę. Udział nowego uczestnika po k granicach wynosi co najwyżej 1 − 1.25^−k w dowolnej skali, po dwóch około jednej trzeciej |
admission_by_vote | true | stawka rejestrująca nowy klucz jest odrzucana (NotAdmitted), dopóki walidatorzy dysponujący więcej niż dwiema trzecimi wagi głosów nie zagłosują za przyjęciem klucza (niżej); zwiększenie stawki nie wymaga głosowania |
max_weight_bps | 3,333 | waga żadnego walidatora nie przekracza 33.33 % sumy wag jego zbioru; nadwyżka jest obcinana w zbiorze, a stake w rejestrze pozostaje bez zmian |
registration_v2 | true | rejestracja podpisuje rand-register-2 nad hashem genesis, identyfikatorem łańcucha, adresem walidatora i adresem wypłaty; rejestracja v1 jest odrzucana |
faucet_budget_per_epoch | 10,000 RAND | faucet emituje co najwyżej tyle na epokę (FaucetBudgetExhausted po przekroczeniu; licznik zaczyna od nowa w następnej epoce) |
faucet_recipients | 16 portfeli | Mint faucetu płaci wyłącznie tym portfelom (FaucetRecipientNotAllowed); ta lista pozwala faucetowi działać obok mostu |
faucet_minters | 18 walidatorów | Mint faucetu może być podpisany tylko tymi kluczami walidatorów (MinterNotAllowed), co sprawdza się przy przyjęciu i przy zastosowaniu bloku, niezależnie od tego, jak długo inny klucz ma złożoną stawkę. W chain 20 jest to 18 z 26 walidatorów genesis. Lista występuje w każdym genesis od chain 16 |
slashing | brak | brak sekcji slashing, więc żaden walidator nie jest karany ani zawieszany (Co jest jawne) |
Przy tej sekcji licznik faucetu i kolejka stawek są dołączane do korzenia stanu pod domeną rand-state-5, a liść walidatora to rand-validator-leaf-4. Chain 20 ma ponadto dynamiczne ceny gazu i stan programów RPL-2, dołączone po nich, co przenosi jego domenę na rand-state-8, a przyjęcie przez głosowanie opakowuje ten korzeń jeszcze raz jako H("rand-state-admitted-1", root ‖ admitted_root). Chain 15 nie miał listy faucet_minters: tam każdy węzeł umieszczał w puli i przekazywał Mint faucetu tylko wtedy, gdy podpisał go walidator genesis (MinterNotAllowed, jako polityka węzła), aby walidator, który złożył stawkę po genesis, nie mógł opróżnić budżetu przez pulę. Dzięki liście to samo odrzucenie jest regułą ważności.
Historia: łańcuchy od 15 do 19 nie miały admission_by_vote, więc zarejestrować mógł się każdy klucz z 1,000 RAND, a budżet wejścia ustalały jako stałe max_stake_entry_per_epoch w wysokości 10,000 RAND na granicę. Stała liczba jest dobrana do jednego genesis; ułamek utrzymuje udział nowego uczestnika na tym samym poziomie niezależnie od wielkości stawki. Tych dwóch pól nie można ustawić jednocześnie.
Przyjęcie przez głosowanie
Nowość w fullnode v0.6.8 (audyt v6, STAKE-2), włączona w chain 20. Przy 26 kluczach po 1,000 RAND blokująca jedna trzecia zbioru to około 13,000 RAND kupionej stawki, a nic nie jest karane slashingiem. Zgodnie z tą regułą to zbiór decyduje, które nowe klucze w ogóle mogą się zarejestrować.
- Głosowanie jest akcją
AdmitValidator { candidate, signatures }, bez pakietu i bez opłaty. Każdy podpis jest podpisem Dilithium2 walidatora nadblake3("rand-admit-validator-1" ‖ genesis hash ‖ candidate address), w rosnącej kolejności adresów głosujących. Głosujący muszą dysponować ściśle więcej niż dwiema trzecimi wagi zbioru głosującego, czyli arytmetyką certyfikatu kworum: w chain 20 to 18 z 26 walidatorów. Głos jest związany z jednym genesis i nie wymaga nonce: przyjęcie jest zużywane przez rejestrację, na którą pozwala. - Zbiór głosujący to własne wyprowadzenie rejestru dla bieżącej epoki. Stawka w kolejce nie jest w nim wagą, a wycofanie stawki usuwa z niego od razu. Kandydat nie może zagłosować sam na siebie.
- Zbiór przyjętych to stan konsensusu, co najwyżej 256 kluczy czekających na rejestrację (
AdmissionSetFullpo przekroczeniu). Udostępnia gorand_getAdmitted;rand_getLimits.admission_by_votemówi, czy łańcuch wymaga głosowania.
# 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
Następnie stawka jest składana jak niżej. rand_getGenesisHash zwraca hash, którego potrzebuje admit sign.
Cztery polecenia
To, który plik binarny obsługuje którą akcję, wynika z tego, który klucz ją podpisuje: stawka wydaje noty, więc jest poleceniem portfela; wycofanie stawki i podjęcie środków podpisuje klucz Dilithium2 walidatora, który ma tylko węzeł.
| akcja | polecenie | przenosi się w | płaci |
|---|---|---|---|
| rejestracja | rand-node register | w niczym — wypisuje blob | nic |
Bond | rand bond | pakiet, którego burn_r jest stawką | 0.001 RAND z not portfela |
Unbond | rand-node unbond | w niczym (bundle: null) | nic |
Withdraw | rand-node withdraw | w niczym (bundle: null) | 0.001 RAND z podejmowanej kwoty |
Rejestracja
rand-node register --key node.key.json --payout "$(rand --key payout.key.json address)" --v2 [--rpc http://127.0.0.1:8545]
Wypisuje adres walidatora i szesnastkowy Registration: jego klucz publiczny, adres wypłaty i podpis. Z --v2, którego wymaga chain 20, podpis jest składany pod rand-register-2 nad hashem genesis łańcucha, identyfikatorem łańcucha, adresem walidatora i adresem wypłaty; bez niego powstaje forma v1, blake3("rand-register" ‖ bincode(chain_id, payout)), którą chain 20 odrzuca (w łańcuchu z binding_domain: 1, takim jak chain 20, komunikatem rejestracji jest w każdym razie komunikat v2). Z RPC odczytywany jest identyfikator łańcucha, a z --v2 także hash genesis, więc rejestracja podpisana dla jednego łańcucha jest odrzucana w innym. Adres wypłaty jest adresem portfela, a nie kluczem węzła, i jest jedynym polem, którego późniejsze zwiększenie stawki nie może zmienić. Przekaż szesnastkowy ciąg temu, kto dysponuje stawką.
Zablokowanie stawki
rand bond <validator base58> <amount in RAND> [--registration <hex>] [--fee <RAND>] [--no-wait] [--cuda]
Zwykła transakcja osłonięta: portfel dowodzi pakiet 4-in-4-out z ukrytymi aktywami, który niesie każda transakcja od chain 14, a stawkowaną kwotę umieszcza w spaleniu RAND, burn_r; księga przyjmuje depozyt (bond) tylko wtedy, gdy burn_r == amount. Na chain 20 pakiet niesie dwa dowody, jak każdy pakiet od chain 17: dowód pakietu tier-14 oraz dowód autoryzacji tier-10 nad kluczem wydatkowania. Oba są domyślnie generowane lokalnie, w około dwie minuty na laptopie z Apple Silicon (Pamięć potrzebna do dowodzenia). Z opcją portfela --prover dowód pakietu wytwarza sparowany dowodzący, a dowód autoryzacji pozostaje na maszynie portfela. --registration jest wymagane dokładnie wtedy, gdy walidatora nie ma jeszcze w rejestrze, i odrzucane, gdy już w nim jest; portfel najpierw odpytuje rejestr. Na chain 20 depozyt rejestrujący zostaje odrzucony z błędem NotAdmitted, o ile zbiór nie zagłosował wcześniej za przyjęciem klucza (Przyjęcie w głosowaniu); dopłata nie wymaga głosowania. Rejestracja wymaga depozytu co najmniej 1,000 RAND; dopłata może wynosić dowolną kwotę większą od zera. Łańcuch dowiaduje się tylko, że stawka tego walidatora wzrosła o tę kwotę; które noty zapłaciły i do kogo należały, pozostaje ukryte, tak jak w transferze — każdy może stawkować na rzecz walidatora, ujawniając wyłącznie kwotę.
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
Epoka wypisana przez portfel to najbliższa granica. Nie uwzględnia ona opóźnienia aktywacji: na chain 20 depozyt złożony w epoce 68 ma wagę najwcześniej od epoki 71, a później, jeśli wyczerpany zostanie budżet wejść, czyli jedna czwarta aktywnej wagi na granicę.
Unbond
rand-node unbond <amount in RAND> --key node.key.json [--rpc …] [--no-wait]
Przenosi amount z stake do pending z release_epoch = epoch + 2 i zwiększa nonce. Jest darmowy, bez pakietu, podpisany nad blake3("rand-unbond" ‖ bincode(chain_id, validator, amount, nonce)) na chainach od 14 do 19 oraz nad blake3("rand-unbond-2" ‖ bincode(genesis_hash, chain_id, validator, amount, nonce)) na chain 20 (genesis binding_domain: 1); zostaje zatwierdzony w czasie jednego bloku. rand-node v0.6.8 wybiera formę odpowiednią dla łańcucha, z którym rozmawia. Wycofanie więcej niż wynosi stawka, albo zera, jest odrzucane; kilka unbondów w jednej epoce łączy się w jeden wiersz pending.
Withdraw
rand-node withdraw <amount in RAND> --key node.key.json [--rpc …] [--no-wait]
Wypłaca uwolnioną wartość — rewards oraz wiersze pending, których release_epoch nadeszła, od najstarszego — do noty depozytowej na adres payout z rejestru. Jest podpisany przez walidatora (na chain 20 pod rand-withdraw-2, z hashem genesis na pierwszym miejscu, jak przy unbondzie) i bez pakietu, ale nie jest darmowy: opłata bazowa 0.001 RAND trafia do proposera bloku, który go stosuje, i jest pobierana z kwoty, więc wypłata 1,000 RAND tworzy notę wartą 999.999, a kwota, która nie pokrywa opłaty bazowej, jest odrzucana. Notę oblicza sam łańcuch:
cm = H_CM(payout.pk, from = 0, amount − 0.001 RAND, asset 0, time, r)
time to wysokość czoła łańcucha w chwili uruchomienia polecenia (wypisywana i związana podpisem), przyjmowana w dowolnym miejscu okna pakietu (1,024 bloków na chain 20, którego genesis ustawia proof_window_blocks; 256 na wcześniejszych chainach); r jest publikowane, aby łańcuch nigdy nie przyjmował zobowiązania z sieci. Następnie portfel wypłaty odnajduje notę skanowaniem, bez żadnej informacji od kogokolwiek:
rand --key payout.key.json balance # balance: 999.999 RAND
Dołączanie i odchodzenie
# 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
Przed unbondem sprawdź arytmetykę kworum: zbiór liczący n walidatorów potrzebuje więcej niż 2/3 swojej stawki online. Na chain 20 wszystkich 26 walidatorów ma dokładnie MIN_STAKE, więc każdy unbond, nawet najmniejszy, usuwa ten węzeł ze zbioru następnej epoki, a z 26 musi działać 18. Walidator, który działa, lecz nie głosuje, również liczy się przeciwko kworum: od chain 14 walidator wstrzymuje głos na blok wyprzedzający jego własny zegar o ponad 15 s, więc walidator ze spóźnionym zegarem przestaje głosować bez żadnego błędu. Uruchom na nim NTP (Uruchamianie walidatora).
Genesis zasila rejestr bezpośrednio — jeden wpis na --validator <key or hex pubkey>,<stake>,<payout> — i odrzuca stawkę poniżej minimum. Genesis chain 20 zasila 26 wierszy po 1,000 RAND każdy, z tymi samymi kluczami co chain 19.
Od v0.5.11 build niesie także genesis vesting: jawny rejestr alokacji z blokadą czasową, z którego nieodwoływalny wpis może zdeponować zablokowane RAND jako stawkę walidatora (rand-node vesting bond) i odebrać je z powrotem (rand-node vesting unbond). Wymaga to sekcji vesting w genesis. Żaden chain jej nie ma, chain 20 również, a bez niej te akcje są odrzucane przy przyjęciu. (v0.6.8 pozwala też odwołać odwoływalny wpis vestingu wyłącznie progowi sygnatariuszy, na rzecz stałego skarbca, znów tylko przy sekcji vesting.)
Co jest jawne
| jawne | ukryte | |
|---|---|---|
| rejestr | każdy wiersz: adres, stawka, kolejka unbondingu, niewypłacone nagrody, adres wypłaty, nonce | nic |
Bond | walidator, kwota, czy dołączono rejestrację, burn_r pakietu | które noty zapłaciły, kto jest ich właścicielem, reszta |
AdmitValidator (chain 20) | kandydat i każdy głosujący wraz z podpisem | nic innego nie istnieje |
Unbond | walidator, kwota, nonce | nic innego nie istnieje |
Withdraw | walidator, kwota, nonce, time i r noty | kto może otworzyć notę i każde jej późniejsze wydanie |
Nagrodami są opłaty z bloków zaproponowanych przez walidatora. Nie ma nagród za bloki ani inflacji, a na chain 20 nie ma slashingu ani jailingu walidatorów: walidator, który się źle zachowuje, nie traci przez to stawki, a środki zaradcze leżą po stronie operatorów. Fullnode v0.6.8 zawiera regułę slashingu za equivocation lidera (audit v6, STAKE-1: dwa podpisane nagłówki bloku dla jednego widoku (view) przez jeden klucz niszczą equivocation_bps stawki i jailują klucz), ale tylko pod sekcją genesis staking.slashing, a chain 20 jej nie ma (rand_getLimits.slashing to null); fullnode zaleca ją dopiero wtedy, gdy stawkę posiada więcej niż jeden operator. (Kod agregacji bloków zawiera także akcję SlashAggregator dla agregatorów, którzy podpisują dwa różne agregaty przy jednym nonce; nie jest ona aktywna na chain 20, który nie ma sekcji agregacji.)
Odczyt
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…"] }
Kwoty to dziesiętne ciągi znaków w jednostkach. active mówi, czy wiersz należy do zbioru prowadzącego bieżącą epokę; jailed_until to null, chyba że istnieje sekcja slashingu; admitted wymienia klucze, za którymi zagłosowano, a które jeszcze się nie zarejestrowały; next_set to projekcja, którą przesuwa każdy bond i unbond przed granicą. GET /validators eksploratora dodaje share_percent, blocks_proposed i wysokość ostatniego zaproponowanego bloku.