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

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

render.gno

13.96 Kb · 373 lines
  1package hlmailbox
  2
  3import (
  4	"strconv"
  5	"strings"
  6
  7	"gno.land/p/nt/avl/v0"
  8	"gno.land/p/g1zdaz6sypk5tdsjc2fzsqrrn4ttn8qzmnl4waqp/decisions/v0"
  9	hl "gno.land/p/g1zdaz6sypk5tdsjc2fzsqrrn4ttn8qzmnl4waqp/hyperlane/v0"
 10)
 11
 12const (
 13	pageMax      = 20 // messages a "msgs/" page holds
 14	verifiersMax = 20 // registered verifier paths (and announced successors) the page and "json" name, and a "verifiers/" ("successors/") page holds
 15)
 16
 17// Render:
 18//
 19//	""                  the mailbox's page: the launch values live or waiting, verifiers, signers, seats, flags
 20//	"decisions"         the decisions page: the last 100 pending and executed per kind
 21//	"json"              domain, hook, count, root, live, signers, verifiers (the first 20 registered paths and the
 22//	                    count), seats, flags, routes (each with its predecessor and successor, and the height and
 23//	                    time of the kind 6 that enrolled it on Solana: enrolled, enrolledAt), successors (the
 24//	                    first 20 announced paths, the count, and successorRoutes: the route each announced for),
 25//	                    for the agents
 26//	"decisions.json"    every decision, as a JSON array
 27//	"msg/<nonce>"       one dispatched message: {"nonce","id","root","height","message"}; "null" if none
 28//	"msgs/<from>"       up to 20 messages from nonce <from> on, as a JSON array
 29//	"verifiers/<from>"  up to 20 registered verifier paths from offset <from> on, in path order, as a JSON array
 30//	"successors/<from>" up to 20 announced successor paths from offset <from> on, in path order, as a JSON array
 31//	"delivered/<id>"    the block height a message id (hex) was delivered at here, 0 if never
 32func Render(path string) string {
 33	switch {
 34	case path == "":
 35		return page()
 36	case path == "decisions":
 37		return "# Decisions\n\n" + ledger.Page()
 38	case path == "json":
 39		return summaryJSON()
 40	case path == "decisions.json":
 41		return ledger.JSON()
 42	case strings.HasPrefix(path, "msg/"):
 43		n, ok := parseNonce(path[len("msg/"):])
 44		if !ok {
 45			return "null"
 46		}
 47		return msgJSON(n)
 48	case strings.HasPrefix(path, "msgs/"):
 49		from, ok := parseNonce(path[len("msgs/"):])
 50		if !ok {
 51			return "[]"
 52		}
 53		var b strings.Builder
 54		b.WriteString("[")
 55		for i := uint32(0); i < pageMax && uint64(from)+uint64(i) < uint64(tree.Count); i++ {
 56			if i > 0 {
 57				b.WriteString(",")
 58			}
 59			b.WriteString(msgJSON(from + i))
 60		}
 61		b.WriteString("]")
 62		return b.String()
 63	case strings.HasPrefix(path, "verifiers/"):
 64		from, ok := parseNonce(path[len("verifiers/"):])
 65		if !ok {
 66			return "[]"
 67		}
 68		return pathsJSON(&verifiers, int(from))
 69	case strings.HasPrefix(path, "successors/"):
 70		from, ok := parseNonce(path[len("successors/"):])
 71		if !ok {
 72			return "[]"
 73		}
 74		return pathsJSON(&successors, int(from))
 75	case strings.HasPrefix(path, "delivered/"):
 76		return strconv.FormatInt(Delivered(path[len("delivered/"):]), 10)
 77	}
 78	return "unknown path; see the mailbox's page"
 79}
 80
 81func parseNonce(s string) (uint32, bool) {
 82	n, err := strconv.ParseUint(s, 10, 32)
 83	return uint32(n), err == nil
 84}
 85
 86func q(s string) string { return strconv.Quote(s) }
 87
 88func msgJSON(n uint32) string {
 89	v := sent.Get(nonceKey(n))
 90	if v == nil {
 91		return "null"
 92	}
 93	s := v.(*sentMsg)
 94	id := hl.ID([]byte(s.raw))
 95	return "{\"nonce\":" + u32(n) + ",\"id\":" + q(hx(id[:])) + ",\"root\":" + q(hx(s.root[:])) +
 96		",\"height\":" + strconv.FormatInt(s.height, 10) + ",\"message\":" + q(hx([]byte(s.raw))) + "}"
 97}
 98
 99func keysJSON(ks []Key) string {
100	parts := make([]string, len(ks))
101	for i, k := range ks {
102		parts[i] = q(hx(k[:]))
103	}
104	return "[" + strings.Join(parts, ",") + "]"
105}
106
107// pathsJSON is up to verifiersMax keys of a tree of pkgpaths (the registered verifiers, the announced successors)
108// from offset from on, in path order: the listing is bounded whatever the count, since nothing is ever removed.
109func pathsJSON(t *avl.Tree, from int) string {
110	var b strings.Builder
111	b.WriteString("[")
112	first := true
113	t.IterateByOffset(from, verifiersMax, func(k string, _ any) bool {
114		if !first {
115			b.WriteString(",")
116		}
117		first = false
118		b.WriteString(q(k))
119		return false
120	})
121	b.WriteString("]")
122	return b.String()
123}
124
125// pathsLine is a tree of pkgpaths on the page: the first verifiersMax in path order and, past them, where the rest
126// are (page is the Render path that pages them); "none" for an empty tree.
127func pathsLine(t *avl.Tree, page string) string {
128	n := t.Size()
129	if n == 0 {
130		return "none"
131	}
132	var b strings.Builder
133	i := 0
134	t.IterateByOffset(0, verifiersMax, func(k string, _ any) bool {
135		if i > 0 {
136			b.WriteString(", ")
137		}
138		i++
139		b.WriteString("`" + k + "`")
140		return false
141	})
142	if n > verifiersMax {
143		m := strconv.Itoa(verifiersMax)
144		b.WriteString(", … (the first " + m + "; the rest at [" + page + "/" + m + "](" + pkgPathLink() + ":" + page + "/" + m + "))")
145	}
146	return b.String()
147}
148
149func summaryJSON() string {
150	var b strings.Builder
151	b.WriteString("{\"domain\":" + u32(localDomain) + ",\"hook\":" + q(Hook()) + ",\"address\":" + q(string(Address())) +
152		",\"count\":" + u32(tree.Count) + ",\"root\":" + q(Root()) + ",\"delivered\":" + strconv.Itoa(done.Size()) +
153		",\"live\":" + strconv.FormatBool(live) + ",\"launchedAt\":" + strconv.FormatInt(launchedAt, 10) +
154		"," + decisions.GovernorJSON(launchGovernor, yielded) +
155		",\"launchOrigin\":" + u32(launchOrigin) + ",\"clocks\":" + decisions.ClocksJSON() + ",\"decisions\":" + strconv.FormatInt(ledger.Last(), 10) +
156		",\"signers\":{\"keys\":" + keysJSON(signerKeys) + ",\"threshold\":" + strconv.Itoa(signerRule.Threshold) +
157		",\"groupA\":" + keysJSON(signerRule.GroupA) + ",\"groupB\":" + keysJSON(signerRule.GroupB) + "}" +
158		",\"verifiers\":{")
159	first := true
160	active.Iterate("", "", func(k string, v any) bool {
161		origin, _ := strconv.ParseUint(k, 10, 32)
162		if !first {
163			b.WriteString(",")
164		}
165		first = false
166		b.WriteString(q(strconv.FormatUint(origin, 10)) + ":{\"pkgpath\":" + q(v.(string)) + ",\"registered\":" + strconv.FormatBool(verifiers.Has(v.(string))) + "}")
167		return false
168	})
169	b.WriteString("},\"registered\":" + pathsJSON(&verifiers, 0) + ",\"registeredCount\":" + strconv.Itoa(verifiers.Size()) + ",\"seats\":[")
170	for i := 1; i <= seatCount; i++ {
171		if i > 1 {
172			b.WriteString(",")
173		}
174		b.WriteString(q(string(seats[i])))
175	}
176	b.WriteString("],\"routes\":{")
177	first = true
178	routes.Iterate("", "", func(k string, v any) bool {
179		r := v.(*routeRec)
180		if !first {
181			b.WriteString(",")
182		}
183		first = false
184		b.WriteString(q(k) + ":{\"epoch\":" + strconv.FormatInt(r.epoch, 10) + ",\"epochAt\":" + strconv.FormatInt(r.epochAt, 10) +
185			",\"sends\":" + strconv.FormatBool(Paused(k, FnSends)) + ",\"credits\":" + strconv.FormatBool(Paused(k, FnCredits)) +
186			",\"payouts\":" + strconv.FormatBool(Paused(k, FnPayouts)) + ",\"flags\":[")
187		for i, f := range Flags(k) {
188			if i > 0 {
189				b.WriteString(",")
190			}
191			b.WriteString(q(f))
192		}
193		b.WriteString("],\"predecessor\":" + q(r.predecessor) + ",\"successor\":" + q(r.successor) +
194			",\"enrolled\":" + strconv.FormatInt(r.enrolled, 10) + ",\"enrolledAt\":" + strconv.FormatInt(r.enrolledAt, 10) +
195			",\"pulled\":" + strconv.FormatBool(r.pulled) + ",\"dropped\":" + quotedJSON(r.dropped) + "}")
196		return false
197	})
198	b.WriteString("},\"droppedRoutes\":" + droppedRoutesJSON() + ",\"successors\":" + pathsJSON(&successors, 0) + ",\"successorCount\":" + strconv.Itoa(successors.Size()) + ",\"successorRoutes\":" + successorRoutesJSON() + "}")
199	return b.String()
200}
201
202// quotedJSON is a list of strings as a JSON array.
203func quotedJSON(ss []string) string {
204	parts := make([]string, len(ss))
205	for i, s := range ss {
206		parts[i] = q(s)
207	}
208	return "[" + strings.Join(parts, ",") + "]"
209}
210
211// droppedRoutesJSON is every dropped successor (R35) with the predecessor whose re-point dropped it, as an object
212// of pkgpath → pkgpath: the tombstones, bounded by the migrations a bridge sees in its life.
213func droppedRoutesJSON() string {
214	var b strings.Builder
215	b.WriteString("{")
216	first := true
217	droppedRoutes.Iterate("", "", func(k string, v any) bool {
218		if !first {
219			b.WriteString(",")
220		}
221		first = false
222		b.WriteString(q(k) + ":" + q(v.(string)))
223		return false
224	})
225	b.WriteString("}")
226	return b.String()
227}
228
229// droppedLine is the dropped successors on the page, each with the predecessor that re-pointed away from it; ""
230// when none.
231func droppedLine() string {
232	if droppedRoutes.Size() == 0 {
233		return ""
234	}
235	var parts []string
236	droppedRoutes.Iterate("", "", func(k string, v any) bool {
237		parts = append(parts, "`"+k+"` (by `"+v.(string)+"`)")
238		return false
239	})
240	return "- dropped (routes no more, re-pointed away): " + strings.Join(parts, ", ") + "\n"
241}
242
243// successorRoutesJSON is the first verifiersMax announced successors with the route each announced for (AQ5), as
244// an object of pkgpath → "GNOT" or "BUBBLE".
245func successorRoutesJSON() string {
246	var b strings.Builder
247	b.WriteString("{")
248	first := true
249	successors.IterateByOffset(0, verifiersMax, func(k string, v any) bool {
250		if !first {
251			b.WriteString(",")
252		}
253		first = false
254		b.WriteString(q(k) + ":" + q(v.(string)))
255		return false
256	})
257	b.WriteString("}")
258	return b.String()
259}
260
261// successorsLine is the announced successors on the page, each with the route it announced for (AQ5): the first
262// verifiersMax in path order and, past them, where the rest are; "none" for an empty tree.
263func successorsLine() string {
264	n := successors.Size()
265	if n == 0 {
266		return "none"
267	}
268	var b strings.Builder
269	i := 0
270	successors.IterateByOffset(0, verifiersMax, func(k string, v any) bool {
271		if i > 0 {
272			b.WriteString(", ")
273		}
274		i++
275		b.WriteString("`" + k + "` (" + v.(string) + ")")
276		return false
277	})
278	if n > verifiersMax {
279		m := strconv.Itoa(verifiersMax)
280		b.WriteString(", … (the first " + m + "; the rest at [successors/" + m + "](" + pkgPathLink() + ":successors/" + m + "))")
281	}
282	return b.String()
283}
284
285func page() string {
286	var b strings.Builder
287	b.WriteString("# The bridge's mailbox\n\n")
288	b.WriteString("gno.land's end of the GNOT and BUBBLE bridge to Solana: messages out to Solana (the Dispatch event the light client proves), and in from it under the active verifier. Every setting here moves only by a decision made with this realm's builders — the governor's own until it yields, a GovDAO proposal after; the guardians and watch keys can only pause.\n\n")
289	if live {
290		b.WriteString("- **live** since " + strconv.FormatInt(launchedAt, 10) + " (the Launch decision executed)\n")
291	} else {
292		b.WriteString("- **not launched**: the values below are the compiled launch values, waiting for the Launch decision; the routes refuse sends and credits. A passed Launch executes within 14 days of its proposal, like every decision; one nobody executed by then lapses and is proposed again\n")
293	}
294	b.WriteString("- " + decisions.GovernorLine(launchGovernor, yielded) + "\n")
295	b.WriteString("- a proposal made with a builder (of this realm, a route, r/hlfees or r/hlgov) is executed with r/gov/dao's `ExecuteOrRejectProposal`: the one error a closure returns — the 14-day deadline — then denies it for good; under `ExecuteProposal` that error reverts the transaction instead and the proposal stands, every retry failing the same way. Anything else a closure refuses is a panic, which leaves the proposal for a retry under either call\n")
296	b.WriteString("- domain: " + u32(localDomain) + "; Solana: " + u32(launchOrigin) + "\n")
297	b.WriteString("- " + decisions.Clocks() + "\n")
298	b.WriteString("- merkle tree hook: `" + Hook() + "`\n")
299	b.WriteString("- messages out: " + u32(tree.Count) + ", root `" + Root() + "`; in: " + strconv.Itoa(done.Size()) + "\n")
300	b.WriteString("- decisions: " + strconv.FormatInt(ledger.Last(), 10) + " ([page](" + pkgPathLink() + ":decisions))\n")
301	b.WriteString("\n## Verifiers\n\n")
302	active.Iterate("", "", func(k string, v any) bool {
303		origin, _ := strconv.ParseUint(k, 10, 32)
304		note := ""
305		if !verifiers.Has(v.(string)) {
306			note = " (**not registered**: nothing from this origin is taken until it is)"
307		}
308		b.WriteString("- from " + strconv.FormatUint(origin, 10) + ": `" + v.(string) + "`" + note + "\n")
309		return false
310	})
311	b.WriteString("- registered (" + strconv.Itoa(verifiers.Size()) + "): " + pathsLine(&verifiers, "verifiers"))
312	b.WriteString("\n\n## Signers\n\n- rule: " + signerRule.String() + "\n")
313	for i, k := range signerKeys {
314		b.WriteString("- " + strconv.Itoa(i) + ": `" + hx(k[:]) + "`\n")
315	}
316	b.WriteString("\n## Seats\n\n")
317	for i := 1; i <= seatCount; i++ {
318		kind := "guardian"
319		if !IsGuardianSeat(i) {
320			kind = "watch key"
321		}
322		a := string(seats[i])
323		if a == "" {
324			a = "(empty)"
325		}
326		b.WriteString("- seat " + strconv.Itoa(i) + " (" + kind + "): " + a + "\n")
327	}
328	b.WriteString("\n## Routes and flags\n\n")
329	routes.Iterate("", "", func(k string, v any) bool {
330		r := v.(*routeRec)
331		b.WriteString("- `" + k + "`: payouts epoch " + strconv.FormatInt(r.epoch, 10))
332		if r.predecessor != "" {
333			b.WriteString(", successor of `" + r.predecessor + "`")
334		}
335		if r.pulled {
336			b.WriteString(", page 0 pulled")
337		}
338		if r.successor != "" {
339			b.WriteString(", migrated to `" + r.successor + "`")
340		}
341		if len(r.dropped) > 0 {
342			b.WriteString(", dropped `" + strings.Join(r.dropped, "`, `") + "`")
343		}
344		if r.enrolled != 0 {
345			b.WriteString(", enrolled on Solana by kind 6 at height " + strconv.FormatInt(r.enrolled, 10) + " (" + strconv.FormatInt(r.enrolledAt, 10) + ")")
346		}
347		fs := Flags(k)
348		if len(fs) == 0 {
349			b.WriteString(", no flag\n")
350		} else {
351			b.WriteString("\n")
352			for _, f := range fs {
353				b.WriteString("  - **paused** " + f + "\n")
354			}
355		}
356		return false
357	})
358	b.WriteString(droppedLine())
359	b.WriteString("- successors announced (" + strconv.Itoa(successors.Size()) + "): " + successorsLine() + " (a route's migrate names only one of them, announced for its own route)\n")
360	if tree.Count > 0 {
361		b.WriteString("\n## Latest out\n\n")
362		sent.ReverseIterateByOffset(0, 10, func(k string, v any) bool {
363			s := v.(*sentMsg)
364			m, _ := hl.ParseMessage([]byte(s.raw))
365			id := hl.ID([]byte(s.raw))
366			b.WriteString("- #" + u32(m.Nonce) + " to " + u32(m.Destination) + " at block " + strconv.FormatInt(s.height, 10) + ": `" + hx(id[:]) + "`\n")
367			return false
368		})
369	}
370	return b.String()
371}
372
373func pkgPathLink() string { return strings.TrimPrefix(pkgPath, "gno.land") }