Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

introspect.gno

9.09 Kb · 210 lines
  1package governor
  2
  3// What a consumer can ask that the pages already say.
  4//
  5// Render publishes most of this — the slot count, a proposal's epoch, why one
  6// ended — and a caller that has to scrape a markdown page for a number the
  7// engine holds is an engine with an incomplete API. These are the reads that
  8// close that gap.
  9//
 10// None of them hands back a pointer. That is the rule the whole package is
 11// built to (gno-security-guide.md §3(B)): a /p/-declared type can be named by
 12// another /p/ package, so an exported *proposal or a live tree handle would let
 13// a stranger declare a mutator over it and have the storage-realm borrow run it
 14// under the CONSUMING realm's authority. Counts, copies and strings only.
 15
 16// The board's dimensions, which the front page prints and a caller may want to
 17// reason about before proposing.
 18const (
 19	// MaxLive is how many proposals may stand at once.
 20	MaxLive = maxLive
 21
 22	// MaxOpen is how many of those an ordinary kind may fill. The difference
 23	// is kept for the governor's own kinds, so a flood of one thing cannot
 24	// stop the holders changing the rules.
 25	MaxOpen = maxOpen
 26
 27	// GovLanes is that difference.
 28	GovLanes = govLanes
 29
 30	// The bounds on a proposal's three strings, which doc.gno publishes as a
 31	// table because they decide what a proposer can say before finding out by
 32	// transaction.
 33	MaxTitle    = maxTitle
 34	MaxPayload  = maxPayload
 35	MaxKindName = maxKindName
 36
 37	// MaxBatch is how many members one govern:batch may carry.
 38	MaxBatch = maxBatch
 39
 40	// Bps is the basis-point scale every bar here is expressed in.
 41	Bps = bps
 42
 43	// MaxReason is the longest failure message a kind can put on the record.
 44	// A kind returns the string and the governor keeps it for the life of the
 45	// realm, so it is clipped rather than trusted.
 46	MaxReason = maxReason
 47
 48	// Reserved is the prefix the governor's own kinds carry, which Offer
 49	// refuses so that nothing published can render as a built-in.
 50	Reserved = reserved
 51)
 52
 53// OpenSlots is how many proposals are holding a slot, decided-and-unrun
 54// included. The number the front page prints beside MaxLive.
 55func (g *Governor) OpenSlots() int { return g.openIdx.Size() }
 56
 57// Proposals is how many questions have ever been asked here.
 58func (g *Governor) Proposals() int { return g.proposals.Size() }
 59
 60// StoredState is the state that has been WRITTEN DOWN, which is not always the
 61// state a reader is shown.
 62//
 63// State computes the outcome from the rules and the clock, because a proposal
 64// whose deadline passed is decided whether or not anybody has poked it. Only a
 65// transaction records that, so between the deadline and the next Settle the two
 66// differ — and the difference is the whole reason reading is free: a read works
 67// out the answer and stores nothing.
 68//
 69// A caller wanting to know whether the slot has actually been given back, or
 70// whether there is anything left for Settle to do, wants this one.
 71func (g *Governor) StoredState(id int64) string { return stateName(g.mustProposal(id).state) }
 72
 73// Reason is why a proposal ended, or the empty string while it is still open.
 74// The page prints it; this is the same string.
 75func (g *Governor) Reason(id int64) string { return g.mustProposal(id).reason }
 76
 77// EpochOf is the sealed epoch a proposal's weight was read at. Fixed when the
 78// question was asked, and the reason buying in afterwards buys nothing.
 79func (g *Governor) EpochOf(id int64) uint32 { return g.mustProposal(id).epoch }
 80
 81// Snapshot is a proposal's engaged weight and the sealed epoch both it and the
 82// votes were read at — snapshotted at Propose and reachable no other way (EpochOf
 83// gives the epoch alone). Engaged is the turnout DENOMINATOR the pool is shown
 84// against; for a proposal opened with an absolute quorum floor the bar is that
 85// floor, not a fraction of engaged — read it with QuorumFloor. Scalars, so nothing
 86// is handed out to write through.
 87func (g *Governor) Snapshot(id int64) (engaged int64, epoch uint32) {
 88	p := g.mustProposal(id)
 89	return p.engaged, p.epoch
 90}
 91
 92// QuorumFloor is the absolute turnout a proposal needs when it was opened with
 93// ProposeWithQuorum, or 0 when the quorum is the rules' QuorumBps fraction of the
 94// engaged weight. A court renders a floored proposal's bar from this; the engine
 95// already uses it in the tally and on its own page. Snapshotted at Propose, so it
 96// is the bar the voters actually face.
 97func (g *Governor) QuorumFloor(id int64) int64 { return g.mustProposal(id).quorumFloor }
 98
 99// RollSize is how many addresses are on a proposal's voter roll.
