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

v0 source pure

Package zones is the content model of a curated registry of gno.land networks: what a zone is, what an endpoint on on...

Readme View source

gno.land/p/moul/zones/v0

The content model of a curated registry of gno.land networks: Zone, Endpoint, their validation, and the curation state machine, held by a Registry.

 1import "gno.land/p/moul/zones/v0"
 2
 3r := zones.NewRegistry()
 4must(r.Propose(proposer, height, "onyx", zones.Info{ChainID: "onyx-1", Title: "Onyx",
 5	Kind: zones.Testnet, RPCURL: "https://rpc.onyx.testnets.gno.land"}))
 6z, _ := r.Zone("onyx")
 7must(r.ReviewZone("onyx", zones.Approved, z.Revision, curator, height, ""))
 8z, _ = r.Zone("onyx") // the approval bumped the revision
 9id, err := r.Register(proposer, height, "onyx", zones.Peer,
10	"g1x5mlj5ava0dw9vkf4j6admjlzswm6f06p44krn@seed-1.onyx.testnets.gno.land:26656", "gno core")
11must(err)
12e, _ := r.Endpoint(id) // a verification names the zone's revision and the endpoint's
13must(r.ReviewEndpoint(id, zones.Verified, z.Revision, e.Revision, curator, height, "answers onyx-1"))
14r.Endpoints(zones.EndpointFilter{Zone: "onyx", Kind: zones.Peer, Status: zones.Verified}) // that one peer

A zone is one network: chain id, title, description, kind (mainnet, testnet, devnet, local), its main RPC, gnoweb and genesis URLs. An endpoint is one way into a zone: rpc, gnoweb, seed, peer, indexer, faucet or explorer, unverified until a curator says otherwise.

The live registry is r/moul/zones, which holds one Registry and decides who may write to it.

