diff --git a/benchmarks/bench_test.go b/benchmarks/bench_test.go index 8a82590d6..ccca550b8 100644 --- a/benchmarks/bench_test.go +++ b/benchmarks/bench_test.go @@ -348,7 +348,6 @@ func runBenchmarks(b *testing.B, benchmarks []benchmark) { ) if bb.dbType != "nodb" { d, err = dbm.NewDB("test", bb.dbType, dirName) - if err != nil { if strings.Contains(err.Error(), "unknown db_backend") { // As an exception to run benchmarks: if the error is about cleveldb, or rocksdb, diff --git a/import.go b/import.go index 282e260ca..4cbd6c73d 100644 --- a/import.go +++ b/import.go @@ -68,15 +68,28 @@ func (i *Importer) writeNode(node *Node) error { buf.Reset() defer bufPool.Put(buf) - if err := node.writeBytes(buf); err != nil { - return err + if i.tree.useLegacyFormat { + if err := node.writeLegacyBytes(buf); err != nil { + return err + } + } else { + if err := node.writeBytes(buf); err != nil { + return err + } } bytesCopy := make([]byte, buf.Len()) copy(bytesCopy, buf.Bytes()) - if err := i.batch.Set(i.tree.ndb.nodeKey(node.GetKey()), bytesCopy); err != nil { - return err + if i.tree.useLegacyFormat { + node.isLegacy = true + if err := i.batch.Set(i.tree.ndb.legacyNodeKey(node.GetKey()), bytesCopy); err != nil { + return err + } + } else { + if err := i.batch.Set(i.tree.ndb.nodeKey(node.GetKey()), bytesCopy); err != nil { + return err + } } i.batchSize++ @@ -193,10 +206,18 @@ func (i *Importer) Commit() error { return ErrNoImport } + var rootHash []byte switch len(i.stack) { case 0: - if err := i.batch.Set(i.tree.ndb.nodeKey(GetRootKey(i.version)), []byte{}); err != nil { - return err + if i.tree.useLegacyFormat { + rootHash = []byte{} + if err := i.batch.Set(i.tree.ndb.legacyNodeKey(GetRootKey(i.version)), []byte{}); err != nil { + return err + } + } else { + if err := i.batch.Set(i.tree.ndb.nodeKey(GetRootKey(i.version)), []byte{}); err != nil { + return err + } } case 1: i.stack[0].nodeKey.nonce = 1 @@ -204,8 +225,18 @@ func (i *Importer) Commit() error { return err } if i.stack[0].nodeKey.version < i.version { // it means there is no update in the given version - if err := i.batch.Set(i.tree.ndb.nodeKey(GetRootKey(i.version)), i.tree.ndb.nodeKey(i.stack[0].nodeKey.GetKey())); err != nil { - return err + if i.tree.useLegacyFormat { + if len(i.stack[0].hash) == 0 { + i.stack[0]._hash(i.version) + } + rootHash = i.stack[0].hash + if err := i.batch.Set(i.tree.ndb.legacyNodeKey(GetRootKey(i.version)), i.tree.ndb.legacyNodeKey(rootHash)); err != nil { + return err + } + } else { + if err := i.batch.Set(i.tree.ndb.nodeKey(GetRootKey(i.version)), i.tree.ndb.nodeKey(i.stack[0].nodeKey.GetKey())); err != nil { + return err + } } } default: @@ -219,9 +250,16 @@ func (i *Importer) Commit() error { } i.tree.ndb.resetLatestVersion(i.version) - _, err = i.tree.LoadVersion(i.version) - if err != nil { - return err + if i.tree.useLegacyFormat { + _, err = i.tree.LoadVersionByRootHash(i.version, rootHash) + if err != nil { + return err + } + } else { + _, err = i.tree.LoadVersion(i.version) + if err != nil { + return err + } } i.Close() diff --git a/mutable_tree.go b/mutable_tree.go index 3ec57eb87..0d2d55cf9 100644 --- a/mutable_tree.go +++ b/mutable_tree.go @@ -41,6 +41,8 @@ type MutableTree struct { unsavedFastNodeRemovals *sync.Map // map[string]interface{} FastNodes that have not yet been removed from disk ndb *nodeDB skipFastStorageUpgrade bool // If true, the tree will work like no fast storage and always not upgrade fast storage + useLegacyFormat bool // If true, save nodes to the DB with the legacy format + rootHash []byte mtx sync.Mutex } @@ -66,6 +68,30 @@ func NewMutableTree(db dbm.DB, cacheSize int, skipFastStorageUpgrade bool, lg lo } } +func NewLegacyMutableTree(db dbm.DB, cacheSize int, skipFastStorageUpgrade, noStoreVersion bool, + rootHash []byte, lg log.Logger, options ...Option, +) *MutableTree { + opts := DefaultOptions() + for _, opt := range options { + opt(&opts) + } + + ndb := newLegacyNodeDB(db, cacheSize, opts, noStoreVersion, lg) + head := &ImmutableTree{ndb: ndb, skipFastStorageUpgrade: skipFastStorageUpgrade} + + return &MutableTree{ + logger: lg, + ImmutableTree: head, + lastSaved: head.clone(), + unsavedFastNodeAdditions: &sync.Map{}, + unsavedFastNodeRemovals: &sync.Map{}, + ndb: ndb, + skipFastStorageUpgrade: skipFastStorageUpgrade, + useLegacyFormat: true, + rootHash: rootHash, + } +} + // IsEmpty returns whether or not the tree has any keys. Only trees that are // not empty can be saved. func (tree *MutableTree) IsEmpty() bool { @@ -169,7 +195,15 @@ func (tree *MutableTree) Set(key, value []byte) (updated bool, err error) { // The returned value must not be modified, since it may point to data stored within IAVL. func (tree *MutableTree) Get(key []byte) ([]byte, error) { if tree.root == nil { - return nil, nil + if tree.rootHash != nil { + root, err := tree.ndb.GetNode(tree.rootHash) + if err != nil { + return nil, err + } + tree.root = root + } else { + return nil, nil + } } if !tree.skipFastStorageUpgrade { @@ -252,7 +286,7 @@ func (tree *MutableTree) set(key []byte, value []byte) (updated bool, err error) if !tree.skipFastStorageUpgrade { tree.addUnsavedAddition(key, fastnode.NewNode(key, value, tree.version+1)) } - tree.ImmutableTree.root = NewNode(key, value) + tree.ImmutableTree.root = NewNode(key, value, tree.useLegacyFormat) return updated, nil } @@ -311,7 +345,7 @@ func (tree *MutableTree) recursiveSetLeaf(node *Node, key []byte, value []byte) subtreeHeight: 1, size: 2, nodeKey: nil, - leftNode: NewNode(key, value), + leftNode: NewNode(key, value, tree.useLegacyFormat), rightNode: node, }, false, nil case 1: // setKey > leafKey @@ -321,10 +355,10 @@ func (tree *MutableTree) recursiveSetLeaf(node *Node, key []byte, value []byte) size: 2, nodeKey: nil, leftNode: node, - rightNode: NewNode(key, value), + rightNode: NewNode(key, value, tree.useLegacyFormat), }, false, nil default: - return NewNode(key, value), true, nil + return NewNode(key, value, tree.useLegacyFormat), true, nil } } @@ -509,6 +543,41 @@ func (tree *MutableTree) LoadVersion(targetVersion int64) (int64, error) { return latestVersion, nil } +// LoadVersionByRootHash loads a tree using the provided version and roothash +func (tree *MutableTree) LoadVersionByRootHash(version int64, rootHash []byte) (int64, error) { + if len(rootHash) == 0 { + return 0, errors.New("LoadVersionByRootHash must be provided a non-empty rootHash argument") + } + + tree.mtx.Lock() + defer tree.mtx.Unlock() + + t := &ImmutableTree{ + ndb: tree.ndb, + version: version, + skipFastStorageUpgrade: tree.skipFastStorageUpgrade, + } + + var err error + t.root, err = tree.ndb.GetNode(rootHash) + if err != nil { + return 0, err + } + + tree.ImmutableTree = t + tree.lastSaved = t.clone() + tree.rootHash = rootHash + + if !tree.skipFastStorageUpgrade { + // Attempt to upgrade + if _, err := tree.enableFastStorageAndCommitIfNotEnabled(); err != nil { + return 0, err + } + } + + return version, nil +} + // loadVersionForOverwriting attempts to load a tree at a previously committed // version, or the latest version below it. Any versions greater than targetVersion will be deleted. func (tree *MutableTree) LoadVersionForOverwriting(targetVersion int64) error { @@ -737,17 +806,30 @@ func (tree *MutableTree) SaveVersion() ([]byte, int64, error) { } else { if tree.root.nodeKey != nil { // it means there are no updated nodes - if err := tree.ndb.SaveRoot(version, tree.root.nodeKey); err != nil { - return nil, 0, err - } - // it means the reference node is a legacy node - if tree.root.isLegacy { - // it will update the legacy node to the new format - // which ensures the reference node is not a legacy node - tree.root.isLegacy = false + if tree.useLegacyFormat { + if len(tree.root.hash) == 0 { + tree.root._hash(version) + } + if err := tree.ndb.SaveLegacyRoot(version, tree.root.hash); err != nil { + return nil, 0, err + } + tree.root.isLegacy = true if err := tree.ndb.SaveNode(tree.root); err != nil { return nil, 0, fmt.Errorf("failed to save the reference legacy node: %w", err) } + } else { + if err := tree.ndb.SaveRoot(version, tree.root.nodeKey); err != nil { + return nil, 0, err + } + // it means the reference node is a legacy node + if tree.root.isLegacy { + // it will update the legacy node to the new format + // which ensures the reference node is not a legacy node + tree.root.isLegacy = false + if err := tree.ndb.SaveNode(tree.root); err != nil { + return nil, 0, fmt.Errorf("failed to save the reference legacy node: %w", err) + } + } } } else { if err := tree.saveNewNodes(version); err != nil { @@ -771,7 +853,9 @@ func (tree *MutableTree) SaveVersion() ([]byte, int64, error) { tree.unsavedFastNodeRemovals = &sync.Map{} } - return tree.Hash(), version, nil + hash := tree.Hash() + tree.rootHash = hash + return hash, version, nil } func (tree *MutableTree) saveFastNodeVersion(latestVersion int64) error { @@ -1009,9 +1093,11 @@ func (tree *MutableTree) saveNewNodes(version int64) error { newNodes := make([]*Node, 0) var recursiveAssignKey func(*Node) ([]byte, error) recursiveAssignKey = func(node *Node) ([]byte, error) { - if node.nodeKey != nil { + node.isLegacy = tree.useLegacyFormat + if (!node.isLegacy && node.nodeKey != nil) || (node.isLegacy && node.hash != nil) { return node.GetKey(), nil } + nonce++ node.nodeKey = &NodeKey{ version: version, @@ -1034,7 +1120,7 @@ func (tree *MutableTree) saveNewNodes(version int64) error { node._hash(version) newNodes = append(newNodes, node) - return node.nodeKey.GetKey(), nil + return node.GetKey(), nil } if _, err := recursiveAssignKey(tree.root); err != nil { diff --git a/node.go b/node.go index 06ffc9e58..22bd9cf99 100644 --- a/node.go +++ b/node.go @@ -77,12 +77,13 @@ type Node struct { var _ cache.Node = (*Node)(nil) // NewNode returns a new node from a key, value and version. -func NewNode(key []byte, value []byte) *Node { +func NewNode(key []byte, value []byte, useLegacy bool) *Node { return &Node{ key: key, value: value, subtreeHeight: 0, size: 1, + isLegacy: useLegacy, } } @@ -243,11 +244,9 @@ func MakeLegacyNode(hash, buf []byte) (*Node, error) { nodeKey: &NodeKey{version: ver}, key: key, hash: hash, - isLegacy: true, } // Read node body. - if node.isLeaf() { val, _, err := encoding.DecodeBytes(buf) if err != nil { @@ -612,6 +611,9 @@ func (node *Node) writeBytes(w io.Writer) error { if node.leftNodeKey == nil { return ErrLeftNodeKeyEmpty } + if node.rightNodeKey == nil { + return ErrRightNodeKeyEmpty + } // check if children NodeKeys are legacy mode if len(node.leftNodeKey) == hashSize { mode += ModeLegacyLeftNode @@ -662,6 +664,54 @@ func (node *Node) writeBytes(w io.Writer) error { return nil } +func (node *Node) writeLegacyBytes(w io.Writer) error { + if node == nil { + return errors.New("cannot write nil node") + } + err := encoding.EncodeVarint(w, int64(node.subtreeHeight)) + if err != nil { + return fmt.Errorf("writing height, %w", err) + } + err = encoding.EncodeVarint(w, node.size) + if err != nil { + return fmt.Errorf("writing size, %w", err) + } + err = encoding.EncodeVarint(w, node.nodeKey.version) + if err != nil { + return fmt.Errorf("writing version, %w", err) + } + + // Unlike writeHashBytes, key is written for inner nodes. + err = encoding.EncodeBytes(w, node.key) + if err != nil { + return fmt.Errorf("writing key, %w", err) + } + + if node.isLeaf() { + err = encoding.EncodeBytes(w, node.value) + if err != nil { + return fmt.Errorf("writing value, %w", err) + } + } else { + if len(node.leftNodeKey) != hashSize { + return errors.New("node provided to writeLegacyBytes does not have a hash for leftNodeKey") + } + err = encoding.EncodeBytes(w, node.leftNodeKey) + if err != nil { + return fmt.Errorf("writing left hash, %w", err) + } + + if len(node.leftNodeKey) != 32 { + return errors.New("node provided to writeLegacyBytes does not have a hash for rightNodeKey") + } + err = encoding.EncodeBytes(w, node.rightNodeKey) + if err != nil { + return fmt.Errorf("writing right hash, %w", err) + } + } + return nil +} + func (node *Node) getLeftNode(t *ImmutableTree) (*Node, error) { if node.leftNode != nil { return node.leftNode, nil diff --git a/nodedb.go b/nodedb.go index b00aa6fac..f62e7d3fd 100644 --- a/nodedb.go +++ b/nodedb.go @@ -90,10 +90,11 @@ type nodeDB struct { legacyLatestVersion int64 // Latest version of nodeDB in legacy format. nodeCache cache.Cache // Cache for nodes in the regular tree that consists of key-value pairs at any version. fastNodeCache cache.Cache // Cache for nodes in the fast index that represents only key-value pairs at the latest version. + useLegacyFormat bool } func newNodeDB(db dbm.DB, cacheSize int, opts Options, lg log.Logger) *nodeDB { - storeVersion, err := db.Get(metadataKeyFormat.Key([]byte(storageVersionKey))) + storeVersion, err := db.Get(metadataKeyFormat.Key(ibytes.UnsafeStrToBytes(storageVersionKey))) if err != nil || storeVersion == nil { storeVersion = []byte(defaultStorageVersionValue) @@ -114,6 +115,34 @@ func newNodeDB(db dbm.DB, cacheSize int, opts Options, lg log.Logger) *nodeDB { } } +func newLegacyNodeDB(db dbm.DB, cacheSize int, opts Options, noStoreVersion bool, lg log.Logger) *nodeDB { + var storeVersion []byte + if noStoreVersion { + storeVersion = []byte(defaultStorageVersionValue) + } else { + var err error + storeVersion, err = db.Get(metadataKeyFormat.Key(ibytes.UnsafeStrToBytes(storageVersionKey))) + if err != nil || storeVersion == nil { + storeVersion = []byte(defaultStorageVersionValue) + } + } + + return &nodeDB{ + logger: lg, + db: db, + batch: NewBatchWithFlusher(db, opts.FlushThreshold), + opts: opts, + firstVersion: 0, + latestVersion: 0, // initially invalid + legacyLatestVersion: 0, + nodeCache: cache.New(cacheSize), + fastNodeCache: cache.New(fastNodeCacheSize), + versionReaders: make(map[int64]uint32, 8), + storageVersion: string(storeVersion), + useLegacyFormat: true, + } +} + // GetNode gets a node from memory or disk. If it is an inner node, it does not // load its children. // It is used for both formats of nodes: legacy and new. @@ -135,9 +164,9 @@ func (ndb *nodeDB) GetNode(nk []byte) (*Node, error) { ndb.opts.Stat.IncCacheMissCnt() // Doesn't exist, load. - isLegcyNode := len(nk) == hashSize + isLegacyNode := len(nk) == hashSize var nodeKey []byte - if isLegcyNode { + if isLegacyNode { nodeKey = ndb.legacyNodeKey(nk) } else { nodeKey = ndb.nodeKey(nk) @@ -151,11 +180,12 @@ func (ndb *nodeDB) GetNode(nk []byte) (*Node, error) { } var node *Node - if isLegcyNode { + if isLegacyNode { node, err = MakeLegacyNode(nk, buf) if err != nil { return nil, fmt.Errorf("error reading Legacy Node. bytes: %x, error: %v", buf, err) } + node.isLegacy = ndb.useLegacyFormat } else { node, err = MakeNode(nk, buf) if err != nil { @@ -209,7 +239,7 @@ func (ndb *nodeDB) SaveNode(node *Node) error { ndb.mtx.Lock() defer ndb.mtx.Unlock() - if node.nodeKey == nil { + if node.nodeKey == nil || (ndb.useLegacyFormat && node.hash == nil) { return ErrNodeMissingNodeKey } @@ -217,12 +247,21 @@ func (ndb *nodeDB) SaveNode(node *Node) error { var buf bytes.Buffer buf.Grow(node.encodedSize()) - if err := node.writeBytes(&buf); err != nil { - return err - } - - if err := ndb.batch.Set(ndb.nodeKey(node.GetKey()), buf.Bytes()); err != nil { - return err + nk := node.GetKey() + if len(nk) == hashSize { + if err := node.writeLegacyBytes(&buf); err != nil { + return err + } + if err := ndb.batch.Set(ndb.legacyNodeKey(nk), buf.Bytes()); err != nil { + return err + } + } else { + if err := node.writeBytes(&buf); err != nil { + return err + } + if err := ndb.batch.Set(ndb.nodeKey(nk), buf.Bytes()); err != nil { + return err + } } ndb.logger.Debug("BATCH SAVE", "node", node) @@ -328,6 +367,9 @@ func (ndb *nodeDB) saveFastNodeUnlocked(node *fastnode.Node, shouldAddToCache bo // Has checks if a node key exists in the database. func (ndb *nodeDB) Has(nk []byte) (bool, error) { + if len(nk) == hashSize { + return ndb.db.Has(ndb.legacyNodeKey(nk)) + } return ndb.db.Has(ndb.nodeKey(nk)) } @@ -514,7 +556,6 @@ func (ndb *nodeDB) DeleteVersionsFrom(fromVersion int64) error { err = ndb.traverseRange(nodeKeyPrefixFormat.KeyInt64(fromVersion), nodeKeyPrefixFormat.KeyInt64(latest+1), func(k, v []byte) error { return ndb.batch.Delete(k) }) - if err != nil { return err } @@ -831,6 +872,13 @@ func (ndb *nodeDB) SaveRoot(version int64, nk *NodeKey) error { return ndb.batch.Set(nodeKeyFormat.Key(GetRootKey(version)), nodeKeyFormat.Key(nk.GetKey())) } +// SaveLegacyRoot saves the root when no updates. +func (ndb *nodeDB) SaveLegacyRoot(version int64, key []byte) error { + ndb.mtx.Lock() + defer ndb.mtx.Unlock() + return ndb.batch.Set(legacyNodeKeyFormat.Key(GetRootKey(version)), legacyNodeKeyFormat.Key(key)) +} + // Traverse fast nodes and return error if any, nil otherwise func (ndb *nodeDB) traverseFastNodes(fn func(k, v []byte) error) error { return ndb.traversePrefix(fastKeyFormat.Key(), fn) @@ -1215,7 +1263,6 @@ func (ndb *nodeDB) String() (string, error) { index++ return nil }) - if err != nil { return "", err } diff --git a/testutils_test.go b/testutils_test.go index b64a54984..7bbd8f24c 100644 --- a/testutils_test.go +++ b/testutils_test.go @@ -52,12 +52,12 @@ func N(l, r interface{}) *Node { if _, ok := l.(*Node); ok { left = l.(*Node) } else { - left = NewNode(i2b(l.(int)), nil) + left = NewNode(i2b(l.(int)), nil, false) } if _, ok := r.(*Node); ok { right = r.(*Node) } else { - right = NewNode(i2b(r.(int)), nil) + right = NewNode(i2b(r.(int)), nil, false) } n := &Node{