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.

blocked.gno

4.62 Kb · 138 lines
  1package nslogic
  2
  3import (
  4	"strings"
  5
  6	"gno.land/r/nym-mnsrehearsal001/nsdata/v2"
  7)
  8
  9/*
 10THE BLOCK LIST: labels nobody may hold, at any price.
 11
 12This is NOT the reserved list, and confusing the two would undo both.
 13
 14	reserved (collections/reserved-labels.json, 232 labels) says
 15	"blocked from the free tier, buyable at list price like anything
 16	else". admin*meme being purchasable is deliberate: the harm it
 17	guards against is handing a phishing address out for nothing, not
 18	the existence of the name. That list is enforced by the issuer,
 19	which is the only place that decides what is free.
 20
 21	THIS list says "not available, full stop". It is the small set where
 22	the answer does not change when somebody offers money: names that
 23	impersonate the registry itself, and the registry's own names.
 24
 25WHY IT LIVES IN THE VAULT'S GLOBALS RATHER THAN IN THIS FILE.
 26
 27Baking it in would mean a logic deploy to block a word, and the words
 28worth blocking are exactly the ones discovered after launch — the label
 29somebody tried yesterday that nobody thought of. Globals are writable by
 30the admin, survive a logic swap because they belong to the vault, and
 31cost storage only for what is actually stored.
 32
 33Chunked because a global value is capped at 512 bytes. Eight chunks is
 34about four kilobytes, or several hundred labels, which is far more than
 35this list should ever need — if it grows past that, the thing being
 36built is a content policy and it belongs off chain where it can be
 37updated without a transaction.
 38
 39FAILS OPEN, and that is the uncomfortable choice, so it is written down.
 40An unreadable chunk blocks nothing rather than blocking everything.
 41Failing closed here would mean one malformed global takes the whole
 42registry offline — every registration, for everyone, until an admin
 43notices. The harm from a blocked label slipping through is one bad name
 44that an admin can freeze; the harm from failing closed is total. The
 45list is also not the only defence: the issuer holds the reserved list,
 46and FreezeName exists for anything that gets past both.
 47*/
 48
 49const (
 50	blockChunks   = 8
 51	blockKeyStem  = "block"
 52	blockChunkMax = 512
 53)
 54
 55// blockKey is the global name for one chunk: block0 … block7.
 56func blockKey(i int) string {
 57	return blockKeyStem + string(rune('0'+i))
 58}
 59
 60/*
 61BlockedLabel reports whether a label may not be registered at all.
 62
 63Compares against the label as the registry stores it — lowercase, already
 64validated — so a caller cannot slip past with different casing. Every
 65comparison is exact: this list holds whole labels, not substrings,
 66because "contains" matching turns a list of a dozen words into an
 67unpredictable filter that refuses names nobody meant to refuse.
 68*/
 69func BlockedLabel(label string) bool {
 70	if label == "" {
 71		return false
 72	}
 73	want := strings.ToLower(label)
 74	for i := 0; i < blockChunks; i++ {
 75		v := nsdata.GetGlobal(blockKey(i))
 76		if v == "" {
 77			continue
 78		}
 79		for _, e := range strings.Split(v, ",") {
 80			if strings.ToLower(strings.TrimSpace(e)) == want {
 81				return true
 82			}
 83		}
 84	}
 85	return false
 86}
 87
 88// BlockList returns one chunk, so the panel can show and edit what is
 89// actually stored rather than what somebody believes is stored.
 90func BlockList(i int) string {
 91	if i < 0 || i >= blockChunks {
 92		return ""
 93	}
 94	return nsdata.GetGlobal(blockKey(i))
 95}
 96
 97// BlockChunks is the number of chunks, so the panel does not hard-code a
 98// constant that this file might later change.
 99func BlockChunks() int { return blockChunks }
100
101/*
102SetBlockList replaces one chunk. Admin only.
103
104Replaces rather than appends: an append-only list has no way to correct a
105mistake, and a mistake here refuses somebody a name for reasons nobody
106can see. The panel reads the chunk, edits it, and writes it back whole.
107*/
108func SetBlockList(cur realm, i int, csv string) {
109	assertNoSend(cur)
110	assertIsAdmin(cur)
111	emitAdmin(cur, "SetBlockList", "chunk", fmtI(i), "csv", csv)
112	if i < 0 || i >= blockChunks {
113		panic("nslogic: chunk out of range")
114	}
115	if len(csv) > blockChunkMax {
116		panic("nslogic: that chunk is longer than a global may be")
117	}
118	// Normalised on the way in, so the comparison above never has to
119	// account for whatever spacing somebody typed into a form.
120	parts := strings.Split(csv, ",")
121	out := []string{}
122	for _, p := range parts {
123		p = strings.ToLower(strings.TrimSpace(p))
124		if p != "" {
125			out = append(out, p)
126		}
127	}
128	setGlobal(cur, blockKey(i), strings.Join(out, ","))
129}
130
131// assertNotBlocked is the gate both doors pass through — the paid
132// registration and the free grant. One function, called twice, because
133// a block that only covers one route is not a block.
134func assertNotBlocked(label string) {
135	if BlockedLabel(label) {
136		panic("nslogic: " + label + " is not available")
137	}
138}