github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/iavl/mutable_tree.go (about)

     1  package iavl
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  
     7  	dbm "github.com/gnolang/gno/tm2/pkg/db"
     8  	"github.com/gnolang/gno/tm2/pkg/errors"
     9  )
    10  
    11  // ErrVersionDoesNotExist is returned if a requested version does not exist.
    12  var ErrVersionDoesNotExist = fmt.Errorf("version does not exist")
    13  
    14  // MutableTree is a persistent tree which keeps track of versions.
    15  type MutableTree struct {
    16  	*ImmutableTree                  // The current, working tree.
    17  	lastSaved      *ImmutableTree   // The most recently saved tree.
    18  	orphans        map[string]int64 // Nodes removed by changes to working tree.
    19  	ndb            *nodeDB
    20  }
    21  
    22  // NewMutableTree returns a new tree with the specified cache size and datastore.
    23  func NewMutableTree(db dbm.DB, cacheSize int) *MutableTree {
    24  	ndb := newNodeDB(db, cacheSize)
    25  	head := &ImmutableTree{ndb: ndb}
    26  
    27  	return &MutableTree{
    28  		ImmutableTree: head,
    29  		lastSaved:     head.clone(),
    30  		orphans:       map[string]int64{},
    31  		ndb:           ndb,
    32  	}
    33  }
    34  
    35  // IsEmpty returns whether or not the tree has any keys. Only trees that are
    36  // not empty can be saved.
    37  func (tree *MutableTree) IsEmpty() bool {
    38  	return tree.ImmutableTree.Size() == 0
    39  }
    40  
    41  // LatestVersion returns the latest version.
    42  func (tree *MutableTree) LatestVersion() int64 {
    43  	return tree.ndb.getLatestVersion()
    44  }
    45  
    46  // VersionExists returns whether or not a version exists.
    47  func (tree *MutableTree) VersionExists(version int64) bool {
    48  	return tree.ndb.getRoot(version) != nil
    49  }
    50  
    51  // AvailableVersions returns all available versions in ascending order
    52  func (tree *MutableTree) AvailableVersions() <-chan int64 {
    53  	return tree.ndb.getRootsCh()
    54  }
    55  
    56  // Hash returns the hash of the latest saved version of the tree, as returned
    57  // by SaveVersion. If no versions have been saved, Hash returns nil.
    58  func (tree *MutableTree) Hash() []byte {
    59  	if tree.version > 0 {
    60  		return tree.lastSaved.Hash()
    61  	}
    62  	return nil
    63  }
    64  
    65  // WorkingHash returns the hash of the current working tree.
    66  func (tree *MutableTree) WorkingHash() []byte {
    67  	return tree.ImmutableTree.Hash()
    68  }
    69  
    70  // String returns a string representation of the tree.
    71  func (tree *MutableTree) String() string {
    72  	return tree.ndb.String()
    73  }
    74  
    75  // Set sets a key in the working tree. Nil values are not supported.
    76  func (tree *MutableTree) Set(key, value []byte) bool {
    77  	orphaned, updated := tree.set(key, value)
    78  	tree.addOrphans(orphaned)
    79  	return updated
    80  }
    81  
    82  func (tree *MutableTree) set(key []byte, value []byte) (orphaned []*Node, updated bool) {
    83  	if value == nil {
    84  		panic(fmt.Sprintf("Attempt to store nil value at key '%s'", key))
    85  	}
    86  	if tree.ImmutableTree.root == nil {
    87  		tree.ImmutableTree.root = NewNode(key, value, tree.version+1)
    88  		return nil, false
    89  	}
    90  	tree.ImmutableTree.root, updated, orphaned = tree.recursiveSet(tree.ImmutableTree.root, key, value)
    91  
    92  	return orphaned, updated
    93  }
    94  
    95  func (tree *MutableTree) recursiveSet(node *Node, key []byte, value []byte) (
    96  	newSelf *Node, updated bool, orphaned []*Node,
    97  ) {
    98  	version := tree.version + 1
    99  
   100  	if node.isLeaf() {
   101  		switch bytes.Compare(key, node.key) {
   102  		case -1:
   103  			return &Node{
   104  				key:       node.key,
   105  				height:    1,
   106  				size:      2,
   107  				leftNode:  NewNode(key, value, version),
   108  				rightNode: node,
   109  				version:   version,
   110  			}, false, []*Node{}
   111  		case 1:
   112  			return &Node{
   113  				key:       key,
   114  				height:    1,
   115  				size:      2,
   116  				leftNode:  node,
   117  				rightNode: NewNode(key, value, version),
   118  				version:   version,
   119  			}, false, []*Node{}
   120  		default:
   121  			return NewNode(key, value, version), true, []*Node{node}
   122  		}
   123  	} else {
   124  		orphaned = append(orphaned, node)
   125  		node = node.clone(version)
   126  
   127  		if bytes.Compare(key, node.key) < 0 {
   128  			var leftOrphaned []*Node
   129  			node.leftNode, updated, leftOrphaned = tree.recursiveSet(node.getLeftNode(tree.ImmutableTree), key, value)
   130  			node.leftHash = nil // leftHash is yet unknown
   131  			orphaned = append(orphaned, leftOrphaned...)
   132  		} else {
   133  			var rightOrphaned []*Node
   134  			node.rightNode, updated, rightOrphaned = tree.recursiveSet(node.getRightNode(tree.ImmutableTree), key, value)
   135  			node.rightHash = nil // rightHash is yet unknown
   136  			orphaned = append(orphaned, rightOrphaned...)
   137  		}
   138  
   139  		if updated {
   140  			return node, updated, orphaned
   141  		}
   142  		node.calcHeightAndSize(tree.ImmutableTree)
   143  		newNode, balanceOrphaned := tree.balance(node)
   144  		return newNode, updated, append(orphaned, balanceOrphaned...)
   145  	}
   146  }
   147  
   148  // Remove removes a key from the working tree.
   149  func (tree *MutableTree) Remove(key []byte) ([]byte, bool) {
   150  	val, orphaned, removed := tree.remove(key)
   151  	tree.addOrphans(orphaned)
   152  	return val, removed
   153  }
   154  
   155  // remove tries to remove a key from the tree and if removed, returns its
   156  // value, nodes orphaned and 'true'.
   157  func (tree *MutableTree) remove(key []byte) (value []byte, orphans []*Node, removed bool) {
   158  	if tree.root == nil {
   159  		return nil, nil, false
   160  	}
   161  	newRootHash, newRoot, _, value, orphaned := tree.recursiveRemove(tree.root, key)
   162  	if len(orphaned) == 0 {
   163  		return nil, nil, false
   164  	}
   165  
   166  	if newRoot == nil && newRootHash != nil {
   167  		tree.root = tree.ndb.GetNode(newRootHash)
   168  	} else {
   169  		tree.root = newRoot
   170  	}
   171  	return value, orphaned, true
   172  }
   173  
   174  // removes the node corresponding to the passed key and balances the tree.
   175  // It returns:
   176  // - the hash of the new node (or nil if the node is the one removed)
   177  // - the node that replaces the orig. node after remove
   178  // - new leftmost leaf key for tree after successfully removing 'key' if changed.
   179  // - the removed value
   180  // - the orphaned nodes.
   181  func (tree *MutableTree) recursiveRemove(node *Node, key []byte) ([]byte, *Node, []byte, []byte, []*Node) {
   182  	version := tree.version + 1
   183  
   184  	if node.isLeaf() {
   185  		if bytes.Equal(key, node.key) {
   186  			return nil, nil, nil, node.value, []*Node{node}
   187  		}
   188  		return node.hash, node, nil, nil, nil
   189  	}
   190  
   191  	// node.key < key; we go to the left to find the key:
   192  	if bytes.Compare(key, node.key) < 0 {
   193  		newLeftHash, newLeftNode, newKey, value, orphaned := tree.recursiveRemove(node.getLeftNode(tree.ImmutableTree), key)
   194  
   195  		if len(orphaned) == 0 {
   196  			return node.hash, node, nil, value, orphaned
   197  		} else if newLeftHash == nil && newLeftNode == nil { // left node held value, was removed
   198  			return node.rightHash, node.rightNode, node.key, value, orphaned
   199  		}
   200  		orphaned = append(orphaned, node)
   201  
   202  		newNode := node.clone(version)
   203  		newNode.leftHash, newNode.leftNode = newLeftHash, newLeftNode
   204  		newNode.calcHeightAndSize(tree.ImmutableTree)
   205  		newNode, balanceOrphaned := tree.balance(newNode)
   206  
   207  		return newNode.hash, newNode, newKey, value, append(orphaned, balanceOrphaned...)
   208  	}
   209  	// node.key >= key; either found or look to the right:
   210  	newRightHash, newRightNode, newKey, value, orphaned := tree.recursiveRemove(node.getRightNode(tree.ImmutableTree), key)
   211  
   212  	if len(orphaned) == 0 {
   213  		return node.hash, node, nil, value, orphaned
   214  	} else if newRightHash == nil && newRightNode == nil { // right node held value, was removed
   215  		return node.leftHash, node.leftNode, nil, value, orphaned
   216  	}
   217  	orphaned = append(orphaned, node)
   218  
   219  	newNode := node.clone(version)
   220  	newNode.rightHash, newNode.rightNode = newRightHash, newRightNode
   221  	if newKey != nil {
   222  		newNode.key = newKey
   223  	}
   224  	newNode.calcHeightAndSize(tree.ImmutableTree)
   225  	newNode, balanceOrphaned := tree.balance(newNode)
   226  
   227  	return newNode.hash, newNode, nil, value, append(orphaned, balanceOrphaned...)
   228  }
   229  
   230  // Load the latest versioned tree from disk.
   231  func (tree *MutableTree) Load() (int64, error) {
   232  	return tree.LoadVersion(int64(0))
   233  }
   234  
   235  // LazyLoadVersion attempts to lazy load only the specified target version
   236  // without loading previous roots/versions. Lazy loading should be used in cases
   237  // where only reads are expected. Any writes to a lazy loaded tree may result in
   238  // unexpected behavior. If the targetVersion is non-positive, the latest version
   239  // will be loaded by default. If the latest version is non-positive, this method
   240  // performs a no-op. Otherwise, if the root does not exist, an error will be
   241  // returned.
   242  func (tree *MutableTree) LazyLoadVersion(targetVersion int64) (int64, error) {
   243  	latestVersion := tree.ndb.getLatestVersion()
   244  	if latestVersion < targetVersion {
   245  		return latestVersion, fmt.Errorf("wanted to load target %d but only found up to %d", targetVersion, latestVersion)
   246  	}
   247  
   248  	// no versions have been saved if the latest version is non-positive
   249  	if latestVersion <= 0 {
   250  		return 0, nil
   251  	}
   252  
   253  	// default to the latest version if the targeted version is non-positive
   254  	if targetVersion <= 0 {
   255  		targetVersion = latestVersion
   256  	}
   257  
   258  	rootHash := tree.ndb.getRoot(targetVersion)
   259  	if rootHash == nil {
   260  		return latestVersion, ErrVersionDoesNotExist
   261  	}
   262  
   263  	iTree := &ImmutableTree{
   264  		ndb:     tree.ndb,
   265  		version: targetVersion,
   266  		root:    tree.ndb.GetNode(rootHash),
   267  	}
   268  
   269  	tree.orphans = map[string]int64{}
   270  	tree.ImmutableTree = iTree
   271  	tree.lastSaved = iTree.clone()
   272  
   273  	return targetVersion, nil
   274  }
   275  
   276  // Returns the version number of the latest version found
   277  func (tree *MutableTree) LoadVersion(targetVersion int64) (int64, error) {
   278  	roots, err := tree.ndb.getRoots()
   279  	if err != nil {
   280  		return 0, err
   281  	}
   282  
   283  	if len(roots) == 0 {
   284  		return 0, nil
   285  	}
   286  
   287  	latestVersion := int64(0)
   288  
   289  	var latestRoot []byte
   290  	for version, r := range roots {
   291  		if version > latestVersion && (targetVersion == 0 || version <= targetVersion) {
   292  			latestVersion = version
   293  			latestRoot = r
   294  		}
   295  	}
   296  
   297  	if !(targetVersion == 0 || latestVersion == targetVersion) {
   298  		return latestVersion, fmt.Errorf("wanted to load target %v but only found up to %v",
   299  			targetVersion, latestVersion)
   300  	}
   301  
   302  	t := &ImmutableTree{
   303  		ndb:     tree.ndb,
   304  		version: latestVersion,
   305  	}
   306  
   307  	if len(latestRoot) != 0 {
   308  		t.root = tree.ndb.GetNode(latestRoot)
   309  	}
   310  
   311  	tree.orphans = map[string]int64{}
   312  	tree.ImmutableTree = t
   313  	tree.lastSaved = t.clone()
   314  
   315  	return latestVersion, nil
   316  }
   317  
   318  // LoadVersionOverwrite returns the version number of targetVersion.
   319  // Higher versions' data will be deleted.
   320  func (tree *MutableTree) LoadVersionForOverwriting(targetVersion int64) (int64, error) {
   321  	latestVersion, err := tree.LoadVersion(targetVersion)
   322  	if err != nil {
   323  		return latestVersion, err
   324  	}
   325  	tree.deleteVersionsFrom(targetVersion + 1)
   326  	return targetVersion, nil
   327  }
   328  
   329  // GetImmutable loads an ImmutableTree at a given version for querying
   330  func (tree *MutableTree) GetImmutable(version int64) (*ImmutableTree, error) {
   331  	rootHash := tree.ndb.getRoot(version)
   332  	if rootHash == nil {
   333  		return nil, ErrVersionDoesNotExist
   334  	} else if len(rootHash) == 0 {
   335  		return &ImmutableTree{
   336  			ndb:     tree.ndb,
   337  			version: version,
   338  		}, nil
   339  	}
   340  	return &ImmutableTree{
   341  		root:    tree.ndb.GetNode(rootHash),
   342  		ndb:     tree.ndb,
   343  		version: version,
   344  	}, nil
   345  }
   346  
   347  // Rollback resets the working tree to the latest saved version, discarding
   348  // any unsaved modifications.
   349  func (tree *MutableTree) Rollback() {
   350  	if tree.version > 0 {
   351  		tree.ImmutableTree = tree.lastSaved.clone()
   352  	} else {
   353  		tree.ImmutableTree = &ImmutableTree{ndb: tree.ndb, version: 0}
   354  	}
   355  	tree.orphans = map[string]int64{}
   356  }
   357  
   358  // GetVersioned gets the value at the specified key and version.
   359  func (tree *MutableTree) GetVersioned(key []byte, version int64) (
   360  	index int64, value []byte,
   361  ) {
   362  	if tree.VersionExists(version) {
   363  		t, err := tree.GetImmutable(version)
   364  		if err != nil {
   365  			return -1, nil
   366  		}
   367  		return t.Get(key)
   368  	}
   369  	return -1, nil
   370  }
   371  
   372  // SaveVersion saves a new tree version to disk, based on the current state of
   373  // the tree. Returns the hash and new version number.
   374  func (tree *MutableTree) SaveVersion() ([]byte, int64, error) {
   375  	version := tree.version + 1
   376  
   377  	if tree.VersionExists(version) {
   378  		// version already exists, throw an error if attempting to overwrite
   379  		// Same hash means idempotent.  Return success.
   380  		existingHash := tree.ndb.getRoot(version)
   381  		newHash := tree.WorkingHash()
   382  		if bytes.Equal(existingHash, newHash) {
   383  			tree.version = version
   384  			tree.ImmutableTree = tree.ImmutableTree.clone()
   385  			tree.lastSaved = tree.ImmutableTree.clone()
   386  			tree.orphans = map[string]int64{}
   387  			return existingHash, version, nil
   388  		}
   389  		return nil, version, fmt.Errorf("version %d was already saved to different hash %X (existing hash %X)",
   390  			version, newHash, existingHash)
   391  	}
   392  
   393  	if tree.root == nil {
   394  		// There can still be orphans, for example if the root is the node being
   395  		// removed.
   396  		debug("SAVE EMPTY TREE %v\n", version)
   397  		tree.ndb.SaveOrphans(version, tree.orphans)
   398  		tree.ndb.SaveEmptyRoot(version)
   399  	} else {
   400  		debug("SAVE TREE %v\n", version)
   401  		// Save the current tree.
   402  		tree.ndb.SaveBranch(tree.root)
   403  		tree.ndb.SaveOrphans(version, tree.orphans)
   404  		tree.ndb.SaveRoot(tree.root, version)
   405  	}
   406  	tree.ndb.Commit()
   407  	tree.version = version
   408  
   409  	// Set new working tree.
   410  	tree.ImmutableTree = tree.ImmutableTree.clone()
   411  	tree.lastSaved = tree.ImmutableTree.clone()
   412  	tree.orphans = map[string]int64{}
   413  
   414  	return tree.Hash(), version, nil
   415  }
   416  
   417  // DeleteVersion deletes a tree version from disk. The version can then no
   418  // longer be accessed.
   419  func (tree *MutableTree) DeleteVersion(version int64) error {
   420  	if version == 0 {
   421  		return errors.New("version must be greater than 0")
   422  	}
   423  	if version == tree.version {
   424  		return errors.New("cannot delete latest saved version (%d)", version)
   425  	}
   426  	if !tree.VersionExists(version) {
   427  		return errors.Wrap(ErrVersionDoesNotExist, "")
   428  	}
   429  
   430  	tree.ndb.DeleteVersion(version, true)
   431  	tree.ndb.Commit()
   432  
   433  	return nil
   434  }
   435  
   436  // deleteVersionsFrom deletes tree version from disk specified version to the
   437  // latest version. The versions can then no longer be accessed.
   438  func (tree *MutableTree) deleteVersionsFrom(version int64) error {
   439  	if version <= 0 {
   440  		return errors.New("version must be greater than 0")
   441  	}
   442  	newLatestVersion := version - 1
   443  	latestVersion := tree.ndb.getLatestVersion()
   444  	for ; version <= latestVersion; version++ {
   445  		if version == tree.version {
   446  			return errors.New("cannot delete latest saved version (%d)", version)
   447  		}
   448  		if !tree.VersionExists(version) {
   449  			return errors.Wrap(ErrVersionDoesNotExist, "")
   450  		}
   451  		tree.ndb.DeleteVersion(version, false)
   452  	}
   453  	tree.ndb.Commit()
   454  	tree.ndb.resetLatestVersion(newLatestVersion)
   455  	return nil
   456  }
   457  
   458  // Rotate right and return the new node and orphan.
   459  func (tree *MutableTree) rotateRight(node *Node) (*Node, *Node) {
   460  	version := tree.version + 1
   461  
   462  	// TODO: optimize balance & rotate.
   463  	node = node.clone(version)
   464  	orphaned := node.getLeftNode(tree.ImmutableTree)
   465  	newNode := orphaned.clone(version)
   466  
   467  	newNoderHash, newNoderCached := newNode.rightHash, newNode.rightNode
   468  	newNode.rightHash, newNode.rightNode = node.hash, node
   469  	node.leftHash, node.leftNode = newNoderHash, newNoderCached
   470  
   471  	node.calcHeightAndSize(tree.ImmutableTree)
   472  	newNode.calcHeightAndSize(tree.ImmutableTree)
   473  
   474  	return newNode, orphaned
   475  }
   476  
   477  // Rotate left and return the new node and orphan.
   478  func (tree *MutableTree) rotateLeft(node *Node) (*Node, *Node) {
   479  	version := tree.version + 1
   480  
   481  	// TODO: optimize balance & rotate.
   482  	node = node.clone(version)
   483  	orphaned := node.getRightNode(tree.ImmutableTree)
   484  	newNode := orphaned.clone(version)
   485  
   486  	newNodelHash, newNodelCached := newNode.leftHash, newNode.leftNode
   487  	newNode.leftHash, newNode.leftNode = node.hash, node
   488  	node.rightHash, node.rightNode = newNodelHash, newNodelCached
   489  
   490  	node.calcHeightAndSize(tree.ImmutableTree)
   491  	newNode.calcHeightAndSize(tree.ImmutableTree)
   492  
   493  	return newNode, orphaned
   494  }
   495  
   496  // NOTE: assumes that node can be modified
   497  // TODO: optimize balance & rotate
   498  func (tree *MutableTree) balance(node *Node) (newSelf *Node, orphaned []*Node) {
   499  	if node.persisted {
   500  		panic("Unexpected balance() call on persisted node")
   501  	}
   502  	balance := node.calcBalance(tree.ImmutableTree)
   503  
   504  	if balance > 1 {
   505  		if node.getLeftNode(tree.ImmutableTree).calcBalance(tree.ImmutableTree) >= 0 {
   506  			// Left Left Case
   507  			newNode, orphaned := tree.rotateRight(node)
   508  			return newNode, []*Node{orphaned}
   509  		}
   510  		// Left Right Case
   511  		var leftOrphaned *Node
   512  
   513  		left := node.getLeftNode(tree.ImmutableTree)
   514  		node.leftHash = nil
   515  		node.leftNode, leftOrphaned = tree.rotateLeft(left)
   516  		newNode, rightOrphaned := tree.rotateRight(node)
   517  
   518  		return newNode, []*Node{left, leftOrphaned, rightOrphaned}
   519  	}
   520  	if balance < -1 {
   521  		if node.getRightNode(tree.ImmutableTree).calcBalance(tree.ImmutableTree) <= 0 {
   522  			// Right Right Case
   523  			newNode, orphaned := tree.rotateLeft(node)
   524  			return newNode, []*Node{orphaned}
   525  		}
   526  		// Right Left Case
   527  		var rightOrphaned *Node
   528  
   529  		right := node.getRightNode(tree.ImmutableTree)
   530  		node.rightHash = nil
   531  		node.rightNode, rightOrphaned = tree.rotateRight(right)
   532  		newNode, leftOrphaned := tree.rotateLeft(node)
   533  
   534  		return newNode, []*Node{right, leftOrphaned, rightOrphaned}
   535  	}
   536  	// Nothing changed
   537  	return node, []*Node{}
   538  }
   539  
   540  func (tree *MutableTree) addOrphans(orphans []*Node) {
   541  	for _, node := range orphans {
   542  		if !node.persisted {
   543  			// We don't need to orphan nodes that were never persisted.
   544  			continue
   545  		}
   546  		if len(node.hash) == 0 {
   547  			panic("Expected to find node hash, but was empty")
   548  		}
   549  		tree.orphans[string(node.hash)] = node.version
   550  	}
   551  }