Search Apps Documentation Source Content File Folder Download Copy Actions Download

block_store.gno

8.39 Kb · 332 lines
  1package block
  2
  3import "gno.land/p/akkadia/v0/ds/btree"
  4
  5const defaultSystemBlockLimit uint32 = 100000
  6
  7var blockStore = newBlockStore()
  8
  9// BlockStore owns block records and secondary indexes.
 10//
 11// Data shape:
 12// - records: blockID(uint32) -> query-string payload(string)
 13// - nameIndex: name(string) -> blockID(uint32)
 14// - metadatas: blockID(uint32) -> metadata(string)
 15// - acrRewardAllowlist: blockID(uint32) -> bool
 16//
 17// Responsibilities:
 18// - allocate normal block IDs and store canonical block records by block ID
 19// - keep block name indexes and ACR reward allowlists in sync with record mutations
 20// - protect store-owned invariants such as duplicate block IDs and names
 21// - return defensive copies from public read methods
 22//
 23// Non-responsibilities:
 24// - validate external parameter shape such as block name format, supply range, or pagination
 25// - parse user-facing encoded input such as CSV property payloads
 26// - perform auth, freeze, migration, payment, or event handling
 27// - interpret economics beyond store-owned existence and index consistency
 28type BlockStore struct {
 29	records            *btree.Uint32BTree
 30	nameIndex          *btree.StringBTree
 31	metadatas          *btree.Uint32BTree
 32	acrRewardAllowlist *btree.Uint32BTree
 33	systemBlockLimit   uint32
 34	maxSystemBlockID   uint32
 35	nextBlockID        uint32
 36}
 37
 38func newBlockStore() *BlockStore {
 39	return &BlockStore{
 40		records:            btree.NewUint32BTree(32),
 41		nameIndex:          btree.NewStringBTree(32),
 42		metadatas:          btree.NewUint32BTree(32),
 43		acrRewardAllowlist: btree.NewUint32BTree(32),
 44		systemBlockLimit:   defaultSystemBlockLimit,
 45		maxSystemBlockID:   0,
 46		nextBlockID:        defaultSystemBlockLimit,
 47	}
 48}
 49
 50// ==================== DANGER: MUTABLE MIGRATION CAPABILITIES ====================
 51//
 52// The getters in this section expose mutable store internals.
 53// Only authorized migration exporter paths may depend on these capabilities.
 54// Runtime reads and test setup must use copy-returning methods or total helpers.
 55func (s *BlockStore) Records() *btree.Uint32BTree {
 56	return s.records
 57}
 58
 59func (s *BlockStore) NameIndex() *btree.StringBTree {
 60	return s.nameIndex
 61}
 62
 63func (s *BlockStore) AcrRewardAllowlist() *btree.Uint32BTree {
 64	return s.acrRewardAllowlist
 65}
 66
 67func (s *BlockStore) Metadatas() *btree.Uint32BTree {
 68	return s.metadatas
 69}
 70
 71// ================= END DANGER: MUTABLE MIGRATION CAPABILITIES ==================
 72
 73func (s *BlockStore) Create(record map[string]string) uint32 {
 74	name := record["name"]
 75	if s.nameIndex.Has(name) {
 76		panic("name already exists: " + name)
 77	}
 78
 79	blockID := s.nextID()
 80	s.set(blockID, record)
 81	return blockID
 82}
 83
 84func (s *BlockStore) CreateSystem(blockID uint32, record map[string]string) {
 85	name := record["name"]
 86	blockIDStr := blockIDToString(blockID)
 87	if !s.IsSystemID(blockID) {
 88		panic("block ID must be less than 100000")
 89	}
 90	if _, found := s.records.Get(blockID); found {
 91		panic("block already exists: " + blockIDStr)
 92	}
 93	if s.nameIndex.Has(name) {
 94		panic("name already exists: " + name)
 95	}
 96
 97	if s.maxSystemBlockID < blockID {
 98		s.maxSystemBlockID = blockID
 99	}
100
101	s.set(blockID, record)
102}
103
104func (s *BlockStore) Delete(blockID uint32) {
105	block := s.MustGet(blockID)
106	s.nameIndex.Remove(block["name"])
107	s.records.Remove(blockID)
108	s.metadatas.Remove(blockID)
109	s.acrRewardAllowlist.Remove(blockID)
110}
111
112func (s *BlockStore) Update(blockID uint32, updates map[string]string) map[string]string {
113	current := s.mustGetDecoded(blockID)
114	oldName := current["name"]
115	newName := oldName
116	if name, found := updates["name"]; found {
117		newName = name
118	}
119	if oldName != newName {
120		if s.nameIndex.Has(newName) {
121			panic("name already exists: " + newName)
122		}
123	}
124
125	updated := copyStringMap(current)
126	for key, val := range updates {
127		updated[key] = val
128	}
129	updated["id"] = blockIDToString(blockID)
130
131	if oldName != newName {
132		s.nameIndex.Remove(oldName)
133		s.nameIndex.Set(newName, blockID)
134	}
135
136	s.records.Set(blockID, encodeStringMap(updated))
137	return copyStringMap(updated)
138}
139
140func (s *BlockStore) Has(blockID uint32) bool {
141	return s.records.Has(blockID)
142}
143
144func (s *BlockStore) Get(blockID uint32) (map[string]string, bool) {
145	blockVal, found := s.records.Get(blockID)
146	if !found {
147		return nil, false
148	}
149	return decodeStringMap(blockVal.(string)), true
150}
151
152func (s *BlockStore) MustGet(blockID uint32) map[string]string {
153	return s.mustGetDecoded(blockID)
154}
155
156func (s *BlockStore) mustGetDecoded(blockID uint32) map[string]string {
157	blockVal, found := s.records.Get(blockID)
158	if !found {
159		panic("block not found: " + blockIDToString(blockID))
160	}
161	return decodeStringMap(blockVal.(string))
162}
163
164func (s *BlockStore) mustHave(blockID uint32) {
165	if !s.records.Has(blockID) {
166		panic("block not found: " + blockIDToString(blockID))
167	}
168}
169
170func (s *BlockStore) MustGetByName(name string) map[string]string {
171	blockIDVal, found := s.nameIndex.Get(name)
172	if !found {
173		panic("block not found by name: " + name)
174	}
175	return s.MustGet(blockIDVal.(uint32))
176}
177
178func (s *BlockStore) List(page, count int) []map[string]string {
179	result := []map[string]string{}
180	offset := (page - 1) * count
181	s.records.IterateByOffset(offset, count, func(_ uint32, value interface{}) bool {
182		result = append(result, decodeStringMap(value.(string)))
183		return false
184	})
185	return result
186}
187
188func (s *BlockStore) ListFromTo(from, to *uint32, limit uint32) []map[string]string {
189	result := []map[string]string{}
190	if limit == 0 {
191		return result
192	}
193
194	if from != nil && to != nil && *from > *to {
195		panic("from is greater than to")
196	}
197
198	var end *uint32
199	if to != nil && *to < ^uint32(0) {
200		endValue := *to + 1
201		end = &endValue
202	}
203	s.records.Iterate(from, end, func(_ uint32, value any) bool {
204		result = append(result, decodeStringMap(value.(string)))
205		return len(result) >= int(limit)
206	})
207	return result
208}
209
210func (s *BlockStore) ListByIDs(blockIDs ...uint32) []map[string]string {
211	result := []map[string]string{}
212	for _, blockID := range blockIDs {
213		block, found := s.Get(blockID)
214		if found {
215			result = append(result, block)
216		}
217	}
218	return result
219}
220
221func (s *BlockStore) SetMetadata(blockID uint32, metadata string) {
222	s.mustHave(blockID)
223	s.metadatas.Set(blockID, metadata)
224}
225
226func (s *BlockStore) GetBlockMetadata(blockID uint32) string {
227	s.mustHave(blockID)
228	metadata, found := s.metadatas.Get(blockID)
229	if !found {
230		return ""
231	}
232	return metadata.(string)
233}
234
235func (s *BlockStore) ListBlockMetadataByIDs(blockIDs ...uint32) []string {
236	result := []string{}
237	for _, blockID := range blockIDs {
238		if !s.records.Has(blockID) {
239			continue
240		}
241		metadata, found := s.metadatas.Get(blockID)
242		if !found {
243			result = append(result, "")
244			continue
245		}
246		result = append(result, metadata.(string))
247	}
248	return result
249}
250
251func (s *BlockStore) Total() int {
252	return s.records.Size()
253}
254
255func (s *BlockStore) NameIndexTotal() int {
256	return s.nameIndex.Size()
257}
258
259func (s *BlockStore) SetAcrRewardEnabled(blockID uint32) {
260	if !s.Has(blockID) {
261		panic("block not found: " + blockIDToString(blockID))
262	}
263	s.acrRewardAllowlist.Set(blockID, true)
264}
265
266func (s *BlockStore) UnsetAcrRewardEnabled(blockID uint32) {
267	blockIDStr := blockIDToString(blockID)
268	if !s.acrRewardAllowlist.Has(blockID) {
269		panic("block not in acr reward allowlist: " + blockIDStr)
270	}
271	s.acrRewardAllowlist.Remove(blockID)
272}
273
274func (s *BlockStore) IsAcrRewardEnabled(blockID uint32) bool {
275	return s.acrRewardAllowlist.Has(blockID)
276}
277
278func (s *BlockStore) ListAcrRewardEnabled() []string {
279	result := []string{}
280	s.acrRewardAllowlist.Iterate(nil, nil, func(key uint32, value any) bool {
281		result = append(result, blockIDToString(key))
282		return false
283	})
284	return result
285}
286
287func (s *BlockStore) AcrRewardTotal() int {
288	return s.acrRewardAllowlist.Size()
289}
290
291func (s *BlockStore) SystemTotal() int {
292	result := 0
293	for i := uint32(0); i <= s.maxSystemBlockID; i++ {
294		if s.records.Has(i) {
295			result++
296		}
297	}
298	return result
299}
300
301func (s *BlockStore) NormalTotal() int {
302	return s.Total() - s.SystemTotal()
303}
304
305func (s *BlockStore) NextID() uint32 {
306	return s.nextBlockID
307}
308
309func (s *BlockStore) SystemBlockLimit() uint32 {
310	return s.systemBlockLimit
311}
312
313func (s *BlockStore) MaxSystemBlockID() uint32 {
314	return s.maxSystemBlockID
315}
316
317func (s *BlockStore) IsSystemID(blockID uint32) bool {
318	return blockID < s.systemBlockLimit
319}
320
321func (s *BlockStore) nextID() uint32 {
322	id := s.nextBlockID
323	s.nextBlockID++
324	return id
325}
326
327func (s *BlockStore) set(blockID uint32, record map[string]string) {
328	stored := copyStringMap(record)
329	stored["id"] = blockIDToString(blockID)
330	s.records.Set(blockID, encodeStringMap(stored))
331	s.nameIndex.Set(stored["name"], blockID)
332}