PathrockNetwork Gno Explorer
HomeBlocksTransactionsTokensRealmsPackagesValidatorsAnalytics

PathrockNetwork Gno Explorer — an independent explorer for Gno.land Mainnet (gnoland-1), operated by PathrockNetwork. Not an official Gno.land service.

gnowebarchive RPC

gno.land/r/sys/validators/v0

Realm
Open in gnoweb ↗

Overview

Kind
Realm (renderable)
Name
v0
Namespace
sys / validators
Files
7 (gnomod.toml)
Exported functions
9
Module
gno.land/r/sys/validators/v0
gno
0.9

Files (7)

  • gnomod.tomltoml
  • cache.gnogno
  • proposal.gnogno
  • validators.gnogno
  • cache_test.gnogno
  • proposal_test.gnogno
  • validators_test.gnogno
proposal.gnogno
1package validators23import (4	"sort"5	"strconv"6	"strings"78

Functions

  • AssertGenesisValopersConsistent(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string})

  • GetValidator(addr string) struct{Address .uverse.address; PubKey string; VotingPower uint64}

  • GetValidators() []gno.land/p/sys/validators/v0.Validator

  • IsValidator(addr string) bool

  • NewValidatorProposalRequest(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, changes []gno.land/r/sys/validators/v0.ValoperChange, title string, description string) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}

  • NewValoperChange(operatorAddress string, power uint64) struct{OperatorAddress .uverse.address; Power uint64}

  • NotifyValoperChanged(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, op string, signingPubKey string, signingAddress string, keepRunning bool)

  • Render(string) string

  • RotateValoperSigningKey(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, op string, oldPubKey string, newPubKey string)

Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.

Rendered

RenderedRawgnoweb ↗

vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.

