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/moul/gnopm/v0

Package
Open in gnoweb ↗

Overview

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

Files (10)

  • README.mdmarkdown
  • gnomod.tomltoml
  • errors.gnogno
  • pkgpath.gnogno
  • registry.gnogno
  • validate.gnogno
  • helpers_test.gnogno
  • pkgpath_test.gnogno
  • registry_test.gnogno
  • validate_test.gnogno
  • registry.gnogno
    1// Package gnopm is the domain model of a source registry for gno packages: a2// map from a deployed package path back to the repository, commit and directory3// that produced it.4//5// It exists because gno deliberately broke the link Go gets for free. A Go6// import path IS a repository URL, so pkg.go.dev needs no registry; a gno7// import path is a chain address, so nothing anywhere says which source

    Functions

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

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

    8// produced the bytes running at gno.land/p/nt/tinyavl/v0.
    9//
    10// # What this can and cannot promise
    11//
    12// It cannot promise anything, and the design is built around saying so rather
    13// than around hiding it. Two claims look alike and are not the same:
    14//
    15// 1. "this package came from that repository". UNPROVABLE, here or anywhere.
    16// Anyone may deploy any bytes and anyone may claim any repository. No
    17// registry design fixes this, and one that implies otherwise is worse than
    18// none.
    19// 2. "the bytes live at path P equal the addpkg payload of directory D in
    20// repository R at commit C". Mechanically provable, by hashing both sides.
    21// It is NOT provable here: a realm cannot clone a repository. It is
    22// provable off chain by anything that can, and gnopm already does exactly
    23// this against a local tree (`gnopm verify -deployed`).
    24//
    25// So this package stores (1), labelled as the claim it is, in the form (2)
    26// needs to be checkable. A Claim is testimony under a signature. It becomes
    27// evidence only once something outside the chain has gone and looked.
    28//
    29// # What a signature does buy
    30//
    31// The chain knows who signed a claim, and that is not nothing. A claim whose
    32// claimant owns the package path's namespace comes from the party that controls
    33// the path, which is the closest thing to a promise available without a
    34// chain-level feature. OwnedBy reports that relationship; it is deliberately
    35// computed on demand from a caller-supplied resolver and never stored, because
    36// a name can be transferred and a stored answer would be a claim of its own.
    37//
    38// Anyone may still claim any path. That is the "open but tagged" choice, and it
    39// is made on purpose: gating registration on namespace ownership would mean
    40// that on day one, when almost nothing is registered by its own deployer, the
    41// registry is empty and teaches nobody anything. The defence against a
    42// misleading claim is that it renders as a stranger's claim, ranked below, and
    43// never as a fact.
    44package gnopm
    45
    46import (
    47 "strings"
    48
    49 "gno.land/p/nt/avl/v0"
    50)
    51
    52// Registry maps a package path to the claims made about its source.
    53//
    54// One path may carry several claims, at most one per claimant. That is not a
    55// flaw to be resolved: a fork is a legitimate second answer, and so is a third
    56// party filling in the map for a package whose author never will.
    57type Registry struct {
    58 packages *avl.Tree // package path -> *Package
    59 claims int
    60}
    61
    62// New returns an empty registry.
    63func New() *Registry {
    64 return &Registry{packages: avl.NewTree()}
    65}
    66
    67// Package is every claim made about one package path.
    68type Package struct {
    69 PkgPath string
    70 claims *avl.Tree // claimant address string -> *Claim
    71}
    72
    73// Claim is one address's statement about where a package came from.
    74//
    75// Every field except Claimant, Height and UpdatedAt is attacker-controlled.
    76// They are charset-validated at write time rather than escaped at read time, so
    77// that a consumer which forgets to escape is still safe: the fields cannot hold
    78// a byte that means anything to markdown, to a terminal or to a URL parser.
    79type Claim struct {
    80 PkgPath string
    81 Repo string // https:// URL of the repository
    82 Commit string // 40 or 64 lowercase hex
    83 Dir string // subdirectory holding the package, "" for the repo root
    84 Ref string // fully-qualified ref the commit was on, optional
    85 Claimant address
    86 Height int64 // block height of the first claim by this claimant
    87 // UpdatedAt is the height of the most recent write. Equal to Height until
    88 // the claimant re-registers, which is how a reader tells a claim that has
    89 // been kept current from one made once and abandoned.
    90 UpdatedAt int64
    91}
    92
    93// Register records or replaces claimant's claim about pkgPath.
    94//
    95// Re-registering the same path overwrites that claimant's own previous claim
    96// and nobody else's, so the ordinary "I deployed a new commit" flow is one call
    97// with no read first. Height is preserved across an update: it dates the claim,
    98// not the edit.
    99func (r *Registry) Register(claimant address, height int64, pkgPath, repo, commit, dir, ref string) (*Claim, error) {
    100 if !ValidPkgPath(pkgPath) {
    101 return nil, ErrInvalidPkgPath
    102 }
    103 if !ValidRepo(repo) {
    104 return nil, ErrInvalidRepo
    105 }
    106 if !ValidCommit(commit) {
    107 return nil, ErrInvalidCommit
    108 }
    109 if !ValidDir(dir) {
    110 return nil, ErrInvalidDir
    111 }
    112 if !ValidRef(ref) {
    113 return nil, ErrInvalidRef
    114 }
    115
    116 p := r.Package(pkgPath)
    117 if p == nil {
    118 p = &Package{PkgPath: pkgPath, claims: avl.NewTree()}
    119 r.packages.Set(pkgPath, p)
    120 }
    121
    122 key := claimant.String()
    123 c := &Claim{
    124 PkgPath: pkgPath,
    125 Repo: repo,
    126 Commit: commit,
    127 Dir: dir,
    128 Ref: ref,
    129 Claimant: claimant,
    130 Height: height,
    131 UpdatedAt: height,
    132 }
    133 if prev := p.Claim(claimant); prev != nil {
    134 c.Height = prev.Height
    135 } else {
    136 if p.claims.Size() >= MaxClaimants {
    137 return nil, ErrTooManyClaimants
    138 }
    139 r.claims++
    140 }
    141 p.claims.Set(key, c)
    142 return c, nil
    143}
    144
    145// Withdraw removes claimant's own claim about pkgPath. A claimant can always
    146// take back what they said; nobody can remove anyone else's.
    147func (r *Registry) Withdraw(claimant address, pkgPath string) error {
    148 p := r.Package(pkgPath)
    149 if p == nil {
    150 return ErrClaimNotFound
    151 }
    152 if _, removed := p.claims.Remove(claimant.String()); !removed {
    153 return ErrClaimNotFound
    154 }
    155 r.claims--
    156 if p.claims.Size() == 0 {
    157 r.packages.Remove(pkgPath)
    158 }
    159 return nil
    160}
    161
    162// Package returns every claim about a path, or nil.
    163func (r *Registry) Package(pkgPath string) *Package {
    164 v := r.packages.Get(pkgPath)
    165 if v == nil {
    166 return nil
    167 }
    168 return v.(*Package)
    169}
    170
    171// Size is the number of package paths carrying at least one claim.
    172func (r *Registry) Size() int { return r.packages.Size() }
    173
    174// Claims is the total number of claims across every path.
    175func (r *Registry) Claims() int { return r.claims }
    176
    177// IteratePackages walks paths in lexical order, which groups a namespace's
    178// packages together without needing a second index. The callback returns true
    179// to STOP, following avl's own convention rather than inverting it here.
    180func (r *Registry) IteratePackages(offset, count int, cb func(*Package) bool) {
    181 r.packages.IterateByOffset(offset, count, func(_ string, v any) bool {
    182 return cb(v.(*Package))
    183 })
    184}
    185
    186// IterateNamespace walks every claimed path under "<domain>/<kind>/<ns>/".
    187func (r *Registry) IterateNamespace(domain, kind, ns string, cb func(*Package) bool) {
    188 prefix := domain + "/" + kind + "/" + ns + "/"
    189 r.packages.Iterate(prefix, "", func(k string, v any) bool {
    190 if !strings.HasPrefix(k, prefix) {
    191 return true // past the prefix: stop
    192 }
    193 return cb(v.(*Package))
    194 })
    195}
    196
    197// Claim returns claimant's claim about this package, or nil.
    198func (p *Package) Claim(claimant address) *Claim {
    199 v := p.claims.Get(claimant.String())
    200 if v == nil {
    201 return nil
    202 }
    203 return v.(*Claim)
    204}
    205
    206// Count is how many addresses have claimed this package.
    207func (p *Package) Count() int { return p.claims.Size() }
    208
    209// IterateClaims walks the claims in claimant-address order. Order is not
    210// significance: a caller that wants the owner's claim first must ask OwnedBy,
    211// because the registry has no opinion about which claim is true.
    212func (p *Package) IterateClaims(cb func(*Claim) bool) {
    213 p.claims.Iterate("", "", func(_ string, v any) bool {
    214 return cb(v.(*Claim))
    215 })
    216}
    217
    218// OwnedBy reports whether c comes from the party that controls the package
    219// path's namespace: either the address whose own namespace it is, or whoever
    220// resolve says holds the registered name.
    221//
    222// resolve maps a namespace name to the address holding it and whether that name
    223// is held at all. It is a parameter rather than an import because this package
    224// stays pure: the realm supplies r/sys/users, and a test supplies a table.
    225//
    226// This is the one label in the whole design that is derived rather than
    227// declared, so it is computed here and never stored. A name can be transferred;
    228// a stored answer would age into a lie of exactly the kind this package exists
    229// to avoid.
    230func (c *Claim) OwnedBy(resolve func(name string) (address, bool)) bool {
    231 _, _, ns, _, ok := SplitPkgPath(c.PkgPath)
    232 if !ok {
    233 return false
    234 }
    235 if AddressNamespace(ns) {
    236 return ns == c.Claimant.String()
    237 }
    238 if resolve == nil {
    239 return false
    240 }
    241 addr, held := resolve(ns)
    242 return held && addr == c.Claimant
    243}
    244
    245// SourceURL builds a browsable link to the claimed source: the commit, plus the
    246// directory when the package is not at the repository root.
    247//
    248// GitHub's "/tree/<commit>/<dir>" layout is also GitLab's, Gitea's, Forgejo's
    249// and Codeberg's, so one construction covers every forge this registry is
    250// likely to meet. It is a convenience, not a promise the link resolves: the
    251// repository may be gone, private or renamed since the claim was made, which is
    252// itself something a verifier reports rather than something a realm can know.
    253func (c *Claim) SourceURL() string {
    254 u := strings.TrimSuffix(c.Repo, "/") + "/tree/" + c.Commit
    255 if c.Dir != "" {
    256 u += "/" + c.Dir
    257 }
    258 return u
    259}
    260