genesis.gno
5.73 Kb · 163 lines
1package nsdata
2
3import "gno.land/p/nt/avl/v0"
4
5/*
6GENESIS — the state a registry is born with.
7
8WHY IT LIVES IN THE PACKAGE. This project has been through three chains
9(topaz-1, sapphire-1, pearl-1), and every time the price list, the
10referral tiers and the domain list existed only as somebody's memory of an
11afternoon spent in the admin panel. A value set by hand after deployment
12is a value that is lost with the chain. A value in this file ships with
13the realm.
14
15WHY nsdata AND NOT nslogic. This realm owns the state it writes, so init
16can write it with no permission from anybody. nslogic cannot: it writes
17through this realm's trust gate, and trust is granted by a timelocked
18proposal AFTER deployment — an init in nslogic would panic before the
19grant existed and take the deploy down with it.
20
21WHY DOMAINS CAN BE CREATED HERE AT ALL. They used to be tokens, and
22minting one needs nsnft to trust this realm, which it cannot yet do during
23init. Domains are records now (MIGRATION.md, rule 7), so creating one is a
24tree write and nothing more.
25
26WHAT IS NOT HERE. Names. No holder's records carry from one chain to the
27next — that is the owner's decision (see the mainnet checklist): testnet
28wallets get an entitlement, not their old records. And nothing that names
29a generation — the token URI base, the lab domain — because this file is
30meant to be the same for the sandbox and for mainnet.
31
32EVERY VALUE IS VALIDATED AND A BAD ONE PANICS. A genesis file that quietly
33skipped a malformed global would deploy a registry that looks configured
34and is missing its price list, and nothing would say so until somebody was
35charged the fallback. Failing the deploy costs one retry.
36
37The data itself is generated into genesis_data.gno by
38tools/freeze-genesis.py, from a snapshot of a chain that was configured by
39hand. Edit the chain, snapshot it, regenerate; do not edit the data file.
40*/
41
42// Genesis domains get a long runway. They are project-owned and all
43// created in the same block, so a single term would have every one of them
44// lapse on the same day a year later — and a lapsed domain refuses every
45// registration under it. Renewing them is still the admin's job; this
46// just stops the first renewal being a cliff.
47const genesisDomainTerms = 10
48
49/*
50refusedInGenesis names what may never be baked into a registry's birth.
51
52Anything whose VALUE is an address: a key that holds power — the free-tier
53granter was the one that shipped by accident — is set deliberately, after
54deploy, on the chain it belongs to, never inherited from whatever chain a
55snapshot was taken on. And the lab nomination, which belongs to a sandbox
56generation and would otherwise let a hand-edited data file ship a lab on
57a real domain.
58
59No regexp: one compiled pattern costs more storage than this whole file.
60*/
61func refusedInGenesis(key, value string) bool {
62 if key == "labdomain" || key == "granter" {
63 return true
64 }
65 return looksLikeAddress(value)
66}
67
68func looksLikeAddress(v string) bool {
69 if len(v) != 40 || v[0] != 'g' || v[1] != '1' {
70 return false
71 }
72 for i := 2; i < len(v); i++ {
73 c := v[i]
74 if !((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')) {
75 return false
76 }
77 }
78 return true
79}
80
81type genesisCfg struct {
82 termLength, gracePeriod int64
83 minLabelLen, maxLabelLen int
84 maxExtraKeyLen, maxExtraValueLen, maxExtraEntries int
85 maxDeletePerCall int
86}
87
88type genesisDomain struct {
89 label string
90 extra [][2]string
91}
92
93func applyGenesis(deployer address, now int64) {
94 // Limits first, so every extra written below is held to them.
95 c := genesisConfig()
96 if c.maxExtraKeyLen > 0 {
97 maxExtraKeyLen = c.maxExtraKeyLen
98 }
99 if c.maxExtraValueLen > 0 {
100 maxExtraValueLen = c.maxExtraValueLen
101 }
102 if c.maxExtraEntries > 0 {
103 maxExtraEntries = c.maxExtraEntries
104 }
105 if c.maxDeletePerCall > 0 {
106 maxDeletePerCall = c.maxDeletePerCall
107 }
108 if c.minLabelLen > 0 {
109 minLabelLen = c.minLabelLen
110 }
111 if c.maxLabelLen > 0 {
112 maxLabelLen = c.maxLabelLen
113 }
114 if c.termLength > 0 {
115 termLength = c.termLength
116 }
117 if c.gracePeriod > 0 {
118 gracePeriod = c.gracePeriod
119 }
120 // The one invariant the lifecycle setters guard, guarded here too:
121 // anchoring a renewal to its previous due date only makes sense while a
122 // renewal cannot land more than one term late.
123 if gracePeriod > termLength {
124 panic("nsdata genesis: grace period is longer than the term")
125 }
126
127 for _, kv := range genesisGlobals() {
128 for _, r := range reservedGlobals {
129 if kv[0] == r {
130 panic("nsdata genesis: " + r + " belongs to the restore machinery")
131 }
132 }
133 if refusedInGenesis(kv[0], kv[1]) {
134 panic("nsdata genesis: " + kv[0] + " holds an address or names a generation; set it after deploy, deliberately")
135 }
136 if err := setGlobal(kv[0], kv[1]); err != nil {
137 panic("nsdata genesis: global " + kv[0] + ": " + err.Error())
138 }
139 }
140
141 for _, g := range genesisDomains() {
142 if err := assertLabelKeySafe(g.label); err != nil {
143 panic("nsdata genesis: domain " + g.label + ": " + err.Error())
144 }
145 if len(g.label) < minLabelLen || len(g.label) > maxLabelLen {
146 panic("nsdata genesis: domain " + g.label + " is outside the label length limits")
147 }
148 if _, dup := getDomain(g.label); dup {
149 panic("nsdata genesis: domain " + g.label + " is listed twice")
150 }
151 // Owned by the deployer, like every domain the admin registers.
152 // Under `gno test` there is no deployer, and the domains are still
153 // created so the tests exercise the same registry a chain gets.
154 d := &Domain{owner: deployer, registered: now,
155 expires: now + genesisDomainTerms*termLength, extra: avl.Tree{}}
156 for _, kv := range g.extra {
157 if err := setExtra(&d.extra, kv[0], kv[1]); err != nil {
158 panic("nsdata genesis: domain " + g.label + " key " + kv[0] + ": " + err.Error())
159 }
160 }
161 domains.Set(g.label, d)
162 }
163}