It decides nothing about who may act, deliberately. Every write that records a decision takes the acting address and the height as arguments (the two removals take neither: who may remove is the holder's call). Whether that address is a curator, the proposer, or nobody is the holding realm's call, so the same model can move under a DAO or a system realm later without a line changing here.

The curation policy

decision from reason also
approve pending, rejected, retired; approved, to restate the reason optional (required to restate)
reject pending; rejected, to restate the reason required verified endpoints go back to unverified (a restatement resets nothing)
retire approved; retired, to restate the reason required verified endpoints go back to unverified (a restatement resets nothing)
edit pending, approved none before review, required after a new chain id un-verifies endpoints; leaving local drops the private ones, at most MaxDropPerEdit (64) in one edit, refused while one carries a curator's ruling
remove pending, rejected endpoints go with it
verify an endpoint any, the same one only with a new reason optional zone pending or approved
unverify an endpoint any, the same one only with a new reason optional
flag an endpoint any, the same one only with a new reason required

Every endpoint verdict, and every removal of one endpoint, names the endpoint's own Revision, bumped on registration, on every verdict and on every reset, from the same never-repeating counter zones use: so it fails if another curator ruled on the endpoint since it was read (a verdict landing unseen would reverse theirs). A verification also names the zone's Revision, and fails if the zone changed: what it checks is that the endpoint answers for this zone's chain id. A flag or an unverify does not, so editing a zone cannot hold off a warning. The two are named apart, never combined, because a reader who takes them from two reads at two heights could otherwise combine stale ones into a valid one.

  • Every decision on a zone binds to the zone as read: its fields and its status. Every edit bumps the zone's Revision, registry-wide and never reused (not even by a zone removed and proposed again under the same slug). Approving, rejecting, retiring, editing and removing all name it, so an edit that lands between the reading and the decision makes the decision fail, instead of attaching a name to text nobody read. Every status change bumps it too, so an approval opened before a colleague's rejection fails rather than reversing it. Verifying an endpoint binds the same way, to the revision it was checked against: what was checked is that it answers for this zone's chain id. An edit that changes nothing is refused, so a revision only moves when something did. An endpoint's verdict is not part of the zone and does not move it: a flag on the zone's own main RPC shows on the zone, it does not invalidate an approval in flight.
  • Only a pending or approved zone is editable. An edit before review takes no reason. An edit to an approved zone is a curator decision: the reason is required and replaces the review on record. A changed chain id, on any zone, sends every verified endpoint back to unverified, because what was verified was that it answered for the old one. A reset caused by an edit to a pending zone by its own proposer records nobody, because that is not a review: ReviewedBy is empty and Reason says why. Every other reset records the editor or the reviewer who caused it.
  • Nothing goes back to pending. A rejected zone is approved after all, removed, or pushed out by 64 newer rejections.
  • A zone that was ever official is never removed by a caller. An approved one is retired, and a retired one is kept until 128 newer retirements push it out, so whoever still holds its chain id can find out what happened to it.

What a field accepts

Validated at write time, not escaped at render time, for everything that is also an index key, a URL segment or a config-file line:

  • Slug: 2 to 32 of [a-z0-9-], alphanumeric at both ends.
  • Chain id: 1 to 50 of [A-Za-z0-9._-].
  • URL: visible ASCII with none of <>"'`()[]{}|\^#, no credentials, no % without two hex digits after it, no &name; shape, a host that is a DNS name or an IPv4 address (anything a browser would read as IPv4, like 0x7f.1, must be a valid dotted quad), an optional port of 1 to 65535 with no leading zero, in digits, and a scheme from a short list. A zone's main RPC is http, https or tcp with no path, query or trailing slash, which is what gnokey -remote dials; an rpc endpoint also takes ws, wss and a path (a tcp:// one is host and port only, since gnokey dials it as such), and an indexer takes ws and wss for its subscriptions. A % never escapes a character that needs no escaping, and a path has no . or .. segment, so a URL has one spelling. A host that names a different machine for every reader is refused except on a local zone: a name with no dot, .localhost, .local, .internal, .localdomain, the RFC 6761 names .test, .example and .invalid, the never-delegated .lan, .home, .corp, .mail, .intranet, .private, .onion, .alt, the service-discovery, container and overlay names .consul, .lxd, .incus, .docker, .podman, .localnet, .svc and .i2p, every .arpa name (infrastructure, never a public service), and IPv4 "this network", loopback, private, link-local, CGNAT, IETF-protocol, documentation, benchmarking, 6to4 relay anycast, multicast and reserved ranges. IsPrivateHost fails closed: anything but a bare host is private to it. A zone that leaves the local kind drops every endpoint on one, in the same edit, and the edit is refused while one of them carries a curator's ruling. An IPv4 host has no terminal dot.
  • Peer: <node id>@<host>:<port>, the shape p2p.persistent_peers takes, the node id a lowercase g1 address (tm2 compares node ids byte for byte), and no terminal dot on an IPv4 host (Go's dialer cannot use one, so URLs refuse it too).
  • Address (proposer, registrant, reviewer): a valid g1 address in lowercase. bech32 also decodes the uppercase form, and here it would be a second identity.
  • Trimming: tabs and every Unicode space separator (Zs: U+0020, U+00A0, U+3000 and the like) are trimmed from what a caller types. Anything else at an edge (a line separator, U+0085) reaches the validator and is refused, not cut. A zone's gnoweb and genesis URLs, like an endpoint's, have an empty query's ? and a bare / dropped, as a browser's address bar writes them; the zone's main RPC, which gnokey dials, is refused with them. An endpoint is validated as it will be stored.
  • Free text (title, description, label, reason): one line, bounded in characters (so at most four times as many bytes), valid UTF-8, with no control character, no invisible, format, private-use or unassigned character, no variation selector except right after a character it modifies (FE0E or FE0F after a symbol or one of the five emoji whose base is punctuation or a letter by category (‼ ⁉ ℹ 〰 〽), as a phone writes ❤️ or ‼️; a Mongolian free variant; an ideographic variation sequence), no enclosing mark (it draws a badge's frame around any character), no run of more than four nonspacing marks, none of the status glyphs a Render draws nor their look-alikes, and, unless it is empty, something visible. Refused rather than stripped, so what is stored is what is shown. It must still be escaped by whoever renders it. Two consequences: the zero-width joiner and non-joiner are refused, so the Persian and Sinhala spellings and the emoji sequences that need them cannot be written; and "assigned" means in the chain's own Unicode tables, 15.0, so a character assigned since is refused on chain though gno test (which uses the host's tables) accepts it.

An endpoint, like a zone's URLs, is stored with its scheme and host lowercased (a peer lowercased whole, without its host's terminal dot; a URL without an empty query's ? or a bare /, which gnokey would dial), so every later comparison finds nothing to change, and deduplicated on Canonical: scheme and host lowercased, a terminal dot, a default port, an empty path before a query, an empty query's ? and a bare / dropped, %XX hex uppercased, an rpc tcp:// read as the http:// gnokey dials, anything meaningful after the host kept as typed. The same address under two kinds is two endpoints.

Bounds

The live registry, pending and approved zones together, holds at most 256. Rejected and retired zones are kept for the record but do not count against it: each state keeps at most 64 and 128, and the next one in drops the zone that has been in that state longest, endpoints and all. So no flood, no curator and no amount of time fills the registry for good.

The last ReservedForReviewers (16) places of each hard cap, the live registry and a zone's endpoints, take only the exempt calls: strangers who keep a cap full would otherwise lock out the curators who act under it.

Per-address caps make one address cheap to ignore: 4 pending proposals, 16 endpoints on a zone. Neither stops a flood from many addresses, so the review queue has an admission gate of its own: 64 pending zones in all, 64 endpoints per zone waiting for a verdict, under a hard 128 per zone. A flagged endpoint has its verdict and leaves the queue, so curators keep warnings instead of deleting them to make room. The gate is checked where something enters, so a review or a reset can push a count past it. A flood fills the queue and never crowds out an approved zone or a verified endpoint, and ProposeExempt and RegisterExempt skip the gate and the per-address caps (not the hard caps) for the reviewers the holding realm trusts, so a full queue never locks out the people who clear it. An endpoint registered that way is marked Exempt. Clearable is an endpoint nobody ruled on that is not Exempt, what a bulk clear of a flood may remove, and Registry.Clearable counts them without reading one; since the gate holds the queue, there are never more than 64. Each entry costs its sender a storage deposit, refunded to whoever signs the transaction that frees it (on a chain with transfers locked, to the storage fee collector instead), so a flooder who withdraws first gets it back: a bond, not a fee.

Storage

Records live in a B+ tree, the keyed, ordered container EFFECTIVE_GNO recommends for iteration and pagination (671 B per entry at fanout 32), with ids that are never reused. Every tree here, kit/index's included, is at fanout 32, not 128: a removal shifts every later value in its leaf, and each shifted value is rewritten, about 90k gas apiece for a number and 230k for a pointer on a real node, so a smaller leaf bounds what one removal costs. kit/store has that shape on an avl tree (2,029 B), and on an immutable path the choice is permanent. Three B+ trees hold an id per key: slug, the endpoint dedup key (a 128-bit hash of the canonical address rather than a second copy of it), and the order zones entered their state. Four hold a count per key: pending proposals per proposer, endpoints per registrant on a zone, flagged and clearable endpoints per zone. The other six are kit/index: status, approved-by-chain-id, and for endpoints zone, zone-and-kind, not-verified and on-a-private-host (so leaving local reads only those). Every one is written in the same method as its record; counts come from the indexes without reading a record, and a page reads the records on it only (the bucket's id list, at most 256 or 128 ids, is read whole). Measured on a node at the caps, with every text field at full length in the costliest characters and 240-character hosts: the heaviest page is a zone page of 25 flagged rows with full reasons, about 1.5B of the 3B query cap. A removal shifts the later records in its B+ tree leaf, and borrows from a neighbour when the leaf underflows; endpoint ids are handed out registry-wide, so what a removal of a zone's endpoints costs depends on how they interleave with other zones'. With them registered together, a rejection or retirement that evicts a full zone costs about 0.39B; in the worst layout a registrant can arrange, with the shifted neighbours at full size and flagged and the zone's verified endpoints spread across leaves, 2.35B (78% of a 3B block), the heaviest write (an earlier 2.20B layout was sent to a node capped at 3B and landed). That is also why an edit leaving local drops at most 64 (2.02B worst, with a chain-id change and both own URLs moved in the same edit).


Part of 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/p/moul/zones/v0 dependency graph

⚠️ Disclaimer: provided as-is, without warranty; not security-audited. Full disclaimer: DISCLAIMER.

Overview

Package zones is the content model of a curated registry of gno.land networks: what a zone is, what an endpoint on one is, which strings each field accepts, and the curation states both move through.

A zone is one network a person can point a node or a wallet at: mainnet, a testnet, a staging chain, somebody's gnodev. An endpoint is one way in: an RPC, a gnoweb, a seed or persistent peer, an indexer, a faucet, an explorer. Anybody may propose a zone, and register endpoints as the holding realm allows; a curator decides which zones are official and which endpoints are verified. Every decision is recorded with who made it and when; a rejection, a retirement, a flag and an edit to an approved zone also require a reason, which the public reads.

The package decides nothing about WHO may act. Every write that records a decision takes the acting address and the height as arguments (the two removals take neither), and the realm holding the Registry decides whether that address is a curator, the proposer, or nobody. That is the split that lets the same model move under a different authority later (a DAO, a system realm) without a line changing here.

Live registry: r/moul/zones.

Constants 7

const MaxZones, MaxPending, MaxPendingPerProposer, MaxRejected, MaxRetired, MaxEndpointsPerZone, MaxUnverifiedPerZone, MaxEndpointsPerAddress, MaxDropPerEdit, ReservedForReviewers

 1const (
 2	MaxZones               = 256 // pending and approved zones together
 3	MaxPending             = 64  // zones awaiting review, all proposers together
 4	MaxPendingPerProposer  = 4   // open proposals one address may have at once
 5	MaxRejected            = 64  // rejected zones kept for the record
 6	MaxRetired             = 128 // retired zones kept for the record
 7	MaxEndpointsPerZone    = 128
 8	MaxUnverifiedPerZone   = 64 // endpoints on one zone still waiting for a verdict (flagged ones have theirs)
 9	MaxEndpointsPerAddress = 16 // endpoints one address may register on one zone
10	// MaxDropPerEdit bounds how many endpoints one edit off the local kind
11	// drops, and so its gas: a removal rewrites up to about 45 later values in
12	// two B+ tree leaves, and a caller can lay ids out so every removal does,
13	// which at 128 measured 2.6B, close to a 3B block, and at 64 measures
14	// 2.02B even with a chain-id reset spread across leaves and both own URLs
15	// moved in the same edit. More waits for removals.
16	MaxDropPerEdit = 64
17	// ReservedForReviewers is how much of each hard cap (MaxZones live,
18	// MaxEndpointsPerZone) only the exempt calls may use: strangers who keep
19	// a cap full refill it the block after a curator clears it, and a cap a
20	// curator cannot reach is one they cannot act under.
21	ReservedForReviewers = 16
22)
source

Bounds on what the registry holds.

The LIVE registry, pending and approved zones, has a hard cap. Rejected and retired zones are kept for the record but do not count against it: each has a cap of its own, and when it is reached the zone that has been in that state longest is dropped to make room (by when it entered the state, not when it was proposed, so a long-lived network retired today is not the next to go). So nothing a proposer or a curator does, and no amount of time, can fill the registry for good: a cap that only ever fills is a lifetime cap, and on an immutable path that is a brick.

The per-address caps make one address cheap to ignore. Neither stops a flood from many addresses, because an address is not an identity, so admission to the review queue has a gate of its own (MaxPending zones, MaxUnverifiedPerZone endpoints awaiting a verdict), well under the hard caps: a flood fills the queue and never crowds out an approved zone or a verified endpoint. A flagged endpoint has its verdict and leaves the queue, so curators keep a warning instead of having to delete it to make room. The gate is checked where something enters (Propose, Register); a review or a reset can push a count past it, and never fails for it. ProposeExempt and RegisterExempt skip it, and the per-address caps, for the reviewers a holder trusts: a full queue must not lock out the people who clear it. The queue clears through approval, verification, a flag, rejection, RemoveZone, RemoveEndpoint, an edit leaving the local kind, and eviction. Flagged endpoints still count toward MaxEndpointsPerZone: a flood a curator flags rather than removes can fill the places a gated registration may use (all but the last ReservedForReviewers), and removing it is the answer. Each entry costs its sender a storage deposit, refunded to whoever signs the transaction that frees it (on a chain where ugnot is not transfer-locked; on one that is, the refund goes to the storage fee collector), so a flooder who withdraws first gets it back: a bond, not a fee.

const ChainIDChanged, ZoneRetired, ZoneRejected

1const (
2	ChainIDChanged = "the zone's chain id changed; verify again"
3	ZoneRetired    = "the zone was retired; verify again if it returns"
4	ZoneRejected   = "the zone was rejected; verify again if it is approved"
5)
source

The reasons an endpoint carries when its zone changed under it and sent it back to unverified. A reset caused by an edit to a pending zone by its own proposer records nobody, because that is not a review: ReviewedBy is empty and Reason says why. Every other reset (a rejection, a retirement, anybody else's edit, any edit to an approved zone) records whoever caused it.

const MinSlugLen, MaxSlugLen, MaxChainIDLen, MaxTitleLen, MaxDescriptionLen, MaxURLLen, MaxLabelLen, MaxReasonLen

 1const (
 2	MinSlugLen        = 2
 3	MaxSlugLen        = 32
 4	MaxChainIDLen     = 50 // tm2's own limit on a chain id
 5	MaxTitleLen       = 64
 6	MaxDescriptionLen = 512
 7	MaxURLLen         = 256
 8	MaxLabelLen       = 64
 9	MaxReasonLen      = 280
10)
source

Bounds on every caller-supplied string. A bound rather than none: every stored byte locks a storage deposit, and an unbounded field is a bill a stranger chooses the size of.

const RPC, Gnoweb, Seed, Peer, Indexer, Faucet, Explorer

1const (
2	RPC      EndpointKind = "rpc"      // a tm2 JSON-RPC: http(s) and tcp for gnokey, ws(s) for a subscriber
3	Gnoweb   EndpointKind = "gnoweb"   // a gnoweb frontend
4	Seed     EndpointKind = "seed"     // a p2p seed, for p2p.seeds
5	Peer     EndpointKind = "peer"     // a p2p node, for p2p.persistent_peers
6	Indexer  EndpointKind = "indexer"  // a tx-indexer GraphQL endpoint
7	Faucet   EndpointKind = "faucet"   // a faucet page or API
8	Explorer EndpointKind = "explorer" // a block explorer
9)
source

const Pending, Approved, Rejected, Retired

 1const (
 2	// Pending is a proposal nobody has reviewed yet. Every zone starts here.
 3	Pending Status = "pending"
 4	// Approved is an official zone: the one state a reader should trust.
 5	Approved Status = "approved"
 6	// Rejected is a proposal a curator turned down, with the reason kept.
 7	Rejected Status = "rejected"
 8	// Retired is a zone that was official and no longer runs. It stays
 9	// listed, because a node operator holding its chain id deserves to find
10	// out why nothing answers, until MaxRetired newer retirements push it out.
11	Retired Status = "retired"
12)
source

const Unverified, Verified, Flagged

1const (
2	// Unverified is every endpoint until a curator looks at it. Listed, and
3	// labelled as such, never hidden: an unverified RPC is still an RPC.
4	Unverified Verification = "unverified"
5	// Verified means a curator checked it answers for this zone.
6	Verified Verification = "verified"
7	// Flagged means a curator says do not use it, and says why.
8	Flagged Verification = "flagged"
9)
source

Functions 22

func Canonical

1func Canonical(kind EndpointKind, addr string) string
source

Canonical is the form two addresses are compared in, so one endpoint cannot be listed twice under two spellings: the scheme and the host lowercased (both are case-insensitive), the host's terminal dot dropped, the scheme's default port dropped (:443 for https and wss, :80 for http and ws), an empty path before a query dropped, an empty query's "?" and a bare "/" dropped, the hex digits of every %XX escape uppercased, and for an rpc endpoint tcp:// read as http://, which is how gnokey dials it. A path or a query that says something is kept as typed: those are case-sensitive and name different resources. A peer is lowercased whole and loses its host's terminal dot: its node id is lowercase bech32 and its host is a name.

func HasVisible

1func HasVisible(s string) bool
source

HasVisible reports whether s has at least one character a reader can see: not a space, not a combining mark, not an invisible format character, not a blank filler. A required title or reason must, or it passes the "needs a reason" check and renders blank.

func HostOf

1func HostOf(kind EndpointKind, addr string) string
source

HostOf returns the host of an endpoint address: the part between :// and the port or path of a URL, or between @ and the port of a peer. It assumes an address that already passed validation.

func IsPrivateHost

1func IsPrivateHost(host string) bool
source

IsPrivateHost reports whether host names a machine that is different for every reader, or no machine at all:

  • a name with no dot (resolved through the reader's own search domain), or one under a suffix reserved for local or private use, or that public DNS does not delegate: .localhost, .local (mDNS), .internal, .localdomain, .test, .example, .invalid, .lan, .home, .corp, .mail, .intranet, .private, .alt, .onion and .i2p, the service-discovery and container names .consul, .lxd, .incus, .docker, .podman, .localnet and .svc, and every .arpa name, which is infrastructure and never a public service (.home.arpa, ipv4only.arpa, default.service.arpa). Hosts files' localhost4, localhost6, ip6-localhost and ip6-loopback have no dot and fall under the first rule;
  • an IPv4 address in a loopback, private, link-local, carrier-grade NAT, "this network", IETF-protocol, documentation, benchmarking, 6to4 relay anycast, multicast or reserved range;
  • anything else shaped like a number but not a valid dotted quad, so an outside caller is not told 127.1 is public (validation refuses it anyway).

It fails closed: anything but a bare host ([A-Za-z0-9.-] only) is private to it. It looks at the string only; a public name that resolves to a private address is beyond what a registry can know.

func IsReset

1func IsReset(e Endpoint) bool
source

IsReset reports whether an unverified endpoint is unverified only because its zone changed under it: its reason is one of the three above. A reset reaches only a verified endpoint, so it says nothing against the endpoint itself.

func TrimSpaces

1func TrimSpaces(s string) string
source

TrimSpaces trims spaces (Unicode Zs: U+0020, no-break space, U+3000 and the like) and tabs from both ends, and nothing else. strings.TrimSpace also strips line breaks and separators (CR, LF, U+0085, U+2028), which free text refuses: trimmed first, they would be accepted silently, changed. A line break a caller types reaches the validator and is refused there.

func ValidAddress

1func ValidAddress(a address) bool
source

ValidAddress reports whether a is a valid g1 address in the one spelling the chain uses for it: lowercase. bech32 also decodes an all-uppercase string, so IsValid accepts G1ABC…, but every comparison here (curators, proposers, registrants, node ids) is on the string, and an uppercase spelling of a real address would be a second identity for it.

func ValidateChainID

1func ValidateChainID(s string) error
source

ValidateChainID accepts what tm2 accepts for a chain id: 1 to 50 characters, here narrowed to [A-Za-z0-9._-] so it is safe raw in a table.

func ValidateEndpoint

1func ValidateEndpoint(kind EndpointKind, addr string) error
source

ValidateEndpoint checks an endpoint's address against the shape its kind needs: a URL for everything but a seed or a peer, which are id@host:port.

func ValidateInfo

1func ValidateInfo(in Info) error
source

ValidateInfo checks every field of a zone's description, and returns the first problem it finds.

func ValidateLabel

1func ValidateLabel(s string) error
source

ValidateLabel accepts an optional single line of up to 64 characters.

func ValidateReason

1func ValidateReason(s string) error
source

ValidateReason accepts an optional single line of up to 280 characters. Whether a reason is REQUIRED depends on the decision; see Registry.

func ValidateSlug

1func ValidateSlug(s string) error
source

ValidateSlug accepts 2 to 32 characters of [a-z0-9-], starting and ending with a letter or a digit.

Checked at write time rather than escaped at render time, deliberately: the slug is also an index key and a URL path segment, so one carrying a slash or a pipe would break the link and the table as well as the page.

func ParseEndpointKind

1func ParseEndpointKind(s string) (EndpointKind, error)
source

ParseEndpointKind reads an endpoint kind. "" is "any" to a filter.

func ParseKind

1func ParseKind(s string) (Kind, error)
source

ParseKind reads a zone kind. "" is the zero Kind, "any" to a filter.

func NewRegistry

1func NewRegistry() *Registry
source

NewRegistry returns an empty registry.

func ParseStatus

1func ParseStatus(s string) (Status, error)
source

ParseStatus reads a status from a caller's string. "" is the zero Status, which a filter reads as "any".

func Statuses

1func Statuses() []Status
source

Statuses, Kinds, EndpointKinds and Verifications list every value of each enum in display order, for a Render that wants one section per value.

func ParseVerification

1func ParseVerification(s string) (Verification, error)
source

ParseVerification reads an endpoint verdict. "" is "any" to a filter.

Types 10

type Endpoint

struct
 1type Endpoint struct {
 2	ID           int64
 3	Zone         string // the zone's slug
 4	Kind         EndpointKind
 5	Address      string // a URL, or id@host:port for a seed or a peer
 6	Label        string // who runs it, or what it is, in the registrant's words
 7	Registrant   address
 8	RegisteredAt int64
 9
10	Status     Verification
11	ReviewedBy address
12	ReviewedAt int64
13	Reason     string
14	// Revision is bumped, from the registry-wide counter zones use, when the
15	// endpoint is registered, on every verdict and on every reset. A verdict
16	// and a removal name it, so either fails on an endpoint that changed
17	// after it was read.
18	Revision int64
19	// Exempt marks an endpoint registered through RegisterExempt, by a
20	// reviewer the holder trusts: never clearable as a never-reviewed one,
21	// whoever is a reviewer later.
22	Exempt bool
23}
source

Endpoint is one way into a zone, as the registry holds it. Flat for the same reason as Zone.

Methods on Endpoint

func Clearable

method on Endpoint
1func (e Endpoint) Clearable() bool
source

Clearable reports whether nobody has ruled on the endpoint and it was not a reviewer's own registration: no verdict (every verdict names its reviewer), no reset (every reset leaves a reason), not Exempt. It is what a bulk clear of a flood may remove.

type EndpointFilter

struct
1type EndpointFilter struct {
2	Zone       string
3	Kind       EndpointKind
4	Status     Verification
5	Registrant address
6}
source

EndpointFilter selects endpoints. A zero field matches anything.

Methods on EndpointFilter

func Match

method on EndpointFilter
1func (f EndpointFilter) Match(e Endpoint) bool
source

Match reports whether e passes the filter.

type EndpointKind

ident
1type EndpointKind string
source

EndpointKind says what an endpoint is for, and therefore which address shape it accepts.

type Info

struct
1type Info struct {
2	ChainID     string // what a node's genesis and a signer's -chainid say
3	Title       string
4	Description string
5	Kind        Kind
6	GnowebURL   string // optional: a local chain may not run one
7	RPCURL      string // required: the one endpoint every tool needs
8	GenesisURL  string // optional: where to download genesis.json
9}
source

Info is everything a proposer describes about a zone. It is the part that can be edited; the slug, the status and the history cannot.

type Kind

ident
1type Kind string
source

Kind says what sort of network a zone is.

type Registry

struct
 1type Registry struct {
 2	// The two sequences live behind a pointer like every container here, so a
 3	// copied Registry value shares them: with them inline, r2 := *r1 would
 4	// share every zone but count revisions on its own, and hand out a revision
 5	// r1 had already used, which a stale decision would then pass.
 6	ids *sequences
 7
 8	zones      *table
 9	bySlug     *unique      // slug -> zone id
10	byStatus   *index.Index // status -> zone ids, so a page reads only its rows
11	entered    *unique      // status|entry sequence -> zone id: who has been in a state longest
12	byProposer *counter     // proposer -> how many PENDING zones they have
13	approved   *index.Index // chain id -> ids of APPROVED zones naming it
14
15	endpoints *table
16	byAddress *unique      // slug|kind|hash(canonical address) -> endpoint id
17	byZone    *index.Index // slug -> endpoint ids
18	byKind    *index.Index // slug|kind -> endpoint ids, so a page reads only its rows
19	byOwner   *counter     // slug|registrant -> how many endpoints they registered
20	unchecked *index.Index // slug -> ids of endpoints that are not Verified
21	flagged   *counter     // slug -> how many of those are Flagged
22	fresh     *counter     // slug -> how many endpoints are Clearable
23	private   *index.Index // slug -> ids of endpoints on a private host (a local zone's only)
24}
source

Registry holds the zones and their endpoints. The zero value is not usable: call NewRegistry. All its state is behind pointers, so a copy of the value is the same registry, not a second one sharing half of it.

Every write touches its record and all of that record's indexes in one method. The index writes cannot fail as written: every key is validated non-empty and every unique key is checked free before the first write. If a later change made one fail, the method returns the error and the holding realm's abort undoes the half-applied write; that abort, not this method, is the atomicity guarantee.

Methods on Registry

func ApprovedByChainID

method on Registry
1func (r *Registry) ApprovedByChainID(chainID string) []Zone
source

ApprovedByChainID returns the approved zones naming that chain id, in proposal order. Usually one, but chain ids are not unique across networks (every gnodev is "dev"), so it is a list. Only approved zones are indexed, so proposals naming a real chain id cost a reader of this nothing.

func Awaiting

method on Registry
1func (r *Registry) Awaiting(slug string) int
source

Awaiting returns how many of a zone's endpoints are waiting for a verdict, what MaxUnverifiedPerZone gates. No endpoint is read.

func Clearable

method on Registry
1func (r *Registry) Clearable(slug string) int
source

Clearable returns how many of a zone's endpoints are Clearable, what a bulk clear could remove. No endpoint is read.

func Count

method on Registry
1func (r *Registry) Count(slug string) (verified, total int)
source

Count returns how many endpoints a zone has, and how many of them are verified, from the index counts: no endpoint is read.

func Edit

method on Registry
1func (r *Registry) Edit(slug string, revision int64, in Info, by address, at int64, reason string) error
source

Edit replaces a zone's Info: every field but the slug and the status.

Only a pending or an approved zone can be edited. A rejected one is removed and proposed again, or approved first; a retired one is a record, and its retirement reason is the thing it is kept for.

Every edit bumps the zone's Revision and records who made it and when. On a pending zone that is all, and a reason is refused rather than silently dropped: nobody has reviewed anything yet. On an approved zone an edit is a curator decision of its own: the reviewed values changed, so the review on record is replaced by this one, with a reason required.

When the chain id changes, whatever the status, every endpoint verified against the old one goes back to unverified: what was verified was that it answered for a chain this zone no longer names. A reset caused by the proposer's own edit to a pending zone records no reviewer; any other records the editor. An edit that changes nothing is refused, and leaving the local kind removes every endpoint on a private or special-use host (IsPrivateHost), at most MaxDropPerEdit in one edit, and is refused while one of them carries a curator's ruling.

An edit names the revision it was written against, like an approval does, so two editors working from the same reading cannot silently undo each other.

func Endpoint

method on Registry
1func (r *Registry) Endpoint(id int64) (Endpoint, bool)
source

Endpoint returns a copy of the endpoint with that id.

func EndpointByAddress

method on Registry
1func (r *Registry) EndpointByAddress(slug string, kind EndpointKind, addr string) (Endpoint, bool)
source

EndpointByAddress returns a copy of the endpoint a zone lists under that kind and address, compared in Canonical form.

func EndpointCount

method on Registry
1func (r *Registry) EndpointCount(slug string, kind EndpointKind) int
source

EndpointCount returns how many endpoints of that kind a zone has; "" is every kind. No endpoint is read.

func EndpointPage

method on Registry
1func (r *Registry) EndpointPage(slug string, kind EndpointKind, page, size int) []Endpoint
source

EndpointPage returns page (1-based) of a zone's endpoints of that kind, "" for every kind, size per page, oldest first. Like Registry.ZonePage it reads the records on the page only, and the bucket's id list (at most MaxEndpointsPerZone ids) whole.

func Endpoints

method on Registry
1func (r *Registry) Endpoints(f EndpointFilter) []Endpoint
source

Endpoints returns copies of the endpoints passing f, oldest first. With a zone set it reads that zone's ids only (that kind's, with a kind set), so it costs at most MaxEndpointsPerZone records. Without one it reads every endpoint in the registry: bound it yourself.

func Len

method on Registry
1func (r *Registry) Len() int
source

Len returns how many zones the registry holds, whatever their status.

func Live

method on Registry
1func (r *Registry) Live() int
source

Live returns how many zones are pending or approved: what MaxZones bounds.

func OwnerCount

method on Registry
1func (r *Registry) OwnerCount(slug string, who address) int
source

OwnerCount returns how many endpoints that address registered on a zone. No endpoint is read.

func PrivateEndpoints

method on Registry
1func (r *Registry) PrivateEndpoints(slug string) []Endpoint
source

PrivateEndpoints returns a zone's endpoints on a private host, the ones leaving the local kind drops. Only those are read.

func Propose

method on Registry
1func (r *Registry) Propose(by address, at int64, slug string, in Info) error
source

Propose files a new zone, Pending, under a slug nobody holds.

func ProposeExempt

method on Registry
1func (r *Registry) ProposeExempt(by address, at int64, slug string, in Info) error
source

ProposeExempt is Propose without the admission gates (MaxPending and MaxPendingPerProposer), for the reviewers a holding realm trusts: a flood that fills the queue would otherwise lock out the people who clear it. It also takes the last ReservedForReviewers places of the live registry, which Propose may not; MaxZones itself still holds.

func Register

method on Registry
1func (r *Registry) Register(by address, at int64, slug string, kind EndpointKind, addr, label string) (int64, error)
source

Register adds an endpoint to a zone, Unverified, and returns its id.

A zone takes endpoints while it is pending or approved, and a rejected or retired zone takes none. Who may register on a pending zone is the holding realm's call; a URL is stored with its scheme and host lowercased and without an empty tail (trimEmptyTail), a peer lowercased whole without its host's terminal dot, so what is listed is what dials (Canonical, which also drops default ports and reads tcp as http, is what they are compared in, not what is stored).

func RegisterExempt

method on Registry
1func (r *Registry) RegisterExempt(by address, at int64, slug string, kind EndpointKind, addr, label string) (int64, error)
source

RegisterExempt is Register without the admission gates (MaxUnverifiedPerZone and MaxEndpointsPerAddress), for the reviewers a holding realm trusts, as ProposeExempt is, and also takes a zone's last ReservedForReviewers places; the hard cap, MaxEndpointsPerZone, still holds.

func RemoveEndpoint

method on Registry
1func (r *Registry) RemoveEndpoint(id, revision int64) error
source

RemoveEndpoint deletes an endpoint, freeing its storage deposit to whoever signs the removal. revision is the endpoint's Revision as read: a removal fails if a verdict landed since, rather than delete one nobody saw. Who may is the holding realm's call.

func RemoveZone

method on Registry
1func (r *Registry) RemoveZone(slug string, revision int64) error
source

RemoveZone deletes a pending or rejected zone and every endpoint registered on it, freeing their storage deposit to whoever signs the removal. It names the revision it was decided on, so a removal meant for one proposal cannot land on another proposed again under the same slug. A zone that has ever been official cannot be removed this way: an approved one is retired, and a retired one is kept until MaxRetired newer retirements push it out, so whoever still holds its chain id can find out what happened to it.

func ReviewEndpoint

method on Registry
1func (r *Registry) ReviewEndpoint(id int64, to Verification, zoneRevision, revision int64, by address, at int64, reason string) error
source

ReviewEndpoint records a curator's verdict on an endpoint. Any verdict may follow any other, and the same one again with a new reason (so a typo is corrected without passing through a state its registrant may remove it from), with these rules:

  • every verdict names the endpoint's Revision as read, and fails if the endpoint changed since: a verdict landing after another curator's, unseen, would silently reverse it;
  • flagging needs a reason, because "do not use this" with no why is not something an operator can act on;
  • a verification also names the zone's Revision it was checked against, and fails if the zone changed since: what is verified is that the endpoint answers for THIS zone's chain id, and a proposer could otherwise switch it while the verdict is in flight. A flag or an unverify does not, so an edit to the zone cannot hold off a warning;
  • only an endpoint of a pending or approved zone can be verified.

func ReviewZone

method on Registry
1func (r *Registry) ReviewZone(slug string, to Status, revision int64, by address, at int64, reason string) error
source

ReviewZone records a curator's decision on a zone.

The transitions are the curation policy, and they are deliberately few:

Example
1approve  from pending, rejected or retired   reason optional
2reject   from pending                        reason REQUIRED
3retire   from approved                       reason REQUIRED

and a decision restated: the zone's present status again, with a new visible reason, which records the new reviewer and bumps the revision and does nothing else (no new entry in the state, no eviction, no reset). It is the only way to correct a reason.

Every decision names the zone's Revision the curator read, and fails if the zone changed since: otherwise a proposer's edit landing just before an approval would become official under the curator's name, and a rejection's public reason would describe text the curator never saw.

Rejecting or retiring sends every verified endpoint back to unverified: a rejected zone was never vouched for, and a retired network's peers may be somebody else's hosts by the time anyone reads them. Each state keeps at most MaxRejected or MaxRetired zones; the next one in drops the zone that entered that state first, with its endpoints. Every transition bumps the zone's Revision, so a decision prepared against the old status fails.

Nothing goes back to pending: a rejected zone is approved after all, removed, or pushed out by newer rejections. An official zone is never rejected after the fact, it is retired, so the record of it having been official survives.

func Revision

method on Registry
1func (r *Registry) Revision() int64
source

Revision is the last revision handed out, to a zone or an endpoint. Anything that changes after a read gets a later one.

func SoleApproved

method on Registry
1func (r *Registry) SoleApproved(chainID string) (Zone, bool)
source

SoleApproved returns the approved zone naming that chain id when there is exactly one, which is the only case a reader can be pointed at it without a guess. It counts first and reads one record at most.

func Zone

method on Registry
1func (r *Registry) Zone(slug string) (Zone, bool)
source

Zone returns a copy of the zone under slug.

func ZoneCount

method on Registry
1func (r *Registry) ZoneCount(status Status) int
source

ZoneCount returns how many zones have that status ("" for all), without reading any.

func ZonePage

method on Registry
1func (r *Registry) ZonePage(status Status, page, size int) []Zone
source

ZonePage returns page (1-based) of the zones with that status ("" for every status), size per page, in proposal order. A page past the end is empty.

It reads the zones ON the page only; the status's id list (one object, at most MaxZones ids) is read whole and sliced.

func Zones

method on Registry
1func (r *Registry) Zones(f ZoneFilter) []Zone
source

Zones returns copies of the zones passing f, in the order they were proposed. With a status set it reads that status's zones only.

The result, like Endpoints', is capped at its length. A slice handed to another realm is readonly there: an append that fits in spare capacity writes into it and aborts, one that does not copies and succeeds. Uncapped, an importer's append would pass or abort by how many rows the filter dropped, which strangers decide by registering.

type Status

ident
1type Status string
source

Status is where a zone stands in curation.

type Verification

ident
1type Verification string
source

Verification is a curator's verdict on an endpoint.

type Zone

struct
 1type Zone struct {
 2	Slug        string // the key: [a-z0-9-], stable, and what a URL carries
 3	ChainID     string
 4	Title       string
 5	Description string
 6	Kind        Kind
 7	GnowebURL   string
 8	RPCURL      string
 9	GenesisURL  string
10
11	Status     Status
12	Proposer   address
13	ProposedAt int64
14
15	// Revision changes on every edit of the zone's Info, on every status
16	// change and on every restated decision, and is never reused,
17	// not even by a zone removed and proposed again under the same slug. A
18	// curator acting on a zone names the revision they read, so an edit that
19	// lands between their reading and their decision makes the decision fail
20	// instead of attaching their name to text they never saw.
21	Revision int64
22	EditedBy address // who last edited the Info; empty if nobody has
23	EditedAt int64
24	Entered  int64 // when it entered its current status, in registry order: eviction goes oldest first
25
26	// The latest curator decision, empty until there is one. A curator's edit
27	// to an approved zone is a decision too, and replaces these.
28	ReviewedBy address
29	ReviewedAt int64
30	Reason     string
31}
source

Zone is a network, as the registry holds it.

Flat on purpose: every field is a scalar. A nested struct inside a persisted object is stored as an object of its own, and `gnokey query vm/qeval` prints it as an opaque ref(...) instead of its fields, so a reader asking a node for a zone would get the slug and nothing they came for. Zone.Info gives the editable part back as one value.

Methods on Zone

func Info

method on Zone
1func (z Zone) Info() Info
source

Info returns the part of the zone a proposer described.

func Reviewed

method on Zone
1func (z Zone) Reviewed() bool
source

Reviewed reports whether a curator has decided anything about the zone, including an edit made after its first review.

type ZoneFilter

struct
1type ZoneFilter struct {
2	Status Status
3	Kind   Kind
4}
source

ZoneFilter selects zones. A zero field matches anything.

Methods on ZoneFilter

func Match

method on ZoneFilter
1func (f ZoneFilter) Match(z Zone) bool
source

Match reports whether z passes the filter.

Imports 9

Source Files 6