Search Apps Documentation Source Content File Folder Download Copy Actions Download

valset.gno

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