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

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

export.gno

5.23 Kb · 142 lines
  1package nsdata
  2
  3import (
  4	"strings"
  5
  6	"gno.land/p/nt/avl/v0"
  7)
  8
  9// Snapshot support: the other half of the contingency plan.
 10//
 11// Governance protects against a stolen or lost key. This file protects
 12// against the case no key can fix — a VM-level corruption, a chain
 13// migration, a defect in this realm discovered too late — where the only
 14// way out is to reconstruct the registry somewhere else. That is
 15// possible only if the whole state can be read out reliably and
 16// repeatably, which needs pagination: an unpaginated listing of a
 17// registry with tens of thousands of names is one query that is too
 18// large to ever return, and a snapshot you cannot take is not a backup.
 19//
 20// Recovery shape, should it ever be needed:
 21//   1. Pause() — freeze the registry so it cannot drift from the snapshot.
 22//   2. Page through the exports here to reconstruct state off-chain.
 23//   3. Deploy a successor, import the snapshot.
 24//   4. SetSuccessor() on this realm so readers can follow the move.
 25//
 26// The exports are also just useful: an operator who takes a snapshot
 27// every week has an off-chain record independent of any single chain.
 28
 29const maxPage = 200
 30
 31// pageKeys walks a key span and returns up to `limit` keys after the
 32// `after` cursor, plus the cursor to pass next (empty when exhausted).
 33func pageKeys(t *avl.Tree, start, end, after string, limit int) (csv, next string) {
 34	if limit <= 0 || limit > maxPage {
 35		limit = maxPage
 36	}
 37	from := start
 38	if after != "" && after > from {
 39		from = after
 40	}
 41	var b strings.Builder
 42	n := 0
 43	hasMore := false
 44	last := ""
 45	t.Iterate(from, end, func(key string, v any) bool {
 46		if after != "" && key <= after {
 47			return false
 48		}
 49		if n >= limit {
 50			hasMore = true
 51			return true
 52		}
 53		if n > 0 {
 54			b.WriteString(",")
 55		}
 56		b.WriteString(key)
 57		last = key
 58		n++
 59		return false
 60	})
 61	if !hasMore {
 62		last = ""
 63	}
 64	return b.String(), last
 65}
 66
 67// ExportDomains pages through every domain label.
 68func ExportDomains(after string, limit int) (labels, next string) {
 69	return pageKeys(&domains, "", "", after, limit)
 70}
 71
 72// ExportNames pages through every name in the registry, as raw storage
 73// keys (`domain*label`). Pass an empty domainLabel to walk the whole
 74// tree, or a domain to walk just that domain's span.
 75func ExportNames(domainLabel, after string, limit int) (keys, next string) {
 76	if domainLabel == "" {
 77		return pageKeys(&names, "", "", after, limit)
 78	}
 79	start, end := domainRange(domainLabel)
 80	return pageKeys(&names, start, end, after, limit)
 81}
 82
 83// ExportPrimary pages through the reverse-resolution table as owner
 84// addresses; pair with GetPrimary to read each value.
 85func ExportPrimary(after string, limit int) (owners, next string) {
 86	return pageKeys(&primary, "", "", after, limit)
 87}
 88
 89// ExportGlobalKeys lists the package-level extension slot's keys; pair
 90// with GetGlobal for values.
 91func ExportGlobalKeys(after string, limit int) (keys, next string) {
 92	return pageKeys(&globals, "", "", after, limit)
 93}
 94
 95// ExportAccounts pages through the per-address store as raw keys
 96// `<address>:<field>`; pair with GetAccount for each value. Field names
 97// are comma-free by construction (see assertAccountKey).
 98func ExportAccounts(after string, limit int) (keys, next string) {
 99	return pageKeys(&accounts, "", "", after, limit)
100}
101
102// AccountCount is how many per-address fields exist, so a snapshot of
103// the store can be checked for completeness. A separate read rather than
104// a fifth value from Sizes, whose shape the snapshot tools already parse.
105func AccountCount() int { return accounts.Size() }
106
107// ExportDomainExtraKeys / ExportNameExtraKeys list a record's extension
108// keys so a snapshot can capture data this realm has no schema for.
109// Keys are comma-free by construction (see assertExtraKey), so a CSV
110// listing is unambiguous.
111func ExportDomainExtraKeys(label, after string, limit int) (keys, next string) {
112	d := mustGetDomain(label)
113	return pageKeys(&d.extra, "", "", after, limit)
114}
115
116// A previous holder's keys are not exported. A snapshot that captured them
117// would carry the seller's payment addresses into a restore as the buyer's.
118func ExportNameExtraKeys(label, domainLabel, after string, limit int) (keys, next string) {
119	n := mustGetName(label, domainLabel)
120	if !isFresh(n, nameTokenID(label, domainLabel)) {
121		return "", ""
122	}
123	return pageKeys(&n.extra, "", "", after, limit)
124}
125
126// KNOWN SNAPSHOT GAP, recorded rather than left to be discovered during
127// an actual recovery: GRC-721 OPERATOR APPROVALS live inside nsnft's
128// token state and are not enumerable
129// through any read this realm exposes. Every other piece of state is
130// reachable — record fields via GetDomainInfo/GetNameInfo, extension keys
131// via the Export*ExtraKeys pagers, reverse resolution via ExportPrimary,
132// globals via ExportGlobalKeys, per-address state via ExportAccounts —
133// but operator approvals are not, so a
134// reconstructed registry will silently lack them and holders would need
135// to re-grant. Treated as acceptable (an approval is a revocable
136// convenience, not ownership) but it must not be assumed complete.
137
138// Sizes let an operator verify a snapshot is complete rather than
139// trusting that pagination terminated correctly.
140func Sizes() (domainCount, nameCount, primaryCount, globalCount int) {
141	return domains.Size(), names.Size(), primary.Size(), globals.Size()
142}