100//
101// Zero can mean two things and HasRoll separates them: nobody has voted yet, or
102// the roll was reclaimed after the proposal finished.
103func (g *Governor) RollSize(id int64) int {
104	p := g.mustProposal(id)
105	if p.voted == nil {
106		return 0
107	}
108	// The sentinel Propose plants is not a voter. See rollSentinel.
109	n := p.voted.Size()
110	if p.voted.Has(rollSentinel) {
111		n--
112	}
113	return n
114}
115
116// HasRoll reports whether a proposal is still holding its voter roll, or has
117// given it back through ReleaseRoll.
118func (g *Governor) HasRoll(id int64) bool { return g.mustProposal(id).voted != nil }
119
120// KindStatus is whether a name has been offered here, and whether the holders
121// have adopted it. Both false for a name nobody has published.
122func (g *Governor) KindStatus(name string) (offered, live bool) {
123	e := g.entryOf(name)
124	if e == nil {
125		return false, false
126	}
127	return true, e.live
128}
129
130// KindRules is the terms a kind was adopted on. A copy: Rules is a plain struct
131// of numbers, so handing one back gives away nothing to write through.
132func (g *Governor) KindRules(name string) (Rules, bool) {
133	e := g.entryOf(name)
134	if e == nil {
135		return Rules{}, false
136	}
137	return e.rules, true
138}
139
140// Digest is the key a question occupies in the open index, derived from the
141// kind and payload that define it. Exported because it is a pure function of
142// two public strings, and because what it is FOR is worth knowing: a proposer
143// chooses their payload and so chooses their key, which is why the slot sweep
144// rotates rather than always starting at the low end.
145func (g *Governor) Digest(kind, payload string) string { return g.digest(kind, payload) }
146
147// Sweep reclaims the slots of proposals that have finished but were never
148// written down.
149//
150// Propose already calls it when the board is full, which is the path that
151// matters. This is the same thing on request, for a caller who would rather
152// free a slot than be refused one — permissionless, like Settle, and for the
153// same reason: it decides nothing, it only writes down what the rules already
154// decided.
155func (g *Governor) Sweep() { g.sweep() }
156
157// SetElectorate changes where voting weight comes from, keeping everything
158// else — the adopted kinds, every proposal, the slot cursor.
159//
160// The consuming realm's, like Adopt, and for a narrow purpose: a realm that
161// wraps its ledger to drop idle weight out of the quorum denominator swaps the
162// wrapper in here rather than rebuilding the governor and losing its registry.
163//
164// Only sensible before anything is open. A proposal snapshots the epoch it is
165// weighed at but reads the electorate live, so changing it under an open vote
166// moves the bar the voters were shown — which is the thing per-proposal rules
167// exist to prevent, arrived at from the other side.
168func (g *Governor) SetElectorate(e Electorate) {
169	if e == nil {
170		panic("governor: an electorate is required")
171	}
172	g.voters = e
173}
174
175// Adopt installs a kind as live without a vote.
176//
177// The consuming realm's, and nobody else's — reaching it needs the *Governor,
178// which a realm keeps unexported. That is the same authority the realm already
179// has by holding the engine at all, so this grants nothing new; what it does is
180// make the un-voted path explicit and greppable rather than something a realm
181// improvises by reaching into a tree.
182//
183// For a realm's OWN powers, and for arming a governor in a test. Anything a
184// stranger publishes goes through Offer and an adoption vote, which is the
185// distinction the whole design turns on: publishing code and letting that code
186// hold the governor's authority are different decisions taken by different
187// actors.
188//
189// A realm that exposes this through a crossing function has given governance
190// away. So would exposing Propose without its rules, or Execute without its
191// timelock; the engine cannot stop a consumer publishing the wrong thing, and
192// says so here because this is the one most obviously worth not publishing.
193func (g *Governor) Adopt(k Kind, r Rules) {
194	name := k.Name()
195	mustBeUsableName(name)
196	g.mustBeSaneRules(r)
197	g.kinds.Set(name, &entry{kind: k, rules: r, live: true})
198}
199
200// Render is the front page, or one proposal's page when path names an id.
201//
202// notes are extra lines the consuming realm puts under the token and above the
203// governance — who may mint, most of all, which is the realm's fact rather than
204// the engine's and cannot be inferred from silence.
205//
206// Passed in rather than stored. A stored note is a WRITE, and this is a read: a
207// realm calling SetNotes from its own Render turned reading a page into
208// something that costs storage, which is the defect check-storage exists to
209// catch and did.
210func (g *Governor) Render(path, notes string) string { return g.render(path, notes) }