"chain"
9
10 "gno.land/p/nt/ufmt/v0"
11 "gno.land/p/sys/validators/v0"
12 "gno.land/r/gov/dao"
13 sysparams "gno.land/r/sys/params"
14)
15
16// ValoperChange is the operator-keyed input shape for the v0 valset
17// proposal builder. Power=0 removes; Power>0 adds (or upserts the
18// power on an op already in the active set — Tendermint's natural
19// ValidatorUpdate semantics).
20//
21// Each operator may appear AT MOST ONCE per proposal; duplicates are
22// rejected at create-time.
23type ValoperChange struct {
24 OperatorAddress address
25 Power uint64
26}
27
28func NewValoperChange(operatorAddress address, power uint64) ValoperChange {
29 return ValoperChange{
30 OperatorAddress: operatorAddress,
31 Power: power,
32 }
33}
34
35const errNoValoperChanges = "no valoper changes proposed"
36
37// NewValidatorProposalRequest builds a GovDAO proposal that, when
38// executed, applies the deltas to the chain's effective valset and
39// publishes the new full set via SetValsetProposal.
40//
41// NON-CROSSING (no `cur realm`). Direct MsgCall is unsupported;
42// proposers route through r/gnops/valopers/proposal's facade
43// (which IS crossing and accepts user txs).
44//
45// Validation at creation time:
46// - Each operator may appear AT MOST ONCE in changes; duplicates
47// panic. Power changes for an op already in the active set use
48// a single {op, newPower} entry (upsert), not the legacy
49// remove/re-add pair.
50// - Every ValoperChange's OperatorAddress must exist in
51// valoperCache. Unknown operators panic.
52// - Adds (Power > 0) require KeepRunning=true. An op that has
53// called UpdateKeepRunning(false) signals opt-out; no proposal
54// can keep them in the active set, period.
55//
56// Pubkey resolution at execution time: the executor callback
57// re-reads valoperCache for each entry to capture the CURRENT
58// signing pubkey/address — not the creation-time one. Defends
59// against a stale (now-retired) key publication if the operator
60// rotated while the proposal sat in GovDAO. Also re-checks
61// KeepRunning so an operator flipping to KeepRunning=false between
62// propose-create and propose-execute is honored. Removes are
63// unaffected (operator address is the lookup key, not signing
64// address).
65//
66// Emits ValidatorAdded / ValidatorRemoved events per entry on
67// successful execution. (Power-upsert on an existing op also emits
68// ValidatorAdded with the new power.)
69func NewValidatorProposalRequest(cur realm, changes []ValoperChange, title, description string) dao.ProposalRequest {
70 if len(changes) == 0 {
71 panic(errNoValoperChanges)
72 }
73 title = strings.TrimSpace(title)
74 if title == "" {
75 panic("proposal title is empty")
76 }
77 if len(changes) > 40 {
78 panic("max number of allowed validators per proposal is 40")
79 }
80
81 // Dedupe: each operator may appear at most once per proposal.
82 // Power changes are now expressed as a single {op, newPower}
83 // upsert entry, so the legacy [{op,0},{op,N}] pair is a duplicate
84 // and rejected.
85 seen := map[string]bool{}
86 for _, c := range changes {
87 key := c.OperatorAddress.String()
88 if seen[key] {
89 panic("duplicate operator in proposal: " + key)
90 }
91 seen[key] = true
92 }
93
94 // Creation-time validation: every operator must exist in cache,
95 // and adds require KeepRunning=true. KeepRunning=false is a
96 // binding opt-out; no proposal shape can override it.
97 for _, c := range changes {
98 rawCache := valoperCache.Get(c.OperatorAddress.String())
99 if rawCache == nil {
100 panic("unknown operator: " + c.OperatorAddress.String())
101 }
102 entry := rawCache.(cacheEntry)
103 if c.Power > 0 && !entry.KeepRunning {
104 panic("operator " + c.OperatorAddress.String() + " has KeepRunning=false; refusing to add (operator must call UpdateKeepRunning(true) first)")
105 }
106 }
107
108 // Render description against creation-time data. Voters see the
109 // operator addresses being proposed; signing addresses are an
110 // implementation detail resolved at exec.
111 var desc strings.Builder
112 desc.WriteString(description)
113 if len(description) > 0 {
114 desc.WriteString("\n\n")
115 }
116 desc.WriteString("## Validator Updates\n")
117 for _, c := range changes {
118 if c.Power == 0 {
119 desc.WriteString(ufmt.Sprintf("- %s: remove\n", c.OperatorAddress))
120 } else {
121 desc.WriteString(ufmt.Sprintf("- %s: add (power %d)\n", c.OperatorAddress, c.Power))
122 }
123 }
124
125 return dao.NewProposalRequest(title, desc.String(), newValoperChangeExecutor(cur, changes))
126}
127
128// newValoperChangeExecutor builds the GovDAO executor that, on
129// approval, applies the captured ValoperChange deltas. Resolves
130// operator → signing addr/pubkey via valoperCache at execution time
131// for adds (so a mid-flight rotation doesn't publish a stale key).
132// Removes resolve the operator's CURRENT signing address (also via
133// cache) — operator-keyed removes are immune to rotation churn.
134//
135// Power>0 is an upsert against the effective valset map (keyed on
136// signing address): if the op is already present under that signing
137// address, the entry's voting power is overwritten. Tendermint
138// natively handles ValidatorUpdates as upserts, so a single-entry
139// power change is the canonical form.
140func newValoperChangeExecutor(cur realm, changes []ValoperChange) dao.Executor {
141 callback := func(cur realm) error {
142 baseline := sysparams.GetValsetEffective()
143 set := make(map[address]validators.Validator, len(baseline))
144 for _, v := range baseline {
145 set[v.Address] = v
146 }
147
148 for _, c := range changes {
149 rawCache := valoperCache.Get(c.OperatorAddress.String())
150 if rawCache == nil {
151 panic("operator vanished from valoperCache between propose and execute: " + c.OperatorAddress.String())
152 }
153 entry := rawCache.(cacheEntry)
154
155 if c.Power == 0 {
156 if _, ok := set[entry.SigningAddress]; !ok {
157 panic("validator does not exist: " + entry.SigningAddress.String())
158 }
159 delete(set, entry.SigningAddress)
160 chain.Emit(
161 "ValidatorRemoved",
162 "op", c.OperatorAddress.String(),
163 "signingAddr", entry.SigningAddress.String(),
164 )
165 continue
166 }
167
168 // Race-safety: operator may have flipped KeepRunning=false
169 // between propose-create and propose-execute. Re-check.
170 // The opt-out is binding regardless of proposal shape.
171 if !entry.KeepRunning {
172 panic("operator " + c.OperatorAddress.String() + " has KeepRunning=false at execution; refusing to add")
173 }
174
175 // Upsert at the current signing address. If the entry was
176 // already present (single-entry power change on an active
177 // validator), this overwrites the prior power.
178 set[entry.SigningAddress] = validators.Validator{
179 Address: entry.SigningAddress,
180 PubKey: entry.SigningPubKey,
181 VotingPower: c.Power,
182 }
183 chain.Emit(
184 "ValidatorAdded",
185 "op", c.OperatorAddress.String(),
186 "signingAddr", entry.SigningAddress.String(),
187 "power", strconv.FormatUint(c.Power, 10),
188 )
189 }
190
191 // Liveness floor: refuse to publish an empty set.
192 if len(set) == 0 {
193 panic("valset proposal would empty the validator set; refused to keep consensus liveness")
194 }
195
196 entries := make([]string, 0, len(set))
197 for _, v := range set {
198 entries = append(entries, v.PubKey+":"+strconv.FormatUint(v.VotingPower, 10))
199 }
200 sort.Strings(entries)
201 sysparams.SetValsetProposal(cross(cur), entries)
202 return nil
203 }
204
205 return dao.NewSimpleExecutor(0, cur, callback, "")
206}
207