Transaction

3A65941767CDCD…FD2E566578F0

Block 272,415 · index 1 · indexed

Summary

Hash
3A65941767CDCD2E1621869E2FBB28AE90A01E1076865775051AFD2E566578F0
Block
272,415
Size
17850 bytes
Gas used
24,021,681 / 57,500,000
Fee
172500ugnot
Status
success

Messages

#1AddPackagegno.land/r/moul/x/plan9/dev/v011 arguments
Attached funds
8000000ugnot

Arguments · 11

  1. #1dev
  2. #2README.md
  3. #3# `gno.land/r/moul/x/plan9/dev/v0` **The chain as a Plan 9 device tree.** Everything a realm normally reaches through an import of `chain/runtime` is published here as a file instead, so it can be read, listed, bound and unioned like anything else. ``` cat /dev/sysname the chain id cat /dev/height the block height cat /dev/session what a delegated key is allowed to touch ``` | device | contents | |---|---| | `caller` | pkgpath of the realm that crossed into this read | | `domain` | the chain domain | | `drivers` | the table itself | | `height` | current block height | | `null` | always empty | | `random` | sha256 of chain id and height, hex; block-deterministic, **not** unpredictable | | `session` | the calling key's session scope, one `AllowPath` per line | | `sysname` | the chain id | | `time` | block time, RFC3339 | | `user` | the origin caller's address | | `zero` | endless NUL bytes; reads exactly what you ask for | A device is code, not storage: reading `/dev/height` runs a function, so it is never stale. ## The cross-realm mount This realm posts its tree to [`r/moul/x/plan9/ns`](../../../../../r/moul/x/plan9/ns/v0)'s `/srv` at deploy time, so any account can bind it into their own namespace and nobody has to import this realm to use it: ``` bind /srv/dev /dev ``` That is the mechanism the whole experiment turns on: an interface value published by one realm, stored by another, and called back later from a read-only `Render`. Gno has no dynamic dispatch by path, so a mount cannot be a client pulling a server by name; `/srv` is not a convenience here, it is the only available mechanism, which is a pleasing accident. The tree is read-only, like every [`synfs`](../../../../../p/moul/x/plan9/synfs/v0) tree: the files have no `Write`, so grafting this into a stranger's namespace cannot be turned into a write against this realm. ## Why `/dev/session` is the interesting one A gno.land account session is a delegated key scoped to a list of path prefixes. That is Plan 9's "the namespace *is* the capability set", rediscovered thirty-four years later, minus the ability to look at it as a tree. This file prints it. Design and analysis: [moul/gno-contracts#136](https://github.com/moul/gno-contracts/issues/136). --- **Not affiliated with Plan 9.** Plan 9 from Bell Labs is the work of the Computing Science Research Center at Bell Labs; the name and the marks are theirs, and the copyright is held by the [Plan 9 Foundation](https://p9f.org). This realm borrows the vocabulary and none of the code: it is an independent homage, asking what that ecosystem's spirit looks like on a chain. Full attribution: [NOTICE](../../../../../NOTICE.md). <!-- BEGIN GNOCONTRACTS FOOTER (generated by `make readmes`; do not edit below) --> --- Part of **[moul/gno-contracts](https://github.com/moul/gno-contracts)** — moul's versioned gno.land contracts. See the repository for the full catalog, build/test tooling, and usage. **Dependency graph:** ![gno.land/r/moul/x/plan9/dev/v0 dependency graph](https://raw.githubusercontent.com/moul/gno-contracts/main/_assets/gno.land/r/moul/x/plan9/dev/v0/deps.png) > 🧪 **Highly experimental — potentially vibe-coded.** Not audited; may break, change, or be removed at any time. Do not use with anything of value. Full disclaimer: [DISCLAIMER](https://github.com/moul/gno-contracts/blob/main/DISCLAIMER.md). <!-- END GNOCONTRACTS FOOTER -->
  4. #4dev.gno
  5. #5// Package dev is the chain as a Plan 9 device tree. // // In Plan 9 a device is not storage, it is code behind a name: reading // /dev/time runs a function. Everything a gno realm normally reaches through // an import of chain/runtime is published here as a file instead, so it can be // read, listed, bound and unioned like anything else: // // cat /dev/sysname the chain id // cat /dev/height the block height // cat /dev/session what a delegated key is allowed to touch // // The tree is posted to gno.land/r/moul/x/plan9/ns's /srv at deploy time, so // any account can bind it into their own namespace, and nobody has to import // this realm to use it. That is the cross-realm mount the whole experiment // turns on: an interface value published by one realm, stored by another, and // called later from a read-only Render. // // It is READ-ONLY, like every synthetic tree: the files have no Write, so // grafting this into a stranger's namespace cannot be turned into a write // against this realm. // // /dev/session is the one to look at. A gno.land account session is a // delegated key scoped to a list of path prefixes, which is Plan 9's "the // namespace IS the capability" rediscovered thirty-four years later. Printing // it as a file makes that visible. // // NOTICE. Plan 9 from Bell Labs is the work of the Computing Science Research // Center at Bell Labs; the name and the marks are theirs, and the copyright is // held by the Plan 9 Foundation (https://p9f.org). This realm is not // affiliated with, endorsed by, or sponsored by them, and contains no Plan 9 // code: it borrows the vocabulary so that the design reads without a glossary, // and it is an homage, asking what that ecosystem's spirit looks like on a // chain. Full attribution: NOTICE.md at the root of moul/gno-contracts. package dev import ( "chain/runtime" "chain/runtime/unsafe" "crypto/sha256" "encoding/hex" "strconv" "strings" "time" ninep "gno.land/p/moul/x/plan9/ninep/v0" synfs "gno.land/p/moul/x/plan9/synfs/v0" nsrealm "gno.land/r/moul/x/plan9/ns/v0" ) var tree *synfs.Tree // docs describes each device, for Render and for /dev/drivers. var docs = [][2]string{ {"caller", "pkgpath of the realm that crossed into this read"}, {"domain", "the chain domain"}, {"drivers", "this table"}, {"height", "current block height"}, {"null", "always empty; the bit bucket"}, {"random", "sha256 of chain id and height, hex; block-deterministic, NOT unpredictable"}, {"session", "the calling key's session scope, one AllowPath per line"}, {"sysname", "the chain id"}, {"time", "block time, RFC3339"}, {"user", "the origin caller's address"}, {"zero", "endless NUL bytes; reads exactly what you ask for"}, } func init(cur realm) { tree = build() nsrealm.Post(cross(cur), "dev", tree.Root()) } func build() *synfs.Tree { t := synfs.New("dev", "sys", func() int64 { return runtime.ChainHeight() }) r := t.Root() r.Add("sysname", func() string { return runtime.ChainID() }) r.Add("domain", func() string { return runtime.ChainDomain() }) r.Add("height", func() string { return strconv.FormatInt(runtime.ChainHeight(), 10) }) r.Add("time", func() string { return time.Now().Format(time.RFC3339) }) r.Add("user", func() string { return unsafe.OriginCaller().String() }) r.Add("caller", func() string { return unsafe.PreviousRealm().PkgPath() }) r.Add("null", func() string { return "" }) r.Add("random", func() string { sum := sha256.Sum256([]byte(runtime.ChainID() + ":" + strconv.FormatInt(runtime.ChainHeight(), 10))) return hex.EncodeToString(sum[:]) }) r.Add("session", session) r.Add("drivers", drivers) // /dev/zero has no end, so it serves the read window itself rather than // materialising a value. An unbounded read returns nothing, which is what // stops `cat /dev/zero` from being a denial of service. r.AddRange("zero", 0444, func(off, count int64) (string, error) { if count <= 0 { return "", nil } if count > 4096 { count = 4096 } return strings.Repeat("\x00", int(count)), nil }) return t } // session prints the calling key's authority. A plain key has none, which is // itself worth saying out loud. func session() string { pubKeyAddr, expiresAt, allowPaths, isSession := runtime.GetSessionInfo() if !isSession { var b strings.Builder b.WriteString("session no\n") b.WriteString("scope full\n") return b.String() } var b strings.Builder b.WriteString("session yes\n") b.WriteString("key " + pubKeyAddr.String() + "\n") b.WriteString("expires " + strconv.FormatInt(expiresAt, 10) + "\n") for _, p := range allowPaths { b.WriteString("allow " + p + "\n") } return b.String() } func drivers() string { var b strings.Builder for _, d := range docs { b.WriteString(d[0] + "\t" + d[1] + "\n") } return b.String() } // Root returns the device tree, for a realm that would rather import it than // bind it. Reading it is safe from anywhere; it has no mutating method. func Root() ninep.File { return tree.Root() } // Render lists the devices, or reads one. // // Render("") the table of devices // Render("cat/height") one device's contents func Render(path string) string { parts := strings.Split(strings.Trim(path, "/"), "/") if len(parts) >= 2 && parts[0] == "cat" { name := parts[1] f, err := tree.Root().Walk(name) if err != nil { return "# /dev/" + name + "\n\n```\n" + err.Error() + "\n```\n" } data, err := ninep.ReadAll(f) if err != nil { return "# /dev/" + name + "\n\n```\n" + err.Error() + "\n```\n" } return "# /dev/" + name + "\n\n```\n" + data + "\n```\n\n[all devices](:)\n" } return renderIndex() } func renderIndex() string { var b strings.Builder b.WriteString("# /dev\n\n") b.WriteString("The chain as a Plan 9 device tree. Each file is a function: reading it ") b.WriteString("runs code, so `/dev/height` is never stale.\n\n") b.WriteString("Posted to [`r/moul/x/plan9/ns`](/r/moul/x/plan9/ns/v0)'s `/srv` at deploy ") b.WriteString("time, so no realm has to import this one to use it:\n\n") b.WriteString("```\nbind /srv/dev /dev\n```\n\n") b.WriteString("| device | contents |\n|---|---|\n") for _, d := range docs { b.WriteString("| [`" + d[0] + "`](:cat/" + d[0] + ") | " + d[1] + " |\n") } b.WriteString("\nDesign and analysis: ") b.WriteString("[moul/gno-contracts#136](https://github.com/moul/gno-contracts/issues/136).\n") b.WriteString("\n_Not affiliated with Plan 9. Plan 9 from Bell Labs is the ") b.WriteString("work of the Computing Science Research Center at Bell Labs; the name ") b.WriteString("and the marks are theirs, and the copyright is held by the ") b.WriteString("[Plan 9 Foundation](https://p9f.org). This realm borrows the ") b.WriteString("vocabulary and none of the code: it is an homage, asking what that ") b.WriteString("ecosystem's spirit looks like on a chain._\n") return b.String() }
  6. #6dev_test.gno
  7. #7package dev import ( "chain/runtime" "strconv" "strings" "testing" "gno.land/p/nt/testutils/v0" ninep "gno.land/p/moul/x/plan9/ninep/v0" nsrealm "gno.land/r/moul/x/plan9/ns/v0" ) func read(t *testing.T, name string) string { t.Helper() f, err := tree.Root().Walk(name) if err != nil { t.Fatalf("walk %s: %v", name, err) } data, err := ninep.ReadAll(f) if err != nil { t.Fatalf("read %s: %v", name, err) } return data } func TestDevicesFollowTheChain(t *testing.T) { if got, want := read(t, "sysname"), runtime.ChainID(); got != want { t.Errorf("sysname: got %q, want %q", got, want) } if got, want := read(t, "domain"), runtime.ChainDomain(); got != want { t.Errorf("domain: got %q, want %q", got, want) } if got, want := read(t, "height"), strconv.FormatInt(runtime.ChainHeight(), 10); got != want { t.Errorf("height: got %q, want %q", got, want) } // A device is code, not storage: advance the chain and the file follows. before := read(t, "height") testing.SkipHeights(5) after := read(t, "height") if before == after { t.Errorf("height did not move: %q", after) } b, _ := strconv.ParseInt(before, 10, 64) a, _ := strconv.ParseInt(after, 10, 64) if a-b != 5 { t.Errorf("height moved by %d, want 5", a-b) } } func TestRandomIsBlockDeterministic(t *testing.T) { a := read(t, "random") if len(a) != 64 { t.Errorf("a sha256 in hex is 64 characters, got %d", len(a)) } if a != read(t, "random") { t.Error("two reads in the same block must agree, or Render is a consensus bug") } testing.SkipHeights(1) if a == read(t, "random") { t.Error("the value should change with the block") } } func TestNullAndZero(t *testing.T) { if got := read(t, "null"); got != "" { t.Errorf("null: %q", got) } f, _ := tree.Root().Walk("zero") got, _ := f.Read(0, 4) if got != "\x00\x00\x00\x00" { t.Errorf("zero: %q", got) } // An endless file must not answer an unbounded read with an endless value. if got, _ := f.Read(0, -1); got != "" { t.Errorf("unbounded read of /dev/zero: %d bytes", len(got)) } if got, _ := f.Read(0, 1<<20); len(got) != 4096 { t.Errorf("a huge read should cap at 4096, got %d", len(got)) } } func TestSessionReportsAPlainKey(t *testing.T) { got := read(t, "session") if got != "session no\nscope full\n" { t.Errorf("a plain key has no session scope: %q", got) } } func TestDriversListsEveryDevice(t *testing.T) { got := read(t, "drivers") ents, err := tree.Root().ReadDir() if err != nil { t.Fatalf("readdir: %v", err) } for _, e := range ents { if !strings.Contains(got, e.Name+"\t") { t.Errorf("/dev/drivers does not document %q", e.Name) } } if len(ents) != len(docs) { t.Errorf("%d devices but %d documented", len(ents), len(docs)) } } func TestTreeIsReadOnly(t *testing.T) { var f ninep.File = tree.Root() if _, ok := f.(ninep.Mutable); ok { t.Error("a device tree handed to other realms must not be Mutable") } } // TestPostedAtDeployTime is the cross-realm claim the whole suite rests on: // this realm pushed an interface value into another realm at init, and that // realm can call it back later from a read. func TestPostedAtDeployTime(t *testing.T) { found := false for _, s := range nsrealm.Services() { if s == "dev" { found = true } } if !found { t.Fatalf("dev is not posted in /srv: %v", nsrealm.Services()) } } func TestAFreshNamespaceHasDevBound(cur realm, t *testing.T) { user := testutils.TestAddress("glenda") testing.SetRealm(testing.NewUserRealm(user)) nsrealm.Reset(cross(cur)) // Touch the namespace so it is built with /dev already bound. nsrealm.Exec(cross(cur), "echo hi > /tmp/hi") out, err := nsrealm.Run(user.String(), "ls /dev") if err != nil { t.Fatalf("ls /dev: %v", err) } for _, d := range docs { if !strings.Contains(out, d[0]) { t.Errorf("ls /dev is missing %q: %q", d[0], out) } } got, err := nsrealm.Run(user.String(), "cat /dev/sysname") if err != nil { t.Fatalf("cat: %v", err) } if got != runtime.ChainID() { t.Errorf("reading this realm's device through another realm's namespace: %q", got) } // And the mount table says where it came from. if ns := nsrealm.Namespace(user.String()); !strings.Contains(ns, "bind /srv/dev /dev") { t.Errorf("mount table: %q", ns) } }
  8. #8gnomod.toml
  9. #9module = "gno.land/r/moul/x/plan9/dev/v0" gno = "0.9" # public: posts its own tree into r/moul/x/plan9/ns, and a private realm may # not persist an object it owns into another realm
  10. #10render_example_test.gno
  11. #11package dev // ExampleRender pins the device index. It carries no chain value, so it does // not move with the block height the way the devices themselves do. func ExampleRender() { print(Render("")) // Output: // # /dev // // The chain as a Plan 9 device tree. Each file is a function: reading it runs code, so `/dev/height` is never stale. // // Posted to [`r/moul/x/plan9/ns`](/r/moul/x/plan9/ns/v0)'s `/srv` at deploy time, so no realm has to import this one to use it: // // ``` // bind /srv/dev /dev // ``` // // | device | contents | // |---|---| // | [`caller`](:cat/caller) | pkgpath of the realm that crossed into this read | // | [`domain`](:cat/domain) | the chain domain | // | [`drivers`](:cat/drivers) | this table | // | [`height`](:cat/height) | current block height | // | [`null`](:cat/null) | always empty; the bit bucket | // | [`random`](:cat/random) | sha256 of chain id and height, hex; block-deterministic, NOT unpredictable | // | [`session`](:cat/session) | the calling key's session scope, one AllowPath per line | // | [`sysname`](:cat/sysname) | the chain id | // | [`time`](:cat/time) | block time, RFC3339 | // | [`user`](:cat/user) | the origin caller's address | // | [`zero`](:cat/zero) | endless NUL bytes; reads exactly what you ask for | // // Design and analysis: [moul/gno-contracts#136](https://github.com/moul/gno-contracts/issues/136). // // _Not affiliated with Plan 9. Plan 9 from Bell Labs is the work of the Computing Science Research Center at Bell Labs; the name and the marks are theirs, and the copyright is held by the [Plan 9 Foundation](https://p9f.org). This realm borrows the vocabulary and none of the code: it is an homage, asking what that ecosystem's spirit looks like on a chain._ } // ExampleRender_cat pins one device read. /dev/drivers is the one device whose // contents are fixed, which makes it the only one an example can assert; the // chain-derived ones are checked against chain/runtime in dev_test.gno. func ExampleRender_cat() { print(Render("cat/drivers")) // Output: // # /dev/drivers // // ``` // caller pkgpath of the realm that crossed into this read // domain the chain domain // drivers this table // height current block height // null always empty; the bit bucket // random sha256 of chain id and height, hex; block-deterministic, NOT unpredictable // session the calling key's session scope, one AllowPath per line // sysname the chain id // time block time, RFC3339 // user the origin caller's address // zero endless NUL bytes; reads exactly what you ask for // // ``` // // [all devices](:) }

Result log

msg:0,success:true,log:,events:[]

← Back to block 272,415