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

registry.gno

5.47 Kb · 146 lines
  1// Package registry is an open, on-chain map from a deployed gno package path
  2// back to the source that produced it: repository, commit, directory.
  3//
  4// It is the realm half of gno.land/p/moul/gnopm/v0, which holds the domain
  5// model and the whole of the reasoning; this file is wiring. Every exported
  6// mutation is a crossing function that resolves the caller, forwards to the
  7// library, aborts on error (the only way to revert state in gno) and emits an
  8// event for indexers.
  9//
 10// # It cannot promise, and does not pretend to
 11//
 12// Nothing here is verified and nothing here can be. A realm cannot clone a
 13// repository, so it cannot check that the bytes at the claimed commit are the
 14// bytes deployed at the claimed path. What it stores is testimony under a
 15// signature: this address says this package came from that source.
 16//
 17// That is still worth storing, for two reasons.
 18//
 19// First, it is testimony in the exact shape a verifier needs. Path, repository,
 20// commit and directory are precisely the four inputs to "hash the addpkg
 21// payload of that directory and compare it with what the chain hands back",
 22// which gnopm already does against a local tree (`gnopm verify -deployed`). The
 23// registry does not answer the question; it makes the question answerable by
 24// anything that can clone.
 25//
 26// Second, the chain knows who signed. A claim from the address that owns the
 27// package path's namespace comes from the party that controls the path, and
 28// OwnedBy reports that. It is computed on every read, never stored, because a
 29// name can be transferred and a stored answer would rot into exactly the kind
 30// of stale claim this realm exists to distinguish from a live one.
 31//
 32// # Open, and tagged
 33//
 34// Anyone may claim any path, including one they had nothing to do with. That is
 35// deliberate: gating registration on namespace ownership would leave the map
 36// empty on day one, when almost nothing has been registered by its own
 37// deployer, and an empty registry teaches nobody anything. A stranger's claim
 38// is not suppressed, it is labelled and ranked below the owner's, and Render
 39// never presents an unverified claim as a fact.
 40//
 41// A claimant may always withdraw their own claim, and may never touch anyone
 42// else's.
 43package registry
 44
 45import (
 46	"chain"
 47	"chain/runtime"
 48
 49	pm "gno.land/p/moul/gnopm/v0"
 50	"gno.land/r/sys/users"
 51)
 52
 53var reg = pm.New()
 54
 55// caller is the address that crossed into this realm. Deliberately not
 56// restricted to end users: a realm may hold a namespace, and a DAO registering
 57// the source of the packages it governs is the same operation.
 58func caller(cur realm) address {
 59	if !cur.IsCurrent() {
 60		panic("gnopm/registry: spoofed realm")
 61	}
 62	return cur.Previous().Address()
 63}
 64
 65func must(err error) {
 66	if err != nil {
 67		panic(err)
 68	}
 69}
 70
 71// resolveName is the r/sys/users half of the ownership rule, passed to the
 72// library so the library stays pure.
 73//
 74// isCurrent is deliberately ignored, matching r/moul/forge: a name that
 75// resolves through an alias still resolves to the same holder, so a namespace
 76// survives its owner renaming. IsDeleted folds the nil check in, which is what
 77// its own doc comment asks call sites to rely on.
 78func resolveName(name string) (address, bool) {
 79	data, _ := users.ResolveName(name)
 80	if data.IsDeleted() {
 81		return "", false
 82	}
 83	return data.Addr(), true
 84}
 85
 86// Register records that pkgPath was built from dir of repo at commit.
 87//
 88// Calling it again for the same path replaces the caller's own claim and
 89// nobody else's, so moving a claim to a new commit after a redeploy is one
 90// call with no read first.
 91//
 92// ref is optional and is never the thing verified: a ref moves, a commit does
 93// not. It is recorded so a reader can tell a claim pinned to a released tag
 94// from one pinned to a commit on nobody's branch, and so a verifier can report
 95// a commit that has since been orphaned by a force-push.
 96//
 97// dir is empty when the package sits at the repository root.
 98func Register(cur realm, pkgPath, repo, commit, dir, ref string) {
 99	a := caller(cur)
100	_, err := reg.Register(a, runtime.ChainHeight(), pkgPath, repo, commit, dir, ref)
101	must(err)
102	chain.Emit("SourceClaimed",
103		"pkgpath", pkgPath,
104		"claimant", a.String(),
105		"repo", repo,
106		"commit", commit,
107	)
108}
109
110// Withdraw removes the caller's own claim about pkgPath.
111func Withdraw(cur realm, pkgPath string) {
112	a := caller(cur)
113	must(reg.Withdraw(a, pkgPath))
114	chain.Emit("SourceWithdrawn", "pkgpath", pkgPath, "claimant", a.String())
115}
116
117// PackageCount is how many package paths carry at least one claim.
118func PackageCount() int { return reg.Size() }
119
120// ClaimCount is the total number of claims across every path.
121func ClaimCount() int { return reg.Claims() }
122
123// HasClaims reports whether anything has been said about pkgPath. Cheap enough
124// for another realm or an indexer to ask per package.
125func HasClaims(pkgPath string) bool { return reg.Package(pkgPath) != nil }
126
127// OwnerClaim returns the repository, commit and directory claimed by the party
128// that owns pkgPath's namespace, and whether such a claim exists.
129//
130// This is the only read that filters, and it filters on the one signal the
131// chain can actually prove. Everything else a caller wants is in Render or in
132// the events.
133func OwnerClaim(pkgPath string) (repo, commit, dir string, ok bool) {
134	p := reg.Package(pkgPath)
135	if p == nil {
136		return "", "", "", false
137	}
138	p.IterateClaims(func(c *pm.Claim) bool {
139		if c.OwnedBy(resolveName) {
140			repo, commit, dir, ok = c.Repo, c.Commit, c.Dir, true
141			return true // stop
142		}
143		return false
144	})
145	return repo, commit, dir, ok
146}