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 }