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

dev.gno

6.89 Kb · 183 lines
  1// Package dev is the chain as a Plan 9 device tree.
  2//
  3// In Plan 9 a device is not storage, it is code behind a name: reading
  4// /dev/time runs a function. Everything a gno realm normally reaches through
  5// an import of chain/runtime is published here as a file instead, so it can be
  6// read, listed, bound and unioned like anything else:
  7//
  8//	cat /dev/sysname     the chain id
  9//	cat /dev/height      the block height
 10//	cat /dev/session     what a delegated key is allowed to touch
 11//
 12// The tree is posted to gno.land/r/moul/x/plan9/ns's /srv at deploy time, so
 13// any account can bind it into their own namespace, and nobody has to import
 14// this realm to use it. That is the cross-realm mount the whole experiment
 15// turns on: an interface value published by one realm, stored by another, and
 16// called later from a read-only Render.
 17//
 18// It is READ-ONLY, like every synthetic tree: the files have no Write, so
 19// grafting this into a stranger's namespace cannot be turned into a write
 20// against this realm.
 21//
 22// /dev/session is the one to look at. A gno.land account session is a
 23// delegated key scoped to a list of path prefixes, which is Plan 9's "the
 24// namespace IS the capability" rediscovered thirty-four years later. Printing
 25// it as a file makes that visible.
 26//
 27// NOTICE. Plan 9 from Bell Labs is the work of the Computing Science Research
 28// Center at Bell Labs; the name and the marks are theirs, and the copyright is
 29// held by the Plan 9 Foundation (https://p9f.org). This realm is not
 30// affiliated with, endorsed by, or sponsored by them, and contains no Plan 9
 31// code: it borrows the vocabulary so that the design reads without a glossary,
 32// and it is an homage, asking what that ecosystem's spirit looks like on a
 33// chain. Full attribution: NOTICE.md at the root of moul/gno-contracts.
 34package dev
 35
 36import (
 37	"chain/runtime"
 38	"chain/runtime/unsafe"
 39	"crypto/sha256"
 40	"encoding/hex"
 41	"strconv"
 42	"strings"
 43	"time"
 44
 45	"gno.land/p/moul/kit/ui/v0"
 46	"gno.land/p/moul/md/v0"
 47	ninep "gno.land/p/moul/x/plan9/ninep/v0"
 48	synfs "gno.land/p/moul/x/plan9/synfs/v0"
 49
 50	nsrealm "gno.land/r/moul/x/plan9/ns/v1"
 51)
 52
 53var tree *synfs.Tree
 54
 55// docs describes each device, for Render and for /dev/drivers.
 56var docs = [][2]string{
 57	{"caller", "pkgpath of the realm that crossed into this read"},
 58	{"domain", "the chain domain"},
 59	{"drivers", "this table"},
 60	{"height", "current block height"},
 61	{"null", "always empty; the bit bucket"},
 62	{"random", "sha256 of chain id and height, hex; block-deterministic, NOT unpredictable"},
 63	{"session", "the calling key's session scope, one AllowPath per line"},
 64	{"sysname", "the chain id"},
 65	{"time", "block time, RFC3339"},
 66	{"user", "the origin caller's address"},
 67	{"zero", "endless NUL bytes; reads exactly what you ask for"},
 68}
 69
 70func init(cur realm) {
 71	tree = build()
 72	nsrealm.Post(cross(cur), "dev", tree.Root())
 73}
 74
 75func build() *synfs.Tree {
 76	t := synfs.New("dev", "sys", func() int64 { return runtime.ChainHeight() })
 77	r := t.Root()
 78
 79	r.Add("sysname", func() string { return runtime.ChainID() })
 80	r.Add("domain", func() string { return runtime.ChainDomain() })
 81	r.Add("height", func() string { return strconv.FormatInt(runtime.ChainHeight(), 10) })
 82	r.Add("time", func() string { return time.Now().Format(time.RFC3339) })
 83	r.Add("user", func() string { return unsafe.OriginCaller().String() })
 84	r.Add("caller", func() string { return unsafe.PreviousRealm().PkgPath() })
 85	r.Add("null", func() string { return "" })
 86	r.Add("random", func() string {
 87		sum := sha256.Sum256([]byte(runtime.ChainID() + ":" +
 88			strconv.FormatInt(runtime.ChainHeight(), 10)))
 89		return hex.EncodeToString(sum[:])
 90	})
 91	r.Add("session", session)
 92	r.Add("drivers", drivers)
 93
 94	// /dev/zero has no end, so it serves the read window itself rather than
 95	// materialising a value. An unbounded read returns nothing, which is what
 96	// stops `cat /dev/zero` from being a denial of service.
 97	r.AddRange("zero", 0444, func(off, count int64) (string, error) {
 98		if count <= 0 {
 99			return "", nil
100		}
101		if count > 4096 {
102			count = 4096
103		}
104		return strings.Repeat("\x00", int(count)), nil
105	})
106	return t
107}
108
109// session prints the calling key's authority. A plain key has none, which is
110// itself worth saying out loud.
111func session() string {
112	pubKeyAddr, expiresAt, allowPaths, isSession := runtime.GetSessionInfo()
113	if !isSession {
114		var b strings.Builder
115		b.WriteString("session no\n")
116		b.WriteString("scope full\n")
117		return b.String()
118	}
119	var b strings.Builder
120	b.WriteString("session yes\n")
121	b.WriteString("key " + pubKeyAddr.String() + "\n")
122	b.WriteString("expires " + strconv.FormatInt(expiresAt, 10) + "\n")
123	for _, p := range allowPaths {
124		b.WriteString("allow " + p + "\n")
125	}
126	return b.String()
127}
128
129func drivers() string {
130	var b strings.Builder
131	for _, d := range docs {
132		b.WriteString(d[0] + "\t" + d[1] + "\n")
133	}
134	return b.String()
135}
136
137// Root returns the device tree, for a realm that would rather import it than
138// bind it. Reading it is safe from anywhere; it has no mutating method.
139func Root() ninep.File { return tree.Root() }
140
141// Render lists the devices, or reads one.
142//
143//	Render("")            the table of devices
144//	Render("cat/height")  one device's contents
145func Render(path string) string {
146	parts := strings.Split(strings.Trim(path, "/"), "/")
147	if len(parts) >= 2 && parts[0] == "cat" {
148		name := parts[1]
149		f, err := tree.Root().Walk(name)
150		if err != nil {
151			return "# /dev/" + ui.Inline(name) + "\n\n" + md.CodeBlock(err.Error())
152		}
153		data, err := ninep.ReadAll(f)
154		if err != nil {
155			return "# /dev/" + ui.Inline(name) + "\n\n" + md.CodeBlock(err.Error())
156		}
157		return "# /dev/" + ui.Inline(name) + "\n\n" + md.CodeBlock(strings.TrimSuffix(data, "\n")) + "\n[all devices](/r/moul/x/plan9/dev/v1)\n"
158	}
159	return renderIndex()
160}
161
162func renderIndex() string {
163	var b strings.Builder
164	b.WriteString("# /dev\n\n")
165	b.WriteString("The chain as a Plan 9 device tree. Each file is a function: reading it ")
166	b.WriteString("runs code, so `/dev/height` is never stale.\n\n")
167	b.WriteString("Posted to [`r/moul/x/plan9/ns`](/r/moul/x/plan9/ns/v1)'s `/srv` at deploy ")
168	b.WriteString("time, so no realm has to import this one to use it:\n\n")
169	b.WriteString("```\nbind /srv/dev /dev\n```\n\n")
170	b.WriteString("| device | contents |\n|---|---|\n")
171	for _, d := range docs {
172		b.WriteString("| [`" + d[0] + "`](/r/moul/x/plan9/dev/v1:cat/" + d[0] + ") | " + d[1] + " |\n")
173	}
174	b.WriteString("\nDesign and analysis: ")
175	b.WriteString("[moul/gno-contracts#136](https://github.com/moul/gno-contracts/issues/136).\n")
176	b.WriteString("\n_Not affiliated with Plan 9. Plan 9 from Bell Labs is the ")
177	b.WriteString("work of the Computing Science Research Center at Bell Labs; the name ")
178	b.WriteString("and the marks are theirs, and the copyright is held by the ")
179	b.WriteString("[Plan 9 Foundation](https://p9f.org). This realm borrows the ")
180	b.WriteString("vocabulary and none of the code: it is an homage, asking what that ")
181	b.WriteString("ecosystem's spirit looks like on a chain._\n")
182	return b.String()
183}