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.

incident.gno

6.83 Kb · 194 lines
  1package nsdata
  2
  3import (
  4	"strconv"
  5	"time"
  6
  7	"chain"
  8)
  9
 10/*
 11THE INCIDENT SWITCHES: the undo for what a stolen admin key set instantly
 12(re-audit 2026-10-16, M-2).
 13
 14The admin's instant powers leave their marks in this realm — credits it
 15granted its own wallets, the claim window it opened, the granter's and the
 16keeper's seats — and those outlive its eviction. The incident runbook could
 17not undo them in time: every logic write passes the trust gate and the
 18pause, so while the logic was revoked and the registry paused nothing could
 19be disarmed, and the moment recovery unpaused, the thief's wallets redeemed
 20in the same block. So these are DIRECT WALLET CALLS on the vault, by the
 21governance roles, that work while paused and with every logic realm
 22revoked — which is exactly when they are needed — and that only ever take
 23something away:
 24
 25	Disarm           admin, recovery or guardian. Deletes the globals whose
 26	                 presence is a power (disarmKeys): the claim window, the
 27	                 granter and the keeper. Instant and idempotent, so the
 28	                 guardian bot may send it without reading first.
 29
 30	VoidGrantsSince  recovery. Everything a logic realm granted at or after
 31	                 `since`, up to this second, is void — nslogic reads its
 32	                 credits through GrantVoided. Windows only accumulate;
 33	                 none is ever lifted, so nothing voided comes back to
 34	                 life. Past maxGrantVoids the two with the smallest gap
 35	                 merge. The admin grants again what was legitimate.
 36
 37	SetQuarantine    recovery. Until `until`, nothing that gives a name away
 38	                 runs — nslogic: credit redemption, RegisterFor, free
 39	                 grants — whatever the claim window says, so the registry
 40	                 can be unpaused before the new admin has finished
 41	                 cleaning up. At most maxQuarantine ahead; recovery may
 42	                 shorten or lift it.
 43
 44THE REGISTRY STILL DOES NOT DECIDE. The key names below are the logic
 45realm's, like the restore machinery's own keys, and a logic realm must keep
 46"absent" the safe reading of each: no claim window is a closed one, no
 47granter and no keeper is nobody. What a void or a quarantine covers is the
 48logic realm's to say; the vault keeps the clocks and the roles.
 49*/
 50
 51// disarmKeys are the globals whose presence is a power and whose absence is
 52// the safe reading (nslogic: credits.gno, freetier.gno, keeper.gno).
 53var disarmKeys = []string{"claimend", "granter", "keeper"}
 54
 55const (
 56	maxGrantVoids = 8                       // void windows kept; the oldest two merge past it
 57	maxQuarantine = int64(30 * 24 * 60 * 60) // the furthest a quarantine may reach, in seconds
 58)
 59
 60var (
 61	// grantVoids are [from, to] windows, unix seconds inclusive, oldest
 62	// first: a grant whose time falls inside any of them is void.
 63	grantVoids [][2]int64
 64
 65	// quarantineUntil is the unix second giveaways may run again; 0 none.
 66	quarantineUntil int64
 67)
 68
 69func assertIsRecovery(cur realm) {
 70	if a := cur.Previous().Address(); a == "" || a != recovery {
 71		panic("nsdata: restricted to recovery")
 72	}
 73}
 74
 75// Disarm deletes the claim window, the granter and the keeper at once.
 76// Admin, recovery or guardian, as a direct wallet call; it works while the
 77// registry is paused and with every logic realm revoked.
 78func Disarm(cur realm) {
 79	assertCanPause(cur)
 80	had := ""
 81	for _, k := range disarmKeys {
 82		if globals.Get(k) != nil {
 83			globals.Remove(k)
 84			if had != "" {
 85				had += ","
 86			}
 87			had += k
 88		}
 89	}
 90	chain.Emit("Disarmed", "keys", had, "by", cur.Previous().Address().String())
 91}
 92
 93// VoidGrantsSince voids every grant made at or after `since`, up to now.
 94// Recovery only. `since` may be 0 (everything so far) and may not be in the
 95// future. The window joins the ones already in force; past maxGrantVoids
 96// the two oldest merge, which can only void more, never less.
 97func VoidGrantsSince(cur realm, since int64) {
 98	assertIsRecovery(cur)
 99	now := time.Now().Unix()
100	if since < 0 || since > now {
101		panic("nsdata: a void starts in the past, at 0 or later")
102	}
103	grantVoids = insertVoid(grantVoids, [2]int64{since, now})
104	for len(grantVoids) > maxGrantVoids {
105		// Merge the two windows with the smallest gap between them, so the
106		// fewest legitimate grants fall into the merged span (re-audit
107		// follow-up), and say what was merged.
108		best := 0
109		for i := 1; i+1 < len(grantVoids); i++ {
110			if grantVoids[i+1][0]-grantVoids[i][1] < grantVoids[best+1][0]-grantVoids[best][1] {
111				best = i
112			}
113		}
114		a, b := grantVoids[best], grantVoids[best+1]
115		m := [2]int64{a[0], a[1]}
116		if b[1] > m[1] {
117			m[1] = b[1]
118		}
119		chain.Emit("GrantVoidsMerged", "from", strconv.FormatInt(m[0], 10), "to", strconv.FormatInt(m[1], 10))
120		grantVoids = append(append(append([][2]int64{}, grantVoids[:best]...), m), grantVoids[best+2:]...)
121	}
122	chain.Emit("GrantsVoided", "from", strconv.FormatInt(since, 10), "to", strconv.FormatInt(now, 10),
123		"by", cur.Previous().Address().String())
124}
125
126// insertVoid keeps the windows sorted by start and folds overlapping ones
127// together, so "the gap between two windows" means what it says.
128func insertVoid(ws [][2]int64, w [2]int64) [][2]int64 {
129	out := [][2]int64{}
130	placed := false
131	for _, x := range ws {
132		if !placed && w[0] < x[0] {
133			out = append(out, w)
134			placed = true
135		}
136		out = append(out, x)
137	}
138	if !placed {
139		out = append(out, w)
140	}
141	merged := [][2]int64{}
142	for _, x := range out {
143		if n := len(merged); n > 0 && x[0] <= merged[n-1][1] {
144			if x[1] > merged[n-1][1] {
145				merged[n-1][1] = x[1]
146			}
147			continue
148		}
149		merged = append(merged, x)
150	}
151	return merged
152}
153
154// GrantVoided reports whether a grant made at unix second `at` falls inside
155// a void recovery declared.
156func GrantVoided(at int64) bool {
157	for _, w := range grantVoids {
158		if at >= w[0] && at <= w[1] {
159			return true
160		}
161	}
162	return false
163}
164
165// GrantVoids lists the void windows, "from-to" comma-separated, oldest first.
166func GrantVoids() string {
167	out := ""
168	for i, w := range grantVoids {
169		if i > 0 {
170			out += ","
171		}
172		out += strconv.FormatInt(w[0], 10) + "-" + strconv.FormatInt(w[1], 10)
173	}
174	return out
175}
176
177// SetQuarantine holds every giveaway shut until `until` (unix seconds), at
178// most maxQuarantine ahead; 0 lifts it. Recovery only.
179func SetQuarantine(cur realm, until int64) {
180	assertIsRecovery(cur)
181	now := time.Now().Unix()
182	if until != 0 && (until <= now || until > now+maxQuarantine) {
183		panic("nsdata: a quarantine ends in the future and at most " +
184			strconv.FormatInt(maxQuarantine/86400, 10) + " days ahead; 0 lifts it")
185	}
186	quarantineUntil = until
187	chain.Emit("QuarantineSet", "until", strconv.FormatInt(until, 10), "by", cur.Previous().Address().String())
188}
189
190// QuarantineUntil is the unix second giveaways may run again, 0 for none.
191func QuarantineUntil() int64 { return quarantineUntil }
192
193// Quarantined reports whether a quarantine holds right now.
194func Quarantined() bool { return time.Now().Unix() < quarantineUntil }