valset.gno
4.47 Kb · 129 lines
1package params
2
3import (
4 "chain"
5 "chain/runtime"
6 "errors"
7 "strconv"
8 "strings"
9
10 prms "sys/params"
11
12 "gno.land/p/sys/validators"
13)
14
15// Param keys read by gno.land/pkg/gnoland (EndBlocker).
16// Keep in sync with gno.land/pkg/gnoland/node_params.go.
17// nodeModulePrefix is declared in halt.gno (same package).
18const (
19 valsetSubmodule = "valset"
20
21 // dirty signals the chain that the proposed valset differs from the
22 // current applied one. Realm sets true; EndBlocker clears.
23 valsetDirtyKey = "dirty"
24
25 // One []string per slot; each entry has the form "<pubkey>:<power>"
26 // (bech32 pubkey + decimal power). Address is derived from pubkey
27 // on the chain side and not stored.
28 //
29 // proposed = v3's full target valset
30 // current = chain-managed: the set that becomes ACTIVE AT H+2
31 // once the most recent EndBlock's updates apply.
32 // NOT necessarily the set actively signing the current
33 // block — see ABCI H+2 sequencing.
34 valsetProposedKey = "proposed"
35 valsetCurrentKey = "current"
36
37 // Only this realm may write valset:proposed and valset:dirty.
38 // valset:current is chain-managed (see ctx-sentinel in node_params.go).
39 valsetAuthorizedRealm = "gno.land/r/sys/validators/v3"
40)
41
42// SetValsetProposal publishes the realm's desired valset. Each entry is
43// "<bech32-pubkey>:<decimal-power>"; power=0 removes the validator.
44// The chain reads this on the next EndBlocker, diffs it against
45// valset:current, and propagates the changes to consensus.
46func SetValsetProposal(cur realm, entries []string) {
47 assertValsetCaller()
48 prms.SetSysParamStrings(nodeModulePrefix, valsetSubmodule, valsetProposedKey, entries)
49 prms.SetSysParamBool(nodeModulePrefix, valsetSubmodule, valsetDirtyKey, true)
50}
51
52// GetValsetEntries returns the chain's authoritative committed
53// validator set (the contents of valset:current). This is the
54// V_{H+2} view — the set that will be active at H+2 once the most
55// recent EndBlock's updates apply, NOT the set signing the current
56// block. Callers that want "what v3 reports as the current
57// validator set" — including the in-flight proposed set during
58// the dirty window — should call GetValsetEffective instead.
59func GetValsetEntries() []validators.Validator {
60 return parseValsetSlot(valsetCurrentKey)
61}
62
63// ValsetDirty reports whether valset:proposed is awaiting EndBlocker.
64// Realm callers MUST treat this as transient: the dirty flag is set
65// by SetValsetProposal and cleared by the chain's EndBlocker (every
66// block where dirty=true on entry exits with dirty=false).
67func ValsetDirty() bool {
68 d, _ := prms.GetSysParamBool(nodeModulePrefix, valsetSubmodule, valsetDirtyKey)
69 return d
70}
71
72// GetValsetEffective returns the set that WILL be active at H+2:
73// valset:proposed if dirty, else valset:current. Used by v3 so that
74// (a) reads after a same-block proposal callback see that proposal's
75// effects, and (b) sequential same-block proposals accumulate
76// correctly on top of each other.
77//
78// Misuse warning: this exists for r/sys/validators/v3's internal
79// reads. Other realms making "is X a validator" decisions should
80// call v3.IsValidator, not this directly, so future changes to v3's
81// read semantics propagate uniformly.
82func GetValsetEffective() []validators.Validator {
83 key := valsetCurrentKey
84 if ValsetDirty() {
85 key = valsetProposedKey
86 }
87 return parseValsetSlot(key)
88}
89
90func parseValsetSlot(key string) []validators.Validator {
91 raw, _ := prms.GetSysParamStrings(nodeModulePrefix, valsetSubmodule, key)
92 out := make([]validators.Validator, 0, len(raw))
93 for _, e := range raw {
94 v, err := parseEntry(e)
95 if err != nil {
96 panic("valset:" + key + " corrupted: " + err.Error())
97 }
98 out = append(out, v)
99 }
100 return out
101}
102
103// parseEntry splits "<bech32-pubkey>:<decimal-power>" and derives the
104// validator address via the chain.PubKeyAddress native helper.
105func parseEntry(entry string) (validators.Validator, error) {
106 pkStr, pStr, ok := strings.Cut(entry, ":")
107 if !ok {
108 return validators.Validator{}, errors.New("missing ':' separator in " + entry)
109 }
110 addr, err := chain.PubKeyAddress(pkStr)
111 if err != nil {
112 return validators.Validator{}, err
113 }
114 power, err := strconv.ParseUint(pStr, 10, 64)
115 if err != nil {
116 return validators.Validator{}, err
117 }
118 return validators.Validator{
119 Address: addr,
120 PubKey: pkStr,
121 VotingPower: power,
122 }, nil
123}
124
125func assertValsetCaller() {
126 if runtime.PreviousRealm().PkgPath() != valsetAuthorizedRealm {
127 panic("unauthorized: only " + valsetAuthorizedRealm + " may write valset params")
128 }
129}