kv_store.gno
12.95 Kb · 411 lines
1package store
2
3import (
4 "errors"
5
6 bptree "gno.land/p/nt/bptree/v0"
7)
8
9// kvStore represents a domain-specific key-value storage
10// Each domain (pool, position, etc.) creates its own kvStore instance
11type kvStore struct {
12 data map[string]any // key -> value
13 authorizedCallers map[address]Permission
14 domainAddress address
15}
16
17// NewKVStore creates a new kvStore instance for a specific domain.
18// domainAddress is the address of the domain realm that owns this store.
19//
20// Parameters:
21// - domainAddress: Address of the owning domain realm; it is initially granted Write permission.
22//
23// Returns:
24// - KVStore: Empty domain-isolated store whose ACL initially authorizes domainAddress for writes.
25func NewKVStore(domainAddress address) KVStore {
26 return &kvStore{
27 data: make(map[string]any),
28 authorizedCallers: map[address]Permission{
29 domainAddress: Write,
30 },
31 domainAddress: domainAddress,
32 }
33}
34
35// GetDomainAddress returns the domain address.
36//
37// Returns:
38// - address: Address of the domain realm that owns this store.
39func (k *kvStore) GetDomainAddress() address {
40 return k.domainAddress
41}
42
43// GetAllKeys returns all keys stored in this kvStore.
44//
45// Returns:
46// - []string: Stored keys including the domain-address namespace prefix; map iteration order is unspecified.
47// - error: Always nil for this in-memory implementation.
48func (k *kvStore) GetAllKeys() ([]string, error) {
49 keys := make([]string, 0, len(k.data))
50
51 // Keys are namespace-prefixed (domainAddress:key) by design
52 for key := range k.data {
53 keys = append(keys, key)
54 }
55
56 return keys, nil
57}
58
59// Has checks if a key exists in the store.
60//
61// Parameters:
62// - key: Logical key to check; the store applies its domain namespace prefix before lookup.
63//
64// Returns:
65// - bool: true when the namespaced key is present, including when its stored value is nil; false otherwise.
66func (k *kvStore) Has(key string) bool {
67 _, exists := k.data[k.makeKey(key)]
68
69 return exists
70}
71
72// Get retrieves a value by key.
73// Reads are public within the package; only writes are gated by the ACL.
74// Realms exposing sensitive values must enforce their own read policy before
75// calling into the store.
76//
77// Parameters:
78// - key: Logical key whose namespaced entry should be retrieved.
79//
80// Returns:
81// - any: Stored value, including an explicitly stored nil, or nil when key is absent.
82// - error: nil when key exists; otherwise ErrKeyNotFound.
83func (k *kvStore) Get(key string) (any, error) {
84 value, exists := k.data[k.makeKey(key)]
85 if !exists {
86 return nil, errors.New(ErrKeyNotFound)
87 }
88
89 return value, nil
90}
91
92// GetInt64 retrieves a value by key and casts it to int64.
93// Returns ErrFailedCast if the value is not of type int64.
94//
95// Parameters:
96// - key: Logical key whose stored value must have dynamic type int64.
97//
98// Returns:
99// - int64: Stored int64 value, or 0 when the key is absent or has another type.
100// - error: nil on success; otherwise ErrKeyNotFound or ErrFailedCast.
101func (k *kvStore) GetInt64(key string) (int64, error) {
102 result, err := k.Get(key)
103 if err != nil {
104 return 0, err
105 }
106
107 return castToInt64(result)
108}
109
110// GetUint64 retrieves a value by key and casts it to uint64.
111// Returns ErrFailedCast if the value is not of type uint64.
112//
113// Parameters:
114// - key: Logical key whose stored value must have dynamic type uint64.
115//
116// Returns:
117// - uint64: Stored uint64 value, or 0 when the key is absent or has another type.
118// - error: nil on success; otherwise ErrKeyNotFound or ErrFailedCast.
119func (k *kvStore) GetUint64(key string) (uint64, error) {
120 result, err := k.Get(key)
121 if err != nil {
122 return 0, err
123 }
124
125 return castToUint64(result)
126}
127
128// GetBool retrieves a value by key and casts it to bool.
129// Returns ErrFailedCast if the value is not of type bool.
130//
131// Parameters:
132// - key: Logical key whose stored value must have dynamic type bool.
133//
134// Returns:
135// - bool: Stored bool value, or false when the key is absent or has another type.
136// - error: nil on success; otherwise ErrKeyNotFound or ErrFailedCast.
137func (k *kvStore) GetBool(key string) (bool, error) {
138 result, err := k.Get(key)
139 if err != nil {
140 return false, err
141 }
142
143 return castToBool(result)
144}
145
146// GetString retrieves a value by key and casts it to string.
147// Returns ErrFailedCast if the value is not of type string.
148//
149// Parameters:
150// - key: Logical key whose stored value must have dynamic type string.
151//
152// Returns:
153// - string: Stored string value, or the empty string when the key is absent or has another type.
154// - error: nil on success; otherwise ErrKeyNotFound or ErrFailedCast.
155func (k *kvStore) GetString(key string) (string, error) {
156 result, err := k.Get(key)
157 if err != nil {
158 return "", err
159 }
160
161 return castToString(result)
162}
163
164// GetAddress retrieves a value by key and casts it to address.
165// Returns ErrFailedCast if the value is not of type address.
166//
167// Parameters:
168// - key: Logical key whose stored value must have dynamic type address.
169//
170// Returns:
171// - address: Stored address value, or the empty address when the key is absent or has another type.
172// - error: nil on success; otherwise ErrKeyNotFound or ErrFailedCast.
173func (k *kvStore) GetAddress(key string) (address, error) {
174 result, err := k.Get(key)
175 if err != nil {
176 return address(""), err
177 }
178
179 return castToAddress(result)
180}
181
182// GetBPTree retrieves a value by key and casts it to *bptree.BPTree.
183// Returns ErrFailedCast if the value is not of type *bptree.BPTree.
184//
185// Parameters:
186// - key: Logical key whose stored value must have dynamic type *bptree.BPTree.
187//
188// Returns:
189// - *bptree.BPTree: Stored B+ tree pointer, or nil when the key is absent or has another type.
190// - error: nil on success; otherwise ErrKeyNotFound or ErrFailedCast.
191func (k *kvStore) GetBPTree(key string) (*bptree.BPTree, error) {
192 result, err := k.Get(key)
193 if err != nil {
194 return nil, err
195 }
196
197 return castToBPTree(result)
198}
199
200// Set stores a value with the given key.
201//
202// Parameters:
203// - _: Interrealm-call discriminator; callers pass 0.
204// - rlm: Propagated current realm context from the domain wrapper; this implementation validates rlm.IsCurrent() and uses rlm.Address() for code-caller write authorization.
205// - key: Logical key under which value is stored; the store adds its domain namespace prefix.
206// - value: Arbitrary value to store under key.
207//
208// Returns:
209// - error: nil when value is stored; otherwise ErrSpoofedRealm or ErrWritePermissionDenied.
210func (k *kvStore) Set(_ int, rlm realm, key string, value any) error {
211 if !rlm.IsCurrent() {
212 return errors.New(ErrSpoofedRealm)
213 }
214
215 if rlm.IsCode() && !k.IsWriteAuthorized(rlm.Address()) {
216 return errors.New(ErrWritePermissionDenied)
217 }
218
219 k.data[k.makeKey(key)] = value
220
221 return nil
222}
223
224// Delete removes a key from the store.
225//
226// Parameters:
227// - _: Interrealm-call discriminator; callers pass 0.
228// - rlm: Propagated current realm context from the domain wrapper; this implementation validates rlm.IsCurrent() and uses rlm.Address() for code-caller write authorization.
229// - key: Logical key whose namespaced entry should be removed.
230//
231// Returns:
232// - error: nil when key is removed; otherwise ErrSpoofedRealm, ErrWritePermissionDenied, or ErrKeyNotFound.
233func (k *kvStore) Delete(_ int, rlm realm, key string) error {
234 if !rlm.IsCurrent() {
235 return errors.New(ErrSpoofedRealm)
236 }
237
238 if rlm.IsCode() && !k.IsWriteAuthorized(rlm.Address()) {
239 return errors.New(ErrWritePermissionDenied)
240 }
241
242 if !k.Has(key) {
243 return errors.New(ErrKeyNotFound)
244 }
245
246 delete(k.data, k.makeKey(key))
247
248 return nil
249}
250
251// IsDomainAddress checks if the given address is the domain address.
252//
253// Parameters:
254// - addr: Address to compare with the store's owning domain address.
255//
256// Returns:
257// - bool: true when addr equals the owning domain address; false otherwise.
258func (k *kvStore) IsDomainAddress(addr address) bool {
259 return k.domainAddress == addr
260}
261
262// IsWriteAuthorized checks if the caller has write permission.
263//
264// Parameters:
265// - caller: Address whose write permission should be evaluated.
266//
267// Returns:
268// - bool: true for the domain address or a registered caller with Write permission; false otherwise.
269func (k *kvStore) IsWriteAuthorized(caller address) bool {
270 if k.IsDomainAddress(caller) {
271 return true
272 }
273
274 if !k.isRegisteredAuthorizedCaller(caller) {
275 return false
276 }
277
278 return k.authorizedCallers[caller] >= Write
279}
280
281// GetAuthorizedCallers returns a copy of the authorized callers map with their permissions.
282// A copy is returned so callers cannot mutate the ACL map directly.
283//
284// Returns:
285// - map[address]Permission: Newly allocated snapshot of registered caller permissions.
286// - error: nil when the ACL map exists; otherwise ErrAuthorizedCallerNotFound.
287func (k *kvStore) GetAuthorizedCallers() (map[address]Permission, error) {
288 if k.authorizedCallers == nil {
289 return make(map[address]Permission), errors.New(ErrAuthorizedCallerNotFound)
290 }
291
292 out := make(map[address]Permission, len(k.authorizedCallers))
293 for addr, perm := range k.authorizedCallers {
294 out[addr] = perm
295 }
296 return out, nil
297}
298
299// AddAuthorizedCaller adds a new authorized caller with the specified permission.
300//
301// Parameters:
302// - _: Interrealm-call discriminator; callers pass 0.
303// - rlm: Propagated current realm context from the domain wrapper; this implementation validates rlm.IsCurrent() and requires its address to be the owning domain for ACL updates.
304// - caller: Address to add to the write-authorization map.
305// - permission: Permission to assign; registration APIs accept only Write.
306//
307// Returns:
308// - error: nil when caller is added; otherwise ErrSpoofedRealm, ErrUpdatePermissionDenied, ErrAuthorizedCallerAlreadyRegistered, or ErrInvalidPermission.
309func (k *kvStore) AddAuthorizedCaller(_ int, rlm realm, caller address, permission Permission) error {
310 if !rlm.IsCurrent() {
311 return errors.New(ErrSpoofedRealm)
312 }
313
314 if !k.isUpdatableAuthorizedCaller(rlm.Address()) {
315 return errors.New(ErrUpdatePermissionDenied)
316 }
317
318 if k.isRegisteredAuthorizedCaller(caller) {
319 return errors.New(ErrAuthorizedCallerAlreadyRegistered)
320 }
321
322 if !isValidPermission(permission) {
323 return errors.New(ErrInvalidPermission)
324 }
325
326 k.authorizedCallers[caller] = permission
327
328 return nil
329}
330
331// UpdateAuthorizedCaller updates the permission of an existing authorized caller.
332//
333// Parameters:
334// - _: Interrealm-call discriminator; callers pass 0.
335// - rlm: Propagated current realm context from the domain wrapper; this implementation validates rlm.IsCurrent() and requires its address to be the owning domain for ACL updates.
336// - caller: Address of the already registered caller whose permission should change.
337// - permission: Replacement permission; registration APIs accept only Write.
338//
339// Returns:
340// - error: nil when caller's permission is updated; otherwise ErrSpoofedRealm, ErrUpdatePermissionDenied, ErrAuthorizedCallerNotFound, or ErrInvalidPermission.
341func (k *kvStore) UpdateAuthorizedCaller(_ int, rlm realm, caller address, permission Permission) error {
342 if !rlm.IsCurrent() {
343 return errors.New(ErrSpoofedRealm)
344 }
345
346 if !k.isUpdatableAuthorizedCaller(rlm.Address()) {
347 return errors.New(ErrUpdatePermissionDenied)
348 }
349
350 if !k.isRegisteredAuthorizedCaller(caller) {
351 return errors.New(ErrAuthorizedCallerNotFound)
352 }
353
354 if !isValidPermission(permission) {
355 return errors.New(ErrInvalidPermission)
356 }
357
358 k.authorizedCallers[caller] = permission
359
360 return nil
361}
362
363// RemoveAuthorizedCaller removes an authorized caller.
364//
365// Parameters:
366// - _: Interrealm-call discriminator; callers pass 0.
367// - rlm: Propagated current realm context from the domain wrapper; this implementation validates rlm.IsCurrent() and requires its address to be the owning domain for ACL updates.
368// - caller: Address of the registered caller to remove.
369//
370// Returns:
371// - error: nil when caller is removed; otherwise ErrSpoofedRealm, ErrUpdatePermissionDenied, or ErrAuthorizedCallerNotFound.
372func (k *kvStore) RemoveAuthorizedCaller(_ int, rlm realm, caller address) error {
373 if !rlm.IsCurrent() {
374 return errors.New(ErrSpoofedRealm)
375 }
376
377 if !k.isUpdatableAuthorizedCaller(rlm.Address()) {
378 return errors.New(ErrUpdatePermissionDenied)
379 }
380
381 if !k.isRegisteredAuthorizedCaller(caller) {
382 return errors.New(ErrAuthorizedCallerNotFound)
383 }
384
385 delete(k.authorizedCallers, caller)
386
387 return nil
388}
389
390// isRegisteredAuthorizedCaller checks if a caller is registered
391func (k *kvStore) isRegisteredAuthorizedCaller(caller address) bool {
392 _, exists := k.authorizedCallers[caller]
393
394 return exists
395}
396
397// isUpdatableAuthorizedCaller checks if the current realm is the same as the domain address
398func (k *kvStore) isUpdatableAuthorizedCaller(currentRealmAddress address) bool {
399 return currentRealmAddress == k.domainAddress
400}
401
402// makeKey creates a prefixed key with the domain address to ensure isolation
403func (k *kvStore) makeKey(key string) string {
404 return string(k.domainAddress) + ":" + key
405}
406
407// isValidPermission ensures only Write is assignable via registration APIs.
408// The zero value is rejected so callers must spell out an explicit permission.
409func isValidPermission(permission Permission) bool {
410 return permission == Write
411}