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.74 Kb · 181 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	ninep "gno.land/p/moul/x/plan9/ninep/v0"
 46	synfs "gno.land/p/moul/x/plan9/synfs/v0"
 47
 48	nsrealm "gno.land/r/moul/x/plan9/ns/v0"
 49)
 50
 51var tree *synfs.Tree
 52
 53// docs describes each device, for Render and for /dev/drivers.
 54var docs = [][2]string{
 55	{"caller", "pkgpath of the realm that crossed into this read"},
 56	{"domain", "the chain domain"},
 57	{"drivers", "this table"},
 58	{"height", "current block height"},
 59	{"null", "always empty; the bit bucket"},
 60	{"random", "sha256 of chain id and height, hex; block-deterministic, NOT unpredictable"},
 61	{"session", "the calling key's session scope, one AllowPath per line"},
 62	{"sysname", "the chain id"},
 63	{"time", "block time, RFC3339"},
 64	{"user", "the origin caller's address"},
 65	{"zero", "endless NUL bytes; reads exactly what you ask for"},
 66}
 67
 68func init(cur realm) {
 69	tree = build()
 70	nsrealm.Post(cross(cur), "dev", tree.Root())
 71}
 72
 73func build() *synfs.Tree {
 74	t := synfs.New("dev", "sys", func() int64 { return runtime.ChainHeight() })
 75	r := t.Root()
 76
 77	r.Add("sysname", func() string { return runtime.ChainID() })
 78	r.Add("domain", func() string { return runtime.ChainDomain() })
 79	r.Add("height", func() string { return strconv.FormatInt(runtime.ChainHeight(), 10) })
 80	r.Add("time", func() string { return time.Now().Format(time.RFC3339) })
 81	r.Add("user", func() string { return unsafe.OriginCaller().String() })
 82	r.Add("caller", func() string { return unsafe.PreviousRealm().PkgPath() })
 83	r.Add("null", func() string { return "" })
 84	r.Add("random", func() string {
 85		sum := sha256.Sum256([]byte(runtime.ChainID() + ":" +
 86			strconv.FormatInt(runtime.ChainHeight(), 10)))
 87		return hex.EncodeToString(sum[:])
 88	})
 89	r.Add("session", session)
 90	r.Add("drivers", drivers)
 91
 92	// /dev/zero has no end, so it serves the read window itself rather than
 93	// materialising a value. An unbounded read returns nothing, which is what
 94	// stops `cat /dev/zero` from being a denial of service.
 95	r.AddRange("zero", 0444, func(off, count int64) (string, error) {
 96		if count <= 0 {
 97			return "", nil
 98		}
 99		if count > 4096 {
100			count = 4096
101		}
102		return strings.Repeat("\x00", int(count)), nil
103	})
104	return t
105}
106
107// session prints the calling key's authority. A plain key has none, which is
108// itself worth saying out loud.
109func session() string {
110	pubKeyAddr, expiresAt, allowPaths, isSession := runtime.GetSessionInfo()
111	if !isSession {
112		var b strings.Builder
113		b.WriteString("session no\n")
114		b.WriteString("scope full\n")
115		return b.String()
116	}
117	var b strings.Builder
118	b.WriteString("session yes\n")
119	b.WriteString("key " + pubKeyAddr.String() + "\n")
120	b.WriteString("expires " + strconv.FormatInt(expiresAt, 10) + "\n")
121	for _, p := range allowPaths {
122		b.WriteString("allow " + p + "\n")
123	}
124	return b.String()
125}
126
127func drivers() string {
128	var b strings.Builder
129	for _, d := range docs {
130		b.WriteString(d[0] + "\t" + d[1] + "\n")
131	}
132	return b.String()
133}
134
135// Root returns the device tree, for a realm that would rather import it than
136// bind it. Reading it is safe from anywhere; it has no mutating method.
137func Root() ninep.File { return tree.Root() }
138
139// Render lists the devices, or reads one.
140//
141//	Render("")            the table of devices
142//	Render("cat/height")  one device's contents
143func Render(path string) string {
144	parts := strings.Split(strings.Trim(path, "/"), "/")
145	if len(parts) >= 2 && parts[0] == "cat" {
146		name := parts[1]
147		f, err := tree.Root().Walk(name)
148		if err != nil {
149			return "# /dev/" + name + "\n\n```\n" + err.Error() + "\n```\n"
150		}
151		data, err := ninep.ReadAll(f)
152		if err != nil {
153			return "# /dev/" + name + "\n\n```\n" + err.Error() + "\n```\n"
154		}
155		return "# /dev/" + name + "\n\n```\n" + data + "\n```\n\n[all devices](:)\n"
156	}
157	return renderIndex()
158}
159
160func renderIndex() string {
161	var b strings.Builder
162	b.WriteString("# /dev\n\n")
163	b.WriteString("The chain as a Plan 9 device tree. Each file is a function: reading it ")
164	b.WriteString("runs code, so `/dev/height` is never stale.\n\n")
165	b.WriteString("Posted to [`r/moul/x/plan9/ns`](/r/moul/x/plan9/ns/v0)'s `/srv` at deploy ")
166	b.WriteString("time, so no realm has to import this one to use it:\n\n")
167	b.WriteString("```\nbind /srv/dev /dev\n```\n\n")
168	b.WriteString("| device | contents |\n|---|---|\n")
169	for _, d := range docs {
170		b.WriteString("| [`" + d[0] + "`](:cat/" + d[0] + ") | " + d[1] + " |\n")
171	}
172	b.WriteString("\nDesign and analysis: ")
173	b.WriteString("[moul/gno-contracts#136](https://github.com/moul/gno-contracts/issues/136).\n")
174	b.WriteString("\n_Not affiliated with Plan 9. Plan 9 from Bell Labs is the ")
175	b.WriteString("work of the Computing Science Research Center at Bell Labs; the name ")
176	b.WriteString("and the marks are theirs, and the copyright is held by the ")
177	b.WriteString("[Plan 9 Foundation](https://p9f.org). This realm borrows the ")
178	b.WriteString("vocabulary and none of the code: it is an homage, asking what that ")
179	b.WriteString("ecosystem's spirit looks like on a chain._\n")
180	return b.String()
181}