github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/store/rootmulti/store.go (about) 1 package rootmulti 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/gnolang/gno/tm2/pkg/amino" 8 abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" 9 "github.com/gnolang/gno/tm2/pkg/crypto/merkle" 10 "github.com/gnolang/gno/tm2/pkg/crypto/tmhash" 11 dbm "github.com/gnolang/gno/tm2/pkg/db" 12 "github.com/gnolang/gno/tm2/pkg/errors" 13 14 "github.com/gnolang/gno/tm2/pkg/store/cachemulti" 15 serrors "github.com/gnolang/gno/tm2/pkg/store/errors" 16 "github.com/gnolang/gno/tm2/pkg/store/immut" 17 "github.com/gnolang/gno/tm2/pkg/store/types" 18 ) 19 20 const ( 21 latestVersionKey = "s/latest" 22 commitInfoKeyFmt = "s/%d" // s/<version> 23 ) 24 25 // multiStore is composed of many CommitStores. Name contrasts with 26 // cacheMultiStore which is for cache-wrapping other MultiStores. It implements 27 // the CommitMultiStore interface. 28 type multiStore struct { 29 db dbm.DB 30 lastCommitID types.CommitID 31 storeOpts types.StoreOptions 32 storesParams map[types.StoreKey]storeParams 33 stores map[types.StoreKey]types.CommitStore 34 keysByName map[string]types.StoreKey 35 } 36 37 var ( 38 _ types.CommitMultiStore = (*multiStore)(nil) 39 _ types.Queryable = (*multiStore)(nil) 40 ) 41 42 func NewMultiStore(db dbm.DB) *multiStore { 43 return &multiStore{ 44 db: db, 45 storesParams: make(map[types.StoreKey]storeParams), 46 stores: make(map[types.StoreKey]types.CommitStore), 47 keysByName: make(map[string]types.StoreKey), 48 } 49 } 50 51 // Implements CommitMultiStore 52 func (ms *multiStore) GetStoreOptions() types.StoreOptions { 53 return ms.storeOpts 54 } 55 56 // Implements CommitMultiStore 57 func (ms *multiStore) SetStoreOptions(opts types.StoreOptions) { 58 ms.storeOpts = opts 59 for _, store := range ms.stores { 60 store.SetStoreOptions(opts) 61 } 62 } 63 64 // SetLazyLoad sets if the store should be loaded lazily or not 65 func (ms *multiStore) SetLazyLoad(lazyLoad bool) { 66 ms.storeOpts.LazyLoad = lazyLoad 67 } 68 69 // Implements CommitMultiStore. 70 func (ms *multiStore) MountStoreWithDB(key types.StoreKey, cons types.CommitStoreConstructor, db dbm.DB) { 71 if key == nil { 72 panic("MountIAVLStore() key cannot be nil") 73 } 74 if _, ok := ms.storesParams[key]; ok { 75 panic(fmt.Sprintf("Store duplicate store key %v", key)) 76 } 77 if _, ok := ms.keysByName[key.Name()]; ok { 78 panic(fmt.Sprintf("Store duplicate store key name %v", key)) 79 } 80 ms.storesParams[key] = storeParams{ 81 key: key, 82 constructor: cons, 83 db: db, 84 } 85 ms.keysByName[key.Name()] = key 86 } 87 88 // Implements CommitMultiStore. 89 func (ms *multiStore) GetCommitStore(key types.StoreKey) types.CommitStore { 90 return ms.stores[key] 91 } 92 93 // Implements CommitMultiStore. 94 func (ms *multiStore) LoadLatestVersion() error { 95 ver := getLatestVersion(ms.db) 96 return ms.LoadVersion(ver) 97 } 98 99 // Implements CommitMultiStore. 100 func (ms *multiStore) LoadVersion(ver int64) error { 101 if ver == 0 { 102 // Special logic for version 0 where there is no need to get commit 103 // information. 104 for key, storeParams := range ms.storesParams { 105 store, err := ms.constructStore(storeParams) 106 if err != nil { 107 return errors.New("failed to load Store: %v", err) 108 } 109 store.SetStoreOptions(ms.storeOpts) 110 err = store.LoadVersion(ver) 111 if err != nil { 112 return errors.New("failed to load Store version %d: %v", ver, err) 113 } 114 if !store.LastCommitID().IsZero() { 115 return errors.New("failed to load Store: non-empty CommitID for zero state") 116 } 117 ms.stores[key] = store 118 } 119 ms.lastCommitID = types.CommitID{} 120 return nil 121 } 122 123 // Load store commit infos @ version ver. 124 cInfo, err := getCommitInfo(ms.db, ver) 125 if err != nil { 126 return err 127 } 128 129 // Convert StoreInfos slice to map. 130 infos := make(map[types.StoreKey]storeInfo) 131 for _, storeInfo := range cInfo.StoreInfos { 132 infos[ms.nameToKey(storeInfo.Name)] = storeInfo 133 } 134 135 // Load each Store and check CommitID for each. 136 newStores := make(map[types.StoreKey]types.CommitStore) 137 for key, storeParams := range ms.storesParams { 138 var id types.CommitID 139 if info, ok := infos[key]; ok { 140 id = info.Core.CommitID 141 } 142 store, err := ms.constructStore(storeParams) 143 if err != nil { 144 return fmt.Errorf("failed to load Store: %w", err) 145 } 146 store.SetStoreOptions(ms.storeOpts) 147 err = store.LoadVersion(ver) 148 if err != nil { 149 return errors.New("failed to load Store version %d: %v", ver, err) 150 } 151 if !store.LastCommitID().Equals(id) { 152 return errors.New("failed to load Store: wrong commit id: %v vs %v", 153 store.LastCommitID(), 154 id) 155 } 156 newStores[key] = store 157 } 158 159 ms.lastCommitID = cInfo.CommitID() 160 ms.stores = newStores 161 162 return nil 163 } 164 165 // ---------------------------------------- 166 // +CommitStore 167 168 // Implements Committer/CommitStore. 169 func (ms *multiStore) LastCommitID() types.CommitID { 170 return ms.lastCommitID 171 } 172 173 // Implements Committer/CommitStore. 174 func (ms *multiStore) Commit() types.CommitID { 175 // Commit stores. 176 version := ms.lastCommitID.Version + 1 177 commitInfo := commitStores(version, ms.stores) 178 179 // Need to update atomically. 180 batch := ms.db.NewBatch() 181 defer batch.Close() 182 setCommitInfo(batch, version, commitInfo) 183 setLatestVersion(batch, version) 184 batch.Write() 185 186 // Prepare for next version. 187 commitID := types.CommitID{ 188 Version: version, 189 Hash: commitInfo.Hash(), 190 } 191 ms.lastCommitID = commitID 192 return commitID 193 } 194 195 // ---------------------------------------- 196 // +MultiStore 197 198 // Implements MultiStore. 199 func (ms *multiStore) MultiCacheWrap() types.MultiStore { 200 stores := make(map[types.StoreKey]types.Store) 201 for k, v := range ms.stores { 202 stores[k] = v 203 } 204 205 return cachemulti.New(stores, ms.keysByName) 206 } 207 208 // Implements MultiStore. 209 func (ms *multiStore) MultiWrite() { 210 panic("unexpected .MultiWrite() on rootmulti.Store. Commit()?") 211 } 212 213 // Implements CommitMultiStore. 214 func (ms *multiStore) MultiImmutableCacheWrapWithVersion(version int64) (types.MultiStore, error) { 215 ims := &multiStore{ 216 db: dbm.NewImmutableDB(ms.db), 217 storeOpts: ms.storeOpts, 218 storesParams: ms.storesParams, 219 keysByName: ms.keysByName, 220 } 221 ims.storeOpts.Immutable = true 222 err := ims.LoadVersion(version) 223 if err != nil { 224 return nil, err 225 } 226 stores := make(map[types.StoreKey]types.Store, len(ims.stores)) 227 for storeKey, store := range ims.stores { 228 stores[storeKey] = immut.New(store) 229 } 230 return cachemulti.New(stores, ims.keysByName), nil 231 } 232 233 // Implements MultiStore. 234 // If the store does not exist, panics. 235 func (ms *multiStore) GetStore(key types.StoreKey) types.Store { 236 store := ms.stores[key] 237 if store == nil { 238 panic("Could not load store " + key.String()) 239 } 240 return store 241 } 242 243 // Implements MultiStore 244 245 // getStoreByName will first convert the original name to 246 // a special key, before looking up the CommitStore. 247 // This is not exposed to the extensions (which will need the 248 // StoreKey), but is useful in main, and particularly app.Query, 249 // in order to convert human strings into CommitStores. 250 func (ms *multiStore) getStoreByName(name string) types.Store { 251 key := ms.keysByName[name] 252 if key == nil { 253 return nil 254 } 255 return ms.stores[key] 256 } 257 258 // ---------------------- Query ------------------ 259 260 // Query calls substore.Query with the same `req` where `req.Path` is 261 // modified to remove the substore prefix. 262 // Ie. `req.Path` here is `/<substore>/<path>`, and trimmed to `/<path>` for the substore. 263 // TODO: add proof for `multistore -> substore`. 264 func (ms *multiStore) Query(req abci.RequestQuery) (res abci.ResponseQuery) { 265 // Query just routes this to a substore. 266 path := req.Path 267 storeName, subpath, err := parsePath(path) 268 if err != nil { 269 res.Error = err 270 return 271 } 272 273 store := ms.getStoreByName(storeName) 274 if store == nil { 275 msg := fmt.Sprintf("no such store: %s", storeName) 276 res.Error = serrors.ErrUnknownRequest(msg) 277 return 278 } 279 280 queryable, ok := store.(types.Queryable) 281 if !ok { 282 msg := fmt.Sprintf("store %s doesn't support queries", storeName) 283 res.Error = serrors.ErrUnknownRequest(msg) 284 return 285 } 286 287 // trim the path and make the query 288 req.Path = subpath 289 res = queryable.Query(req) 290 291 if !req.Prove { 292 return res 293 } else if res.Proof == nil || len(res.Proof.Ops) == 0 { 294 res.Error = serrors.ErrInternal("proof is unexpectedly empty; ensure height has not been pruned") 295 return 296 } 297 298 commitInfo, errMsg := getCommitInfo(ms.db, res.Height) 299 if errMsg != nil { 300 res.Error = serrors.ErrInternal(errMsg.Error()) 301 return 302 } 303 304 // Restore origin path and append proof op. 305 res.Proof.Ops = append(res.Proof.Ops, NewMultiStoreProofOp( 306 []byte(storeName), 307 NewMultiStoreProof(commitInfo.StoreInfos), 308 ).ProofOp()) 309 310 // TODO: handle in another TM v0.26 update PR 311 // res.Proof = buildMultiStoreProof(res.Proof, storeName, commitInfo.StoreInfos) 312 return res 313 } 314 315 // parsePath expects a format like /<storeName>[/<subpath>] 316 // Must start with /, subpath may be empty 317 // Returns error if it doesn't start with / 318 func parsePath(path string) (storeName string, subpath string, err serrors.Error) { 319 if !strings.HasPrefix(path, "/") { 320 err = serrors.ErrUnknownRequest(fmt.Sprintf("invalid path: %s", path)) 321 return 322 } 323 324 paths := strings.SplitN(path[1:], "/", 2) 325 storeName = paths[0] 326 327 if len(paths) == 2 { 328 subpath = "/" + paths[1] 329 } 330 331 return 332 } 333 334 // ---------------------------------------- 335 336 func (ms *multiStore) constructStore(params storeParams) (store types.CommitStore, err error) { 337 var db dbm.DB 338 if params.db != nil { 339 db = dbm.NewPrefixDB(params.db, []byte("s/_/")) 340 } else { 341 db = dbm.NewPrefixDB(ms.db, []byte("s/k:"+params.key.Name()+"/")) 342 } 343 opts := ms.storeOpts 344 345 // XXX: use these: 346 // return iavl.LoadStore(db, id, ms.pruningOpts, ms.lazyLoading) 347 // return commitDBStoreAdapter{dbadapter.Store{db}}, nil 348 store = params.constructor(db, opts) 349 return store, nil 350 } 351 352 func (ms *multiStore) nameToKey(name string) types.StoreKey { 353 for key := range ms.storesParams { 354 if key.Name() == name { 355 return key 356 } 357 } 358 panic("Unknown name " + name) 359 } 360 361 // ---------------------------------------- 362 // storeParams 363 364 type storeParams struct { 365 key types.StoreKey 366 constructor types.CommitStoreConstructor 367 db dbm.DB 368 } 369 370 // ---------------------------------------- 371 // commitInfo 372 373 // NOTE: Keep commitInfo a simple immutable struct. 374 type commitInfo struct { 375 // Version 376 Version int64 377 378 // Store info for 379 StoreInfos []storeInfo 380 } 381 382 // Hash returns the simple merkle root hash of the stores sorted by name. 383 func (ci commitInfo) Hash() []byte { 384 // TODO: cache to ci.hash []byte 385 m := make(map[string][]byte, len(ci.StoreInfos)) 386 for _, storeInfo := range ci.StoreInfos { 387 m[storeInfo.Name] = storeInfo.Hash() 388 } 389 390 return merkle.SimpleHashFromMap(m) 391 } 392 393 func (ci commitInfo) CommitID() types.CommitID { 394 return types.CommitID{ 395 Version: ci.Version, 396 Hash: ci.Hash(), 397 } 398 } 399 400 // ---------------------------------------- 401 // storeInfo 402 403 // storeInfo contains the name and core reference for an 404 // underlying store. It is the leaf of the Stores top 405 // level simple merkle tree. 406 type storeInfo struct { 407 Name string 408 Core storeCore 409 } 410 411 type storeCore struct { 412 // StoreType StoreType 413 CommitID types.CommitID 414 // ... maybe add more state 415 } 416 417 // Implements merkle.Hasher. 418 func (si storeInfo) Hash() []byte { 419 // Doesn't write Name, since merkle.SimpleHashFromMap() will 420 // include them via the keys. 421 bz := si.Core.CommitID.Hash 422 hasher := tmhash.New() 423 424 _, err := hasher.Write(bz) 425 if err != nil { 426 // TODO: Handle with #870 427 panic(err) 428 } 429 430 return hasher.Sum(nil) 431 } 432 433 // ---------------------------------------- 434 // Misc. 435 436 func getLatestVersion(db dbm.DB) int64 { 437 var latest int64 438 latestBytes := db.Get([]byte(latestVersionKey)) 439 if latestBytes == nil { 440 return 0 441 } 442 443 err := amino.UnmarshalSized(latestBytes, &latest) 444 if err != nil { 445 panic(err) 446 } 447 448 return latest 449 } 450 451 // Set the latest version. 452 func setLatestVersion(batch dbm.Batch, version int64) { 453 latestBytes, _ := amino.MarshalSized(version) 454 batch.Set([]byte(latestVersionKey), latestBytes) 455 } 456 457 // Commits each store and returns a new commitInfo. 458 func commitStores(version int64, storeMap map[types.StoreKey]types.CommitStore) commitInfo { 459 storeInfos := make([]storeInfo, 0, len(storeMap)) 460 461 for key, store := range storeMap { 462 // Commit 463 commitID := store.Commit() 464 /* Print all items. 465 itr := store.Iterator(nil, nil) 466 for ; itr.Valid(); itr.Next() { 467 k, v := itr.Key(), itr.Value() 468 fmt.Println("STORE ENTRY", 469 colors.ColoredBytes(k, colors.Green, colors.Blue), 470 colors.ColoredBytes(v, colors.Cyan, colors.Blue)) 471 } 472 itr.Close() 473 */ 474 // Record CommitID 475 si := storeInfo{} 476 si.Name = key.Name() 477 si.Core.CommitID = commitID 478 // si.Core.StoreType = store.GetStoreType() 479 storeInfos = append(storeInfos, si) 480 } 481 482 ci := commitInfo{ 483 Version: version, 484 StoreInfos: storeInfos, 485 } 486 return ci 487 } 488 489 // Gets commitInfo from disk. 490 func getCommitInfo(db dbm.DB, ver int64) (commitInfo, error) { 491 // Get from DB. 492 cInfoKey := fmt.Sprintf(commitInfoKeyFmt, ver) 493 cInfoBytes := db.Get([]byte(cInfoKey)) 494 if cInfoBytes == nil { 495 return commitInfo{}, fmt.Errorf("failed to get Store: no data") 496 } 497 498 var cInfo commitInfo 499 500 err := amino.UnmarshalSized(cInfoBytes, &cInfo) 501 if err != nil { 502 return commitInfo{}, fmt.Errorf("failed to get Store: %w", err) 503 } 504 505 return cInfo, nil 506 } 507 508 // Set a commitInfo for given version. 509 func setCommitInfo(batch dbm.Batch, version int64, cInfo commitInfo) { 510 cInfoBytes := amino.MustMarshalSized(cInfo) 511 cInfoKey := fmt.Sprintf(commitInfoKeyFmt, version) 512 batch.Set([]byte(cInfoKey), cInfoBytes) 513 }