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.

registry.gno

4.10 Kb · 132 lines
  1// Package registry is spike (a) of tasarim-v6 §5.5: can a realm store an
  2// interface value handed over by ANOTHER realm and call it later, and what
  3// can that foreign code do to the registry while it runs?
  4//
  5// It is deliberately tiny and is never part of the product. Two exports are
  6// intentionally unsafe probes and are marked as such: SharedBoard (returns a
  7// pointer into registry state) and UnsafeBump (a non-crossing exported
  8// mutator). Production core has neither; this spike measures why.
  9package registry
 10
 11import (
 12	"chain"
 13	"chain/runtime"
 14	"crypto/sha256"
 15	"encoding/hex"
 16	"strconv"
 17	"strings"
 18
 19	"gno.land/p/nt/avl/v0"
 20)
 21
 22// Engine is the contract a registered engine satisfies. Methods take and
 23// return plain values only, so the engine never receives a reference into
 24// registry state (tasarim-v6 §5.4 defence (a)).
 25type Engine interface {
 26	Name() string
 27	Render(seed string) string
 28}
 29
 30// SampleSeed is rendered once at registration; its hash is the fingerprint.
 31const SampleSeed = "spike-sample"
 32
 33type record struct {
 34	impl        Engine
 35	fingerprint string
 36	height      int64
 37}
 38
 39// Board is registry-owned state that a foreign engine can get a pointer to.
 40type Board struct {
 41	Count int
 42}
 43
 44var (
 45	engines avl.Tree // engine pkgpath -> *record
 46	board   = &Board{}
 47	bumps   int
 48)
 49
 50// Register stores the caller realm's engine. The id is the caller's pkgpath,
 51// so a realm can only ever register itself.
 52func Register(cur realm, e Engine) string {
 53	id := cur.Previous().PkgPath()
 54	if id == "" || strings.Contains(id, "#") {
 55		panic("only a code realm can register an engine")
 56	}
 57	if e == nil {
 58		panic("nil engine")
 59	}
 60	if engines.Has(id) {
 61		panic("engine already registered: " + id)
 62	}
 63	sample := e.Render(SampleSeed)
 64	engines.Set(id, &record{impl: e, fingerprint: sha256Hex(sample), height: runtime.ChainHeight()})
 65	chain.Emit("SpikeEngineRegistered", "id", id, "name", e.Name())
 66	return id
 67}
 68
 69// RenderWith runs a registered engine. Read-only: reached through qeval.
 70func RenderWith(id, seed string) string {
 71	return mustRecord(id).impl.Render(seed)
 72}
 73
 74// RenderInTx runs an engine inside a transaction. UNSAFE PROBE: a qeval
 75// discards writes, so this is how the spike observes on chain what engine
 76// code can do from a write path — the thing core never does (I-12).
 77func RenderInTx(cur realm, id, seed string) string {
 78	return mustRecord(id).impl.Render(seed)
 79}
 80
 81// VerifyFingerprint re-renders the sample and compares it with the hash
 82// stored at registration; false means the engine's output changed.
 83func VerifyFingerprint(id string) bool {
 84	r := mustRecord(id)
 85	return sha256Hex(r.impl.Render(SampleSeed)) == r.fingerprint
 86}
 87
 88func Fingerprint(id string) string { return mustRecord(id).fingerprint }
 89
 90func IsRegistered(id string) bool { return engines.Has(id) }
 91
 92// SharedBoard returns a pointer into registry state. UNSAFE PROBE: the spike
 93// checks that a foreign engine writing through it is refused by the VM
 94// (interrealm v2 §3.2 readonly check).
 95func SharedBoard() *Board { return board }
 96
 97// BoardCount reads the probe target.
 98func BoardCount() int { return board.Count }
 99
100// UnsafeBump is a non-crossing exported function that mutates registry
101// state. UNSAFE PROBE: interrealm v2 §4.1 says a non-crossing call into
102// /r/registry borrows /r/registry's storage, so a foreign engine calling it
103// SHOULD succeed. That is exactly why production core exports no
104// state-changing non-crossing function (invariant I-12).
105func UnsafeBump() { bumps++ }
106
107// Bumps reads the UnsafeBump counter.
108func Bumps() int { return bumps }
109
110func Render(path string) string {
111	out := "# spike/registry\n\n"
112	engines.Iterate("", "", func(key string, value any) bool {
113		r := value.(*record)
114		out += "- `" + key + "` fingerprint `" + r.fingerprint + "` (h=" + strconv.FormatInt(r.height, 10) + ")\n"
115		return false
116	})
117	out += "\nboard.Count=" + strconv.Itoa(board.Count) + " bumps=" + strconv.Itoa(bumps) + "\n"
118	return out
119}
120
121func mustRecord(id string) *record {
122	v := engines.Get(id)
123	if v == nil {
124		panic("unknown engine: " + id)
125	}
126	return v.(*record)
127}
128
129func sha256Hex(s string) string {
130	sum := sha256.Sum256([]byte(s))
131	return hex.EncodeToString(sum[:])
132}