PathrockNetwork Gno Explorer
HomeBlocksTransactionsTokensRealmsPackagesValidatorsAnalytics

PathrockNetwork Gno Explorer — an independent explorer for Gno.land Mainnet (gnoland-1), operated by PathrockNetwork. Not an official Gno.land service.

gnowebarchive RPC

gno.land/p/g1lnkytfqcjwllws63gvf0mv9yt04aswy4y9amhm/app/v0

Package
Open in gnoweb ↗

Overview

Kind
Pure package
Name
v0
Namespace
g1lnkytfqcjwllws63gvf0mv9yt04aswy4y9amhm / app
Files
5 (gnomod.toml)
Exported functions
n/a — not supported for pure packages by the node (vm/qfuncs)
Module
gno.land/p/g1lnkytfqcjwllws63gvf0mv9yt04aswy4y9amhm/app/v0
gno
0.9

Files (5)

  • gnomod.tomltoml
  • app.gnogno
  • args.gnogno
  • schema.gnogno
  • store.gnogno
app.gnogno
1// Package app is an upgradeable realm in a handful of forwarding functions.2//3// It answers a narrower question than [upgradeable] does. That package gives4// you a proxy and leaves the shape of your realm to you: your interface, your5// state, your entry points, all of them permanent. This one makes a different6// trade — it fixes the shape once, generically, so that nothing about your7// application is permanent except the path.8//9//	package todo10//11//	import "gno.land/p/g1lnkytfqcjwllws63gvf0mv9yt04aswy4y9amhm/app/v0"12//13//	const Admin = address("g1…")14//15//	var a = app.New(app.Owner(Admin))16//17//	func init(cur realm)                              { a.Bind(0, cur) }18//	func Call(cur realm, verb, payload string) string { return a.Call(0, cur, verb, payload) }19//	func Register(cur realm, h app.Handler)           { a.Register(0, cur, h) }20//	func Manage(cur realm, verb, arg string) string   { return a.Admin(0, cur, verb, arg) }21//	func Store(_ int, rlm realm) *app.Store           { return a.Store(0, rlm) }22//	func Render(path string) string                   { return a.Render(path) }23//24// That realm never needs to change again. Its API is [Handler]'s verb set,25// which is data; its state is a [Store], whose keys are data. A later26// implementation adds verbs and keys freely, and callers reach them through27// the same Call.28//29// # Why the forwards cannot be avoided30//31// Those six lines are the irreducible minimum. A `p/` package cannot declare32// crossing functions, and `MsgCall` reaches a function by name at a realm33// path — so every entry point a user calls has to be declared in your realm.34// What a pure package can do is make each one a single forward, which is what35// the methods below are shaped for. Copy the block; only Admin changes.36//37// # What you give up38//39// Typed entry points. `Call(cur, "transfer", payload)` is one signature40// forever, so gnoweb shows one function, arguments are strings you encode41// yourself, and a misspelled verb is a runtime panic rather than a42// compile-time error. If your API is small and you can name it now, declare it43// with [upgradeable] and keep your types.44//45// The two compose: an app realm can carry hand-written typed entry points46// beside Call, forwarding to Proxy() for dispatch. Start generic, add typed47// entry points for the verbs that settle.48package app4950import (51	"errors"52	"strconv"53	"strings"5455	"gno.land/p/g1lnkytfqcjwllws63gvf0mv9yt04aswy4y9amhm/upgradeable/v0"56)5758var (59	// ErrNoHandler is raised when the app has no accepted implementation.60	ErrNoHandler = errors.New("app: no handler is live")6162	// ErrNotAHandler is raised when the live implementation does not satisfy63	// Handler. Register admits only a Handler, so this is unreachable in64	// practice and cheap to rule out.65	ErrNotAHandler = errors.New("app: live implementation does not satisfy Handler")6667	// ErrStaleRealm is raised when the threaded realm value is not the68	// caller's live cur.69	ErrStaleRealm = errors.New("app: realm value is not the caller's live cur")7071	// ErrNotBound is raised when Bind has not run, so the app does not know72	// its own path and cannot tell its callers apart.73	ErrNotBound = errors.New("app: Bind has not been called from init")7475	// ErrForbidden is raised when a realm that is neither the app nor a76	// registered extension asks for the store.77	ErrForbidden = errors.New("app: store is not reachable from this realm")7879	// ErrUnknownVerb is raised by Admin for a verb it does not implement.80	ErrUnknownVerb = errors.New("app: unknown admin verb")8182	// ErrNoSchema is raised when a handler declares no verbs. An app whose83	// API cannot be enumerated is the thing this package exists to avoid.84	ErrNoSchema = errors.New("app: a handler must declare at least one verb")85)8687// Handler is what an implementation realm implements.88//89// Two methods. [Handler.Schema] declares the API as data — a verb set rather90// than a method set, which is what lets it grow past the realm that serves it.91// [Handler.Handle] answers one call, having been handed a payload the app has92// already decoded and checked against that declaration.93//94// Panic to fail; panics cross the realm boundary and abort the transaction.95type Handler interface {96	// Schema declares the verbs this handler answers. It must not be nil or97	// empty, and it should be a package-level value rather than rebuilt per98	// call — the app reads it on every dispatch.99	Schema() *Schema100101	// Handle answers args.Verb(). Every declared parameter is present and102	// parses as its kind by the time this runs, so the accessors on Args do103	// not return errors.104	Handle(_ int, rlm realm, args *Args) string105}106107// App is the proxy, the state and the dispatch, in one object to hold at108// realm level.109type App struct {110	proxy *upgradeable.Proxy111	data  *kv112	self  string113}114115// Owner returns an authority for a single address. Re-exported so a realm116// using this package need not import [upgradeable] as well.117func Owner(addr address) upgradeable.Authority { return upgradeable.NewAddrAuthority(addr) }118119// Governed returns an authority for one or more governance realm paths.120func Governed(paths ...string) upgradeable.Authority {121	return upgradeable.NewRealmAuthority(paths...)122}123124// New returns an App whose implementations must be deployed under the app125// realm's own path.126func New(auth upgradeable.Authority) *App {127	return &App{proxy: upgradeable.New(auth), data: newKV()}128}129130// NewOpen returns an App that accepts implementations from any realm. The131// authority still decides which one serves.132func NewOpen(auth upgradeable.Authority) *App {133	return &App{proxy: upgradeable.NewOpen(auth), data: newKV()}134}135136// Bind teaches the app its own path, which is what every later caller check137// compares against. Call it once, from your realm's init:138//139//	func init(cur realm) { a.Bind(0, cur) }140//141// It is separate from New because a package-level var initializer runs before142// there is a realm frame to read the path from.143func (a *App) Bind(_ int, rlm realm) {144	if !rlm.IsCurrent() {145		panic(ErrStaleRealm)146	}147	a.self = rlm.PkgPath()148}149150// Proxy exposes the underlying proxy, for realms that want typed entry points151// beside Call or their own Render.152func (a *App) Proxy() *upgradeable.Proxy { return a.proxy }153154// The inspection surface. Render shows all of it as a page; these are the155// same facts as discrete values, which is what an operator wants from156// vm/qeval and what a monitor wants to poll.157158// LivePath returns the realm path of the handler currently serving, or "".159func (a *App) LivePath() string { return a.proxy.LivePath() }160161// PendingPaths returns the candidate handler paths awaiting acceptance.162func (a *App) PendingPaths() []string { return paths(a.proxy.Pending()) }163164// HistoryPaths returns the handlers already served, oldest first. A non-empty165// result is what makes rollback available.166func (a *App) HistoryPaths() []string { return paths(a.proxy.History()) }167168// Extensions returns the realms permitted to act as this app.169func (a *App) Extensions() []string { return a.proxy.Extensions() }170171// Frozen reports whether upgradeability has ended.172func (a *App) Frozen() bool { return a.proxy.Frozen() }173174// Authority describes who may upgrade.175func (a *App) Authority() string { return a.proxy.Authority().String() }176177func paths(rs []upgradeable.Release) []string {178	out := []string{}179	for _, r := range rs {180		out = append(out, r.PkgPath())181	}182	return out183}184185// handler returns the live handler.186func (a *App) handler() Handler {187	v, ok := a.proxy.TryImpl()188	if !ok {189		panic(ErrNoHandler)190	}191	h, ok := v.(Handler)192	if !ok {193		panic(ErrNotAHandler)194	}195	return h196}197198// Call decodes payload against the live handler's declaration for verb, and199// dispatches.200//201// An unknown verb, a payload with the wrong number of fields, or a field that202// does not parse as its declared kind are all refused here, before the handler203// runs, with a message naming the verb and its signature.204func (a *App) Call(_ int, rlm realm, verb string, payload string) string {205	h := a.handler()206	sc := h.Schema()207	v, ok := sc.Lookup(verb)208	if !ok {209		panic(errors.New("app: unknown verb " + strconv.Quote(verb) +210			"; this app answers " + strings.Join(sc.Names(), ", ")))211	}212	args, err := decode(v, payload)213	if err != nil {214		panic(err)215	}216	return h.Handle(0, rlm, args)217}218219// Schema returns the live handler's declaration, or nil if nothing is live.220func (a *App) Schema() *Schema {221	v, ok := a.proxy.TryImpl()222	if !ok {223		return nil224	}225	h, ok := v.(Handler)226	if !ok {227		return nil228	}229	return h.Schema()230}231232// Verbs enumerates the API. This is the query a caller makes first.233func (a *App) Verbs() []string { return a.Schema().Names() }234235// Signature returns one verb's declaration, or "" if it is not answered.236func (a *App) Signature(verb string) string { return a.Schema().Signature(verb) }237238// SchemaJSON renders the API for tooling: enough to build a form without239// reading any Gno source.240func (a *App) SchemaJSON() string { return a.Schema().JSON() }241242// Supports reports whether the live handler answers verb.243func (a *App) Supports(verb string) bool {244	_, ok := a.Schema().Lookup(verb)245	return ok246}247248// Register nominates the calling realm's handler. Forward it from your realm249// and call it from the implementation's init.250//251// A handler that declares no verbs is refused here rather than at first call:252// an app whose API cannot be enumerated is the thing this package exists to253// avoid.254func (a *App) Register(_ int, rlm realm, h Handler) {255	if h == nil || len(h.Schema().Names()) == 0 {256		panic(ErrNoSchema)257	}258	a.proxy.Propose(0, rlm, h)259}260261// candidate returns the registered handler at pkgPath.262func (a *App) candidate(pkgPath string) (Handler, bool) {263	for _, r := range a.proxy.Pending() {264		if r.PkgPath() == pkgPath {265			h, ok := r.Impl().(Handler)266			return h, ok267		}268	}269	return nil, false270}271272// accept promotes a candidate, refusing an upgrade that would break callers273// unless force is set.274//275// This is the check a verb-dispatch API cannot have without a schema. Without276// it, a handler that quietly drops a verb its predecessor served is accepted277// happily and callers find out at runtime. With it, the drop is named before278// the upgrade lands.279func (a *App) accept(_ int, rlm realm, pkgPath string, force bool) {280	a.proxy.AssertAuthorized(0, rlm)281	next, ok := a.candidate(pkgPath)282	if ok && !force {283		if breaks := next.Schema().Diff(a.Schema()); len(breaks) > 0 {284			panic(errors.New("app: refusing a breaking upgrade to " + pkgPath +285				": " + strings.Join(breaks, "; ") +286				" — use the accept-breaking verb to override"))287		}288	}289	a.proxy.Accept(0, rlm, pkgPath)290}291292// Store hands out a [Store] accessor to code running on this app's cur: the293// live handler, and any realm the authority has registered as an extension.294//295// A realm holding a perfectly valid cur of its own is refused, because that296// cur carries its own path. And because the accessor carries the cur and297// re-checks it on every operation, the grant cannot outlive this call frame:298// a handler that stashes the accessor aborts the transaction (a realm value299// cannot be persisted), so rollback and freeze can no longer be undermined by300// a captured reference. See [Store].301func (a *App) Store(_ int, rlm realm) *Store {302	a.access(0, rlm) // gate at acquisition, for an immediate, clear refusal303	return &Store{app: a, rlm: rlm}304}305306// access validates that rlm may reach the data and returns it. It runs at307// acquisition and again on every Store operation, so a stale or foreign rlm308// is refused every time, not just when the accessor is minted -- which is the309// teeth behind Store being a per-call capability.310func (a *App) access(_ int, rlm realm) *kv {311	if !rlm.IsCurrent() {312		panic(ErrStaleRealm)313	}314	if a.self == "" {315		panic(ErrNotBound)316	}317	if c := rlm.PkgPath(); c != a.self && !a.proxy.IsExtension(c) {318		panic(errors.New(ErrForbidden.Error() + ": " + c))319	}320	return a.data321}322323// Admin is the whole governance surface behind one entry point, so a realm324// using this package declares one function instead of one per governance325// action.326//327//	accept <path>     make a registered candidate live, unless its schema328//	                  would break a caller of the one it replaces329//	accept-breaking <path>330//	                  the same, allowing a breaking schema change331//	withdraw <path>   drop a candidate332//	rollback          restore the previous handler333//	forget            drop the rollback history334//	freeze            end upgradeability, permanently335//	extend <path>     let another realm act as this one336//	unextend <path>   revoke that337//	owner <address>   hand authority to an address338//	govern <paths>    hand authority to governance realms (comma-separated)339//340// Every one of them is authority-gated by the proxy, except that a realm may341// always withdraw its own candidate. Returns "ok", or panics.342func (a *App) Admin(_ int, rlm realm, verb string, arg string) string {343	switch verb {344	case "accept":345		a.accept(0, rlm, arg, false)346	case "accept-breaking":347		a.accept(0, rlm, arg, true)348	case "withdraw":349		a.proxy.Withdraw(0, rlm, arg)350	case "rollback":351		a.proxy.Rollback(0, rlm)352	case "forget":353		a.proxy.Forget(0, rlm)354	case "freeze":355		a.proxy.Freeze(0, rlm)356	case "extend":357		a.proxy.AddExtension(0, rlm, arg)358	case "unextend":359		a.proxy.DropExtension(0, rlm, arg)360	case "owner":361		a.proxy.TransferAuthority(0, rlm, Owner(address(arg)))362	case "govern":363		parts := strings.Split(arg, ",")364		paths := make([]string, 0, len(parts))365		for _, p := range parts {366			paths = append(paths, strings.TrimSpace(p))367		}368		a.proxy.TransferAuthority(0, rlm, Governed(paths...))369	default:370		panic(errors.New(ErrUnknownVerb.Error() + ": " + verb))371	}372	return "ok"373}374375// Render is a gnoweb page describing the app's current wiring: what serves,376// what could, what it used to, who decides, and how much state there is.377func (a *App) Render(_ string) string {378	var b strings.Builder379	b.WriteString("# " + a.self + "\n\n")380381	if path := a.proxy.LivePath(); path == "" {382		b.WriteString("_No handler accepted yet._\n\n")383	} else {384		b.WriteString("Serving: `" + path + "`\n\n")385	}386	b.WriteString("State: **" + strconv.Itoa(a.data.length()) + "** keys\n\n")387	b.WriteString("Authority: `" + a.proxy.Authority().String() + "`\n\n")388389	if sc := a.Schema(); sc != nil {390		b.WriteString("## API\n\n" + sc.Markdown() + "\n")391	}392393	if a.proxy.Frozen() {394		b.WriteString("**Frozen.** The handler can no longer be replaced.\n\n")395	}396	writeList(&b, "Extensions", a.proxy.Extensions())397	writeReleases(&b, "Awaiting acceptance", a.proxy.Pending())398	writeReleases(&b, "Previously", a.proxy.History())399	return b.String()400}401402func writeList(b *strings.Builder, title string, items []string) {403	if len(items) == 0 {404		return405	}406	b.WriteString("## " + title + "\n\n")407	for _, s := range items {408		b.WriteString("- `" + s + "`\n")409	}410	b.WriteString("\n")411}412413func writeReleases(b *strings.Builder, title string, rs []upgradeable.Release) {414	if len(rs) == 0 {415		return416	}417	b.WriteString("## " + title + "\n\n")418	for _, r := range rs {419		b.WriteString("- `" + r.PkgPath() + "` (height " +420			strconv.FormatInt(r.Height(), 10) + ")\n")421	}422	b.WriteString("\n")423}424

Functions

not supported for pure packages by the node (vm/qfuncs)

Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.