github.com/Finschia/finschia-sdk@v0.48.1/store/rootmulti/store.go (about) 1 package rootmulti 2 3 import ( 4 "encoding/binary" 5 "fmt" 6 "io" 7 "math" 8 "sort" 9 "strings" 10 "sync" 11 12 iavltree "github.com/cosmos/iavl" 13 protoio "github.com/gogo/protobuf/io" 14 gogotypes "github.com/gogo/protobuf/types" 15 "github.com/pkg/errors" 16 abci "github.com/tendermint/tendermint/abci/types" 17 "github.com/tendermint/tendermint/proto/tendermint/crypto" 18 dbm "github.com/tendermint/tm-db" 19 20 "github.com/Finschia/ostracon/libs/log" 21 22 snapshottypes "github.com/Finschia/finschia-sdk/snapshots/types" 23 "github.com/Finschia/finschia-sdk/store/cachemulti" 24 "github.com/Finschia/finschia-sdk/store/dbadapter" 25 "github.com/Finschia/finschia-sdk/store/iavl" 26 "github.com/Finschia/finschia-sdk/store/listenkv" 27 "github.com/Finschia/finschia-sdk/store/mem" 28 "github.com/Finschia/finschia-sdk/store/tracekv" 29 "github.com/Finschia/finschia-sdk/store/transient" 30 "github.com/Finschia/finschia-sdk/store/types" 31 sdkerrors "github.com/Finschia/finschia-sdk/types/errors" 32 ) 33 34 const ( 35 latestVersionKey = "s/latest" 36 pruneHeightsKey = "s/pruneheights" 37 commitInfoKeyFmt = "s/%d" // s/<version> 38 39 proofsPath = "proofs" 40 ) 41 42 const iavlDisablefastNodeDefault = true 43 44 // Store is composed of many CommitStores. Name contrasts with 45 // cacheMultiStore which is used for branching other MultiStores. It implements 46 // the CommitMultiStore interface. 47 type Store struct { 48 db dbm.DB 49 logger log.Logger 50 lastCommitInfo *types.CommitInfo 51 pruningOpts types.PruningOptions 52 iavlCacheSize int 53 iavlDisableFastNode bool 54 storesParams map[types.StoreKey]storeParams 55 stores map[types.StoreKey]types.CommitKVStore 56 keysByName map[string]types.StoreKey 57 lazyLoading bool 58 pruneHeights []int64 59 initialVersion int64 60 61 traceWriter io.Writer 62 traceContext types.TraceContext 63 traceContextMutex sync.Mutex 64 65 interBlockCache types.MultiStorePersistentCache 66 67 listeners map[types.StoreKey][]types.WriteListener 68 } 69 70 var ( 71 _ types.CommitMultiStore = (*Store)(nil) 72 _ types.Queryable = (*Store)(nil) 73 ) 74 75 // NewStore returns a reference to a new Store object with the provided DB. The 76 // store will be created with a PruneNothing pruning strategy by default. After 77 // a store is created, KVStores must be mounted and finally LoadLatestVersion or 78 // LoadVersion must be called. 79 func NewStore(db dbm.DB, logger log.Logger) *Store { 80 return &Store{ 81 db: db, 82 logger: logger, 83 pruningOpts: types.PruneNothing, 84 iavlCacheSize: iavl.DefaultIAVLCacheSize, 85 iavlDisableFastNode: iavlDisablefastNodeDefault, 86 storesParams: make(map[types.StoreKey]storeParams), 87 stores: make(map[types.StoreKey]types.CommitKVStore), 88 keysByName: make(map[string]types.StoreKey), 89 pruneHeights: make([]int64, 0), 90 listeners: make(map[types.StoreKey][]types.WriteListener), 91 } 92 } 93 94 // GetPruning fetches the pruning strategy from the root store. 95 func (rs *Store) GetPruning() types.PruningOptions { 96 return rs.pruningOpts 97 } 98 99 // SetPruning sets the pruning strategy on the root store and all the sub-stores. 100 // Note, calling SetPruning on the root store prior to LoadVersion or 101 // LoadLatestVersion performs a no-op as the stores aren't mounted yet. 102 func (rs *Store) SetPruning(pruningOpts types.PruningOptions) { 103 rs.pruningOpts = pruningOpts 104 } 105 106 func (rs *Store) SetIAVLCacheSize(cacheSize int) { 107 rs.iavlCacheSize = cacheSize 108 } 109 110 func (rs *Store) SetIAVLDisableFastNode(disableFastNode bool) { 111 rs.iavlDisableFastNode = disableFastNode 112 } 113 114 // SetLazyLoading sets if the iavl store should be loaded lazily or not 115 func (rs *Store) SetLazyLoading(lazyLoading bool) { 116 rs.lazyLoading = lazyLoading 117 } 118 119 // GetStoreType implements Store. 120 func (rs *Store) GetStoreType() types.StoreType { 121 return types.StoreTypeMulti 122 } 123 124 // MountStoreWithDB implements CommitMultiStore. 125 func (rs *Store) MountStoreWithDB(key types.StoreKey, typ types.StoreType, db dbm.DB) { 126 if key == nil { 127 panic("MountIAVLStore() key cannot be nil") 128 } 129 if _, ok := rs.storesParams[key]; ok { 130 panic(fmt.Sprintf("store duplicate store key %v", key)) 131 } 132 if _, ok := rs.keysByName[key.Name()]; ok { 133 panic(fmt.Sprintf("store duplicate store key name %v", key)) 134 } 135 rs.storesParams[key] = storeParams{ 136 key: key, 137 typ: typ, 138 db: db, 139 } 140 rs.keysByName[key.Name()] = key 141 } 142 143 // GetCommitStore returns a mounted CommitStore for a given StoreKey. If the 144 // store is wrapped in an inter-block cache, it will be unwrapped before returning. 145 func (rs *Store) GetCommitStore(key types.StoreKey) types.CommitStore { 146 return rs.GetCommitKVStore(key) 147 } 148 149 // GetCommitKVStore returns a mounted CommitKVStore for a given StoreKey. If the 150 // store is wrapped in an inter-block cache, it will be unwrapped before returning. 151 func (rs *Store) GetCommitKVStore(key types.StoreKey) types.CommitKVStore { 152 // If the Store has an inter-block cache, first attempt to lookup and unwrap 153 // the underlying CommitKVStore by StoreKey. If it does not exist, fallback to 154 // the main mapping of CommitKVStores. 155 if rs.interBlockCache != nil { 156 if store := rs.interBlockCache.Unwrap(key); store != nil { 157 return store 158 } 159 } 160 161 return rs.stores[key] 162 } 163 164 // GetStores returns mounted stores 165 func (rs *Store) GetStores() map[types.StoreKey]types.CommitKVStore { 166 return rs.stores 167 } 168 169 // LoadLatestVersionAndUpgrade implements CommitMultiStore 170 func (rs *Store) LoadLatestVersionAndUpgrade(upgrades *types.StoreUpgrades) error { 171 ver := GetLatestVersion(rs.db) 172 return rs.loadVersion(ver, upgrades) 173 } 174 175 // LoadVersionAndUpgrade allows us to rename substores while loading an older version 176 func (rs *Store) LoadVersionAndUpgrade(ver int64, upgrades *types.StoreUpgrades) error { 177 return rs.loadVersion(ver, upgrades) 178 } 179 180 // LoadLatestVersion implements CommitMultiStore. 181 func (rs *Store) LoadLatestVersion() error { 182 ver := GetLatestVersion(rs.db) 183 return rs.loadVersion(ver, nil) 184 } 185 186 // LoadVersion implements CommitMultiStore. 187 func (rs *Store) LoadVersion(ver int64) error { 188 return rs.loadVersion(ver, nil) 189 } 190 191 func (rs *Store) loadVersion(ver int64, upgrades *types.StoreUpgrades) error { 192 infos := make(map[string]types.StoreInfo) 193 194 cInfo := &types.CommitInfo{} 195 196 // load old data if we are not version 0 197 if ver != 0 { 198 var err error 199 cInfo, err = getCommitInfo(rs.db, ver) 200 if err != nil { 201 return err 202 } 203 204 // convert StoreInfos slice to map 205 for _, storeInfo := range cInfo.StoreInfos { 206 infos[storeInfo.Name] = storeInfo 207 } 208 } 209 210 // load each Store (note this doesn't panic on unmounted keys now) 211 newStores := make(map[types.StoreKey]types.CommitKVStore) 212 213 storesKeys := make([]types.StoreKey, 0, len(rs.storesParams)) 214 215 for key := range rs.storesParams { 216 storesKeys = append(storesKeys, key) 217 } 218 if upgrades != nil { 219 // deterministic iteration order for upgrades 220 // (as the underlying store may change and 221 // upgrades make store changes where the execution order may matter) 222 sort.Slice(storesKeys, func(i, j int) bool { 223 return storesKeys[i].Name() < storesKeys[j].Name() 224 }) 225 } 226 227 for _, key := range storesKeys { 228 storeParams := rs.storesParams[key] 229 commitID := rs.getCommitID(infos, key.Name()) 230 231 // If it has been added, set the initial version 232 if upgrades.IsAdded(key.Name()) { 233 storeParams.initialVersion = uint64(ver) + 1 234 } 235 236 store, err := rs.loadCommitStoreFromParams(key, commitID, storeParams) 237 if err != nil { 238 return errors.Wrap(err, "failed to load store") 239 } 240 241 newStores[key] = store 242 243 // If it was deleted, remove all data 244 if upgrades.IsDeleted(key.Name()) { 245 deleteKVStore(store.(types.KVStore)) 246 } else if oldName := upgrades.RenamedFrom(key.Name()); oldName != "" { 247 // handle renames specially 248 // make an unregistered key to satify loadCommitStore params 249 oldKey := types.NewKVStoreKey(oldName) 250 oldParams := storeParams 251 oldParams.key = oldKey 252 253 // load from the old name 254 oldStore, err := rs.loadCommitStoreFromParams(oldKey, rs.getCommitID(infos, oldName), oldParams) 255 if err != nil { 256 return errors.Wrapf(err, "failed to load old store %s", oldName) 257 } 258 259 // move all data 260 moveKVStoreData(oldStore.(types.KVStore), store.(types.KVStore)) 261 } 262 } 263 264 rs.lastCommitInfo = cInfo 265 rs.stores = newStores 266 267 // load any pruned heights we missed from disk to be pruned on the next run 268 ph, err := getPruningHeights(rs.db) 269 if err == nil && len(ph) > 0 { 270 rs.pruneHeights = ph 271 } 272 273 return nil 274 } 275 276 func (rs *Store) getCommitID(infos map[string]types.StoreInfo, name string) types.CommitID { 277 info, ok := infos[name] 278 if !ok { 279 return types.CommitID{} 280 } 281 282 return info.CommitId 283 } 284 285 func deleteKVStore(kv types.KVStore) { 286 // Note that we cannot write while iterating, so load all keys here, delete below 287 var keys [][]byte 288 itr := kv.Iterator(nil, nil) 289 for itr.Valid() { 290 keys = append(keys, itr.Key()) 291 itr.Next() 292 } 293 itr.Close() 294 295 for _, k := range keys { 296 kv.Delete(k) 297 } 298 } 299 300 // we simulate move by a copy and delete 301 func moveKVStoreData(oldDB types.KVStore, newDB types.KVStore) { 302 // we read from one and write to another 303 itr := oldDB.Iterator(nil, nil) 304 for itr.Valid() { 305 newDB.Set(itr.Key(), itr.Value()) 306 itr.Next() 307 } 308 itr.Close() 309 310 // then delete the old store 311 deleteKVStore(oldDB) 312 } 313 314 // SetInterBlockCache sets the Store's internal inter-block (persistent) cache. 315 // When this is defined, all CommitKVStores will be wrapped with their respective 316 // inter-block cache. 317 func (rs *Store) SetInterBlockCache(c types.MultiStorePersistentCache) { 318 rs.interBlockCache = c 319 } 320 321 // SetTracer sets the tracer for the MultiStore that the underlying 322 // stores will utilize to trace operations. A MultiStore is returned. 323 func (rs *Store) SetTracer(w io.Writer) types.MultiStore { 324 rs.traceWriter = w 325 return rs 326 } 327 328 // SetTracingContext updates the tracing context for the MultiStore by merging 329 // the given context with the existing context by key. Any existing keys will 330 // be overwritten. It is implied that the caller should update the context when 331 // necessary between tracing operations. It returns a modified MultiStore. 332 func (rs *Store) SetTracingContext(tc types.TraceContext) types.MultiStore { 333 rs.traceContextMutex.Lock() 334 defer rs.traceContextMutex.Unlock() 335 if rs.traceContext != nil { 336 for k, v := range tc { 337 rs.traceContext[k] = v 338 } 339 } else { 340 rs.traceContext = tc 341 } 342 343 return rs 344 } 345 346 func (rs *Store) getTracingContext() types.TraceContext { 347 rs.traceContextMutex.Lock() 348 defer rs.traceContextMutex.Unlock() 349 350 if rs.traceContext == nil { 351 return nil 352 } 353 354 ctx := types.TraceContext{} 355 for k, v := range rs.traceContext { 356 ctx[k] = v 357 } 358 359 return ctx 360 } 361 362 // TracingEnabled returns if tracing is enabled for the MultiStore. 363 func (rs *Store) TracingEnabled() bool { 364 return rs.traceWriter != nil 365 } 366 367 // AddListeners adds listeners for a specific KVStore 368 func (rs *Store) AddListeners(key types.StoreKey, listeners []types.WriteListener) { 369 if ls, ok := rs.listeners[key]; ok { 370 rs.listeners[key] = append(ls, listeners...) 371 } else { 372 rs.listeners[key] = listeners 373 } 374 } 375 376 // ListeningEnabled returns if listening is enabled for a specific KVStore 377 func (rs *Store) ListeningEnabled(key types.StoreKey) bool { 378 if ls, ok := rs.listeners[key]; ok { 379 return len(ls) != 0 380 } 381 return false 382 } 383 384 // LastCommitID implements Committer/CommitStore. 385 func (rs *Store) LastCommitID() types.CommitID { 386 if rs.lastCommitInfo == nil { 387 return types.CommitID{ 388 Version: GetLatestVersion(rs.db), 389 } 390 } 391 392 return rs.lastCommitInfo.CommitID() 393 } 394 395 // Commit implements Committer/CommitStore. 396 func (rs *Store) Commit() types.CommitID { 397 var previousHeight, version int64 398 if rs.lastCommitInfo.GetVersion() == 0 && rs.initialVersion > 1 { 399 // This case means that no commit has been made in the store, we 400 // start from initialVersion. 401 version = rs.initialVersion 402 } else { 403 // This case can means two things: 404 // - either there was already a previous commit in the store, in which 405 // case we increment the version from there, 406 // - or there was no previous commit, and initial version was not set, 407 // in which case we start at version 1. 408 previousHeight = rs.lastCommitInfo.GetVersion() 409 version = previousHeight + 1 410 } 411 412 rs.lastCommitInfo = commitStores(version, rs.stores) 413 414 // Determine if pruneHeight height needs to be added to the list of heights to 415 // be pruned, where pruneHeight = (commitHeight - 1) - KeepRecent. 416 if rs.pruningOpts.Interval > 0 && int64(rs.pruningOpts.KeepRecent) < previousHeight { 417 pruneHeight := previousHeight - int64(rs.pruningOpts.KeepRecent) 418 // We consider this height to be pruned iff: 419 // 420 // - KeepEvery is zero as that means that all heights should be pruned. 421 // - KeepEvery % (height - KeepRecent) != 0 as that means the height is not 422 // a 'snapshot' height. 423 if rs.pruningOpts.KeepEvery == 0 || pruneHeight%int64(rs.pruningOpts.KeepEvery) != 0 { 424 rs.pruneHeights = append(rs.pruneHeights, pruneHeight) 425 } 426 } 427 428 // batch prune if the current height is a pruning interval height 429 if rs.pruningOpts.Interval > 0 && version%int64(rs.pruningOpts.Interval) == 0 { 430 rs.PruneStores(true, nil) 431 } 432 433 flushMetadata(rs.db, version, rs.lastCommitInfo, rs.pruneHeights) 434 435 return types.CommitID{ 436 Version: version, 437 Hash: rs.lastCommitInfo.Hash(), 438 } 439 } 440 441 // PruneStores will batch delete a list of heights from each mounted sub-store. 442 // If clearStorePruningHeihgts is true, store's pruneHeights is appended to the 443 // pruningHeights and reset after finishing pruning. 444 func (rs *Store) PruneStores(clearStorePruningHeihgts bool, pruningHeights []int64) { 445 if clearStorePruningHeihgts { 446 pruningHeights = append(pruningHeights, rs.pruneHeights...) 447 } 448 449 if len(rs.pruneHeights) == 0 { 450 return 451 } 452 453 for key, store := range rs.stores { 454 if store.GetStoreType() == types.StoreTypeIAVL { 455 // If the store is wrapped with an inter-block cache, we must first unwrap 456 // it to get the underlying IAVL store. 457 store = rs.GetCommitKVStore(key) 458 459 if err := store.(*iavl.Store).DeleteVersions(pruningHeights...); err != nil { 460 if errCause := errors.Cause(err); errCause != nil && errCause != iavltree.ErrVersionDoesNotExist { 461 panic(err) 462 } 463 } 464 } 465 } 466 467 if clearStorePruningHeihgts { 468 rs.pruneHeights = make([]int64, 0) 469 } 470 } 471 472 // CacheWrap implements CacheWrapper/Store/CommitStore. 473 func (rs *Store) CacheWrap() types.CacheWrap { 474 return rs.CacheMultiStore().(types.CacheWrap) 475 } 476 477 // CacheWrapWithTrace implements the CacheWrapper interface. 478 func (rs *Store) CacheWrapWithTrace(_ io.Writer, _ types.TraceContext) types.CacheWrap { 479 return rs.CacheWrap() 480 } 481 482 // CacheWrapWithListeners implements the CacheWrapper interface. 483 func (rs *Store) CacheWrapWithListeners(_ types.StoreKey, _ []types.WriteListener) types.CacheWrap { 484 return rs.CacheWrap() 485 } 486 487 // CacheMultiStore creates ephemeral branch of the multi-store and returns a CacheMultiStore. 488 // It implements the MultiStore interface. 489 func (rs *Store) CacheMultiStore() types.CacheMultiStore { 490 stores := make(map[types.StoreKey]types.CacheWrapper) 491 for k, v := range rs.stores { 492 stores[k] = v 493 } 494 return cachemulti.NewStore(rs.db, stores, rs.keysByName, rs.traceWriter, rs.getTracingContext(), rs.listeners) 495 } 496 497 // CacheMultiStoreWithVersion is analogous to CacheMultiStore except that it 498 // attempts to load stores at a given version (height). An error is returned if 499 // any store cannot be loaded. This should only be used for querying and 500 // iterating at past heights. 501 func (rs *Store) CacheMultiStoreWithVersion(version int64) (types.CacheMultiStore, error) { 502 cachedStores := make(map[types.StoreKey]types.CacheWrapper) 503 for key, store := range rs.stores { 504 switch store.GetStoreType() { 505 case types.StoreTypeIAVL: 506 // If the store is wrapped with an inter-block cache, we must first unwrap 507 // it to get the underlying IAVL store. 508 store = rs.GetCommitKVStore(key) 509 510 // Attempt to lazy-load an already saved IAVL store version. If the 511 // version does not exist or is pruned, an error should be returned. 512 iavlStore, err := store.(*iavl.Store).GetImmutable(version) 513 if err != nil { 514 return nil, err 515 } 516 517 cachedStores[key] = iavlStore 518 519 default: 520 cachedStores[key] = store 521 } 522 } 523 524 return cachemulti.NewStore(rs.db, cachedStores, rs.keysByName, rs.traceWriter, rs.getTracingContext(), rs.listeners), nil 525 } 526 527 // GetStore returns a mounted Store for a given StoreKey. If the StoreKey does 528 // not exist, it will panic. If the Store is wrapped in an inter-block cache, it 529 // will be unwrapped prior to being returned. 530 // 531 // TODO: This isn't used directly upstream. Consider returning the Store as-is 532 // instead of unwrapping. 533 func (rs *Store) GetStore(key types.StoreKey) types.Store { 534 store := rs.GetCommitKVStore(key) 535 if store == nil { 536 panic(fmt.Sprintf("store does not exist for key: %s", key.Name())) 537 } 538 539 return store 540 } 541 542 // GetKVStore returns a mounted KVStore for a given StoreKey. If tracing is 543 // enabled on the KVStore, a wrapped TraceKVStore will be returned with the root 544 // store's tracer, otherwise, the original KVStore will be returned. 545 // 546 // NOTE: The returned KVStore may be wrapped in an inter-block cache if it is 547 // set on the root store. 548 func (rs *Store) GetKVStore(key types.StoreKey) types.KVStore { 549 s := rs.stores[key] 550 if s == nil { 551 panic(fmt.Sprintf("store does not exist for key: %s", key.Name())) 552 } 553 store := s.(types.KVStore) 554 555 if rs.TracingEnabled() { 556 store = tracekv.NewStore(store, rs.traceWriter, rs.getTracingContext()) 557 } 558 if rs.ListeningEnabled(key) { 559 store = listenkv.NewStore(store, key, rs.listeners[key]) 560 } 561 562 return store 563 } 564 565 // GetStoreByName performs a lookup of a StoreKey given a store name typically 566 // provided in a path. The StoreKey is then used to perform a lookup and return 567 // a Store. If the Store is wrapped in an inter-block cache, it will be unwrapped 568 // prior to being returned. If the StoreKey does not exist, nil is returned. 569 func (rs *Store) GetStoreByName(name string) types.Store { 570 key := rs.keysByName[name] 571 if key == nil { 572 return nil 573 } 574 575 return rs.GetCommitKVStore(key) 576 } 577 578 // Query calls substore.Query with the same `req` where `req.Path` is 579 // modified to remove the substore prefix. 580 // Ie. `req.Path` here is `/<substore>/<path>`, and trimmed to `/<path>` for the substore. 581 // Special case: if `req.Path` is `/proofs`, the commit hash is included 582 // as response value. In addition, proofs of every store are appended to the response for 583 // the requested height 584 func (rs *Store) Query(req abci.RequestQuery) abci.ResponseQuery { 585 path := req.Path 586 firstPath, subpath, err := parsePath(path) 587 if err != nil { 588 return sdkerrors.QueryResult(err) 589 } 590 591 if firstPath == proofsPath { 592 return rs.doProofsQuery(req) 593 } 594 595 store := rs.GetStoreByName(firstPath) 596 if store == nil { 597 return sdkerrors.QueryResult(sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "no such store: %s", firstPath)) 598 } 599 600 queryable, ok := store.(types.Queryable) 601 if !ok { 602 return sdkerrors.QueryResult(sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "store %s (type %T) doesn't support queries", firstPath, store)) 603 } 604 605 // trim the path and make the query 606 req.Path = subpath 607 res := queryable.Query(req) 608 609 if !req.Prove || !RequireProof(subpath) { 610 return res 611 } 612 613 if res.ProofOps == nil || len(res.ProofOps.Ops) == 0 { 614 return sdkerrors.QueryResult(sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "proof is unexpectedly empty; ensure height has not been pruned")) 615 } 616 617 // If the request's height is the latest height we've committed, then utilize 618 // the store's lastCommitInfo as this commit info may not be flushed to disk. 619 // Otherwise, we query for the commit info from disk. 620 var commitInfo *types.CommitInfo 621 622 if res.Height == rs.lastCommitInfo.Version { 623 commitInfo = rs.lastCommitInfo 624 } else { 625 commitInfo, err = getCommitInfo(rs.db, res.Height) 626 if err != nil { 627 return sdkerrors.QueryResult(err) 628 } 629 } 630 631 // Restore origin path and append proof op. 632 res.ProofOps.Ops = append(res.ProofOps.Ops, commitInfo.ProofOp(firstPath)) 633 634 return res 635 } 636 637 // SetInitialVersion sets the initial version of the IAVL tree. It is used when 638 // starting a new chain at an arbitrary height. 639 // NOTE: this never errors. Can we fix the function signature ? 640 func (rs *Store) SetInitialVersion(version int64) error { 641 rs.initialVersion = version 642 643 // Loop through all the stores, if it's an IAVL store, then set initial 644 // version on it. 645 for key, store := range rs.stores { 646 if store.GetStoreType() == types.StoreTypeIAVL { 647 // If the store is wrapped with an inter-block cache, we must first unwrap 648 // it to get the underlying IAVL store. 649 store = rs.GetCommitKVStore(key) 650 store.(*iavl.Store).SetInitialVersion(version) 651 } 652 } 653 654 return nil 655 } 656 657 // parsePath expects a format like /<storeName>[/<subpath>] 658 // Must start with /, subpath may be empty 659 // Returns error if it doesn't start with / 660 func parsePath(path string) (storeName string, subpath string, err error) { 661 if !strings.HasPrefix(path, "/") { 662 return storeName, subpath, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid path: %s", path) 663 } 664 665 paths := strings.SplitN(path[1:], "/", 2) 666 storeName = paths[0] 667 668 if len(paths) == 2 { 669 subpath = "/" + paths[1] 670 } 671 672 return storeName, subpath, nil 673 } 674 675 //---------------------- Snapshotting ------------------ 676 677 // Snapshot implements snapshottypes.Snapshotter. The snapshot output for a given format must be 678 // identical across nodes such that chunks from different sources fit together. If the output for a 679 // given format changes (at the byte level), the snapshot format must be bumped - see 680 // TestMultistoreSnapshot_Checksum test. 681 func (rs *Store) Snapshot(height uint64, protoWriter protoio.Writer) error { 682 if height == 0 { 683 return sdkerrors.Wrap(sdkerrors.ErrLogic, "cannot snapshot height 0") 684 } 685 if height > uint64(rs.LastCommitID().Version) { 686 return sdkerrors.Wrapf(sdkerrors.ErrLogic, "cannot snapshot future height %v", height) 687 } 688 689 // Collect stores to snapshot (only IAVL stores are supported) 690 type namedStore struct { 691 *iavl.Store 692 name string 693 } 694 stores := []namedStore{} 695 for key := range rs.stores { 696 switch store := rs.GetCommitKVStore(key).(type) { 697 case *iavl.Store: 698 stores = append(stores, namedStore{name: key.Name(), Store: store}) 699 case *transient.Store, *mem.Store: 700 // Non-persisted stores shouldn't be snapshotted 701 continue 702 default: 703 return sdkerrors.Wrapf(sdkerrors.ErrLogic, 704 "don't know how to snapshot store %q of type %T", key.Name(), store) 705 } 706 } 707 sort.Slice(stores, func(i, j int) bool { 708 return strings.Compare(stores[i].name, stores[j].name) == -1 709 }) 710 711 // Export each IAVL store. Stores are serialized as a stream of SnapshotItem Protobuf 712 // messages. The first item contains a SnapshotStore with store metadata (i.e. name), 713 // and the following messages contain a SnapshotNode (i.e. an ExportNode). Store changes 714 // are demarcated by new SnapshotStore items. 715 for _, store := range stores { 716 exporter, err := store.Export(int64(height)) 717 if err != nil { 718 return err 719 } 720 defer exporter.Close() 721 err = protoWriter.WriteMsg(&snapshottypes.SnapshotItem{ 722 Item: &snapshottypes.SnapshotItem_Store{ 723 Store: &snapshottypes.SnapshotStoreItem{ 724 Name: store.name, 725 }, 726 }, 727 }) 728 if err != nil { 729 return err 730 } 731 732 for { 733 node, err := exporter.Next() 734 if err == iavltree.ExportDone { 735 break 736 } else if err != nil { 737 return err 738 } 739 err = protoWriter.WriteMsg(&snapshottypes.SnapshotItem{ 740 Item: &snapshottypes.SnapshotItem_IAVL{ 741 IAVL: &snapshottypes.SnapshotIAVLItem{ 742 Key: node.Key, 743 Value: node.Value, 744 Height: int32(node.Height), 745 Version: node.Version, 746 }, 747 }, 748 }) 749 if err != nil { 750 return err 751 } 752 } 753 exporter.Close() 754 } 755 756 return nil 757 } 758 759 // Restore implements snapshottypes.Snapshotter. 760 // returns next snapshot item and error. 761 func (rs *Store) Restore( 762 height uint64, format uint32, protoReader protoio.Reader, 763 ) (snapshottypes.SnapshotItem, error) { 764 // Import nodes into stores. The first item is expected to be a SnapshotItem containing 765 // a SnapshotStoreItem, telling us which store to import into. The following items will contain 766 // SnapshotNodeItem (i.e. ExportNode) until we reach the next SnapshotStoreItem or EOF. 767 var importer *iavltree.Importer 768 var snapshotItem snapshottypes.SnapshotItem 769 loop: 770 for { 771 snapshotItem = snapshottypes.SnapshotItem{} 772 err := protoReader.ReadMsg(&snapshotItem) 773 if err == io.EOF { 774 break 775 } else if err != nil { 776 return snapshottypes.SnapshotItem{}, sdkerrors.Wrap(err, "invalid protobuf message") 777 } 778 779 switch item := snapshotItem.Item.(type) { 780 case *snapshottypes.SnapshotItem_Store: 781 if importer != nil { 782 err = importer.Commit() 783 if err != nil { 784 return snapshottypes.SnapshotItem{}, sdkerrors.Wrap(err, "IAVL commit failed") 785 } 786 importer.Close() 787 } 788 store, ok := rs.GetStoreByName(item.Store.Name).(*iavl.Store) 789 if !ok || store == nil { 790 return snapshottypes.SnapshotItem{}, sdkerrors.Wrapf(sdkerrors.ErrLogic, "cannot import into non-IAVL store %q", item.Store.Name) 791 } 792 importer, err = store.Import(int64(height)) 793 if err != nil { 794 return snapshottypes.SnapshotItem{}, sdkerrors.Wrap(err, "import failed") 795 } 796 defer importer.Close() 797 798 case *snapshottypes.SnapshotItem_IAVL: 799 if importer == nil { 800 return snapshottypes.SnapshotItem{}, sdkerrors.Wrap(sdkerrors.ErrLogic, "received IAVL node item before store item") 801 } 802 if item.IAVL.Height > math.MaxInt8 { 803 return snapshottypes.SnapshotItem{}, sdkerrors.Wrapf(sdkerrors.ErrLogic, "node height %v cannot exceed %v", 804 item.IAVL.Height, math.MaxInt8) 805 } 806 node := &iavltree.ExportNode{ 807 Key: item.IAVL.Key, 808 Value: item.IAVL.Value, 809 Height: int8(item.IAVL.Height), 810 Version: item.IAVL.Version, 811 } 812 // Protobuf does not differentiate between []byte{} as nil, but fortunately IAVL does 813 // not allow nil keys nor nil values for leaf nodes, so we can always set them to empty. 814 if node.Key == nil { 815 node.Key = []byte{} 816 } 817 if node.Height == 0 && node.Value == nil { 818 node.Value = []byte{} 819 } 820 err := importer.Add(node) 821 if err != nil { 822 return snapshottypes.SnapshotItem{}, sdkerrors.Wrap(err, "IAVL node import failed") 823 } 824 825 default: 826 break loop 827 } 828 } 829 830 if importer != nil { 831 err := importer.Commit() 832 if err != nil { 833 return snapshottypes.SnapshotItem{}, sdkerrors.Wrap(err, "IAVL commit failed") 834 } 835 importer.Close() 836 } 837 838 flushMetadata(rs.db, int64(height), rs.buildCommitInfo(int64(height)), []int64{}) 839 return snapshotItem, rs.LoadLatestVersion() 840 } 841 842 func (rs *Store) loadCommitStoreFromParams(key types.StoreKey, id types.CommitID, params storeParams) (types.CommitKVStore, error) { 843 var db dbm.DB 844 845 if params.db != nil { 846 db = dbm.NewPrefixDB(params.db, []byte("s/_/")) 847 } else { 848 prefix := "s/k:" + params.key.Name() + "/" 849 db = dbm.NewPrefixDB(rs.db, []byte(prefix)) 850 } 851 852 switch params.typ { 853 case types.StoreTypeMulti: 854 panic("recursive MultiStores not yet supported") 855 856 case types.StoreTypeIAVL: 857 var store types.CommitKVStore 858 var err error 859 860 if params.initialVersion == 0 { 861 store, err = iavl.LoadStore(db, rs.logger, key, id, rs.lazyLoading, rs.iavlCacheSize, rs.iavlDisableFastNode) 862 } else { 863 store, err = iavl.LoadStoreWithInitialVersion(db, rs.logger, key, id, rs.lazyLoading, params.initialVersion, rs.iavlCacheSize, rs.iavlDisableFastNode) 864 } 865 866 if err != nil { 867 return nil, err 868 } 869 870 if rs.interBlockCache != nil { 871 // Wrap and get a CommitKVStore with inter-block caching. Note, this should 872 // only wrap the primary CommitKVStore, not any store that is already 873 // branched as that will create unexpected behavior. 874 store = rs.interBlockCache.GetStoreCache(key, store) 875 } 876 877 return store, err 878 879 case types.StoreTypeDB: 880 return commitDBStoreAdapter{Store: dbadapter.Store{DB: db}}, nil 881 882 case types.StoreTypeTransient: 883 _, ok := key.(*types.TransientStoreKey) 884 if !ok { 885 return nil, fmt.Errorf("invalid StoreKey for StoreTypeTransient: %s", key.String()) 886 } 887 888 return transient.NewStore(), nil 889 890 case types.StoreTypeMemory: 891 if _, ok := key.(*types.MemoryStoreKey); !ok { 892 return nil, fmt.Errorf("unexpected key type for a MemoryStoreKey; got: %s", key.String()) 893 } 894 895 return mem.NewStore(), nil 896 897 default: 898 panic(fmt.Sprintf("unrecognized store type %v", params.typ)) 899 } 900 } 901 902 func (rs *Store) buildCommitInfo(version int64) *types.CommitInfo { 903 storeInfos := []types.StoreInfo{} 904 for key, store := range rs.stores { 905 if store.GetStoreType() == types.StoreTypeTransient { 906 continue 907 } 908 storeInfos = append(storeInfos, types.StoreInfo{ 909 Name: key.Name(), 910 CommitId: store.LastCommitID(), 911 }) 912 } 913 return &types.CommitInfo{ 914 Version: version, 915 StoreInfos: storeInfos, 916 } 917 } 918 919 // RollbackToVersion delete the versions after `target` and update the latest version. 920 func (rs *Store) RollbackToVersion(target int64) error { 921 if target <= 0 { 922 return fmt.Errorf("invalid rollback height target: %d", target) 923 } 924 925 for key, store := range rs.stores { 926 if store.GetStoreType() == types.StoreTypeIAVL { 927 // If the store is wrapped with an inter-block cache, we must first unwrap 928 // it to get the underlying IAVL store. 929 store = rs.GetCommitKVStore(key) 930 _, err := store.(*iavl.Store).LoadVersionForOverwriting(target) 931 if err != nil { 932 return err 933 } 934 } 935 } 936 937 flushMetadata(rs.db, target, rs.buildCommitInfo(target), []int64{}) 938 939 return rs.LoadLatestVersion() 940 } 941 942 type storeParams struct { 943 key types.StoreKey 944 db dbm.DB 945 typ types.StoreType 946 initialVersion uint64 947 } 948 949 func GetLatestVersion(db dbm.DB) int64 { 950 bz, err := db.Get([]byte(latestVersionKey)) 951 if err != nil { 952 panic(err) 953 } else if bz == nil { 954 return 0 955 } 956 957 var latestVersion int64 958 959 if err := gogotypes.StdInt64Unmarshal(&latestVersion, bz); err != nil { 960 panic(err) 961 } 962 963 return latestVersion 964 } 965 966 // Commits each store and returns a new commitInfo. 967 func commitStores(version int64, storeMap map[types.StoreKey]types.CommitKVStore) *types.CommitInfo { 968 storeInfos := make([]types.StoreInfo, 0, len(storeMap)) 969 970 for key, store := range storeMap { 971 commitID := store.Commit() 972 973 if store.GetStoreType() == types.StoreTypeTransient { 974 continue 975 } 976 977 si := types.StoreInfo{} 978 si.Name = key.Name() 979 si.CommitId = commitID 980 storeInfos = append(storeInfos, si) 981 } 982 983 return &types.CommitInfo{ 984 Version: version, 985 StoreInfos: storeInfos, 986 } 987 } 988 989 func (rs *Store) doProofsQuery(req abci.RequestQuery) abci.ResponseQuery { 990 commitInfo, err := getCommitInfo(rs.db, req.Height) 991 if err != nil { 992 return sdkerrors.QueryResult(err) 993 } 994 res := abci.ResponseQuery{ 995 Height: req.Height, 996 Key: []byte(proofsPath), 997 Value: commitInfo.CommitID().Hash, 998 ProofOps: &crypto.ProofOps{Ops: make([]crypto.ProofOp, 0, len(commitInfo.StoreInfos))}, 999 } 1000 1001 for _, storeInfo := range commitInfo.StoreInfos { 1002 res.ProofOps.Ops = append(res.ProofOps.Ops, commitInfo.ProofOp(storeInfo.Name)) 1003 } 1004 return res 1005 } 1006 1007 // Gets commitInfo from disk. 1008 func getCommitInfo(db dbm.DB, ver int64) (*types.CommitInfo, error) { 1009 cInfoKey := fmt.Sprintf(commitInfoKeyFmt, ver) 1010 1011 bz, err := db.Get([]byte(cInfoKey)) 1012 if err != nil { 1013 return nil, errors.Wrap(err, "failed to get commit info") 1014 } else if bz == nil { 1015 return nil, errors.New("no commit info found") 1016 } 1017 1018 cInfo := &types.CommitInfo{} 1019 if err = cInfo.Unmarshal(bz); err != nil { 1020 return nil, errors.Wrap(err, "failed unmarshal commit info") 1021 } 1022 1023 return cInfo, nil 1024 } 1025 1026 func setCommitInfo(batch dbm.Batch, version int64, cInfo *types.CommitInfo) { 1027 bz, err := cInfo.Marshal() 1028 if err != nil { 1029 panic(err) 1030 } 1031 1032 cInfoKey := fmt.Sprintf(commitInfoKeyFmt, version) 1033 batch.Set([]byte(cInfoKey), bz) 1034 } 1035 1036 func setLatestVersion(batch dbm.Batch, version int64) { 1037 bz, err := gogotypes.StdInt64Marshal(version) 1038 if err != nil { 1039 panic(err) 1040 } 1041 1042 batch.Set([]byte(latestVersionKey), bz) 1043 } 1044 1045 func setPruningHeights(batch dbm.Batch, pruneHeights []int64) { 1046 bz := make([]byte, 0) 1047 for _, ph := range pruneHeights { 1048 buf := make([]byte, 8) 1049 binary.BigEndian.PutUint64(buf, uint64(ph)) 1050 bz = append(bz, buf...) 1051 } 1052 1053 batch.Set([]byte(pruneHeightsKey), bz) 1054 } 1055 1056 func getPruningHeights(db dbm.DB) ([]int64, error) { 1057 bz, err := db.Get([]byte(pruneHeightsKey)) 1058 if err != nil { 1059 return nil, fmt.Errorf("failed to get pruned heights: %w", err) 1060 } 1061 if len(bz) == 0 { 1062 return nil, errors.New("no pruned heights found") 1063 } 1064 1065 prunedHeights := make([]int64, len(bz)/8) 1066 i, offset := 0, 0 1067 for offset < len(bz) { 1068 prunedHeights[i] = int64(binary.BigEndian.Uint64(bz[offset : offset+8])) 1069 i++ 1070 offset += 8 1071 } 1072 1073 return prunedHeights, nil 1074 } 1075 1076 func flushMetadata(db dbm.DB, version int64, cInfo *types.CommitInfo, pruneHeights []int64) { 1077 batch := db.NewBatch() 1078 defer batch.Close() 1079 1080 setCommitInfo(batch, version, cInfo) 1081 setLatestVersion(batch, version) 1082 setPruningHeights(batch, pruneHeights) 1083 1084 if err := batch.Write(); err != nil { 1085 panic(fmt.Errorf("error on batch write %w", err)) 1086 } 1087 }