/** * @file * @copyright defined in aergo/LICENSE.txt */ package trie import ( "bytes" "fmt" ) // Revert rewinds the state tree to a previous version // All the nodes (subtree roots and values) reverted are deleted from the database. func (s *Trie) Revert(toOldRoot []byte) error { s.lock.RLock() defer s.lock.RUnlock() // safety precaution if reverting to a shortcut batch that might have been deleted s.atomicUpdate = false // so loadChildren doesnt return a copy batch, _, _, _, isShortcut, err := s.loadChildren(toOldRoot, s.TrieHeight, 0, nil) if err != nil { return err } //check if toOldRoot is in s.pastTries canRevert := false toIndex := 0 for i, r := range s.pastTries { if bytes.Equal(r, toOldRoot) { canRevert = true toIndex = i break } } if !canRevert || bytes.Equal(s.Root, toOldRoot) { return fmt.Errorf("The root cannot be reverted, because already latest of not in pastTries : current : %x, target : %x", s.Root, toOldRoot) } // For every node of toOldRoot, compare it to the equivalent node in other pasttries between toOldRoot and current s.Root. If a node is different, delete the one from pasttries s.db.nodesToRevert = make([][]byte, 0) for i := toIndex + 1; i < len(s.pastTries); i++ { ch := make(chan error, 1) s.maybeDeleteSubTree(toOldRoot, s.pastTries[i], s.TrieHeight, 0, nil, nil, ch) err := <-ch if err != nil { return err } } // NOTE The tx interface doesnt handle ErrTxnTooBig txn := s.db.Store.NewTx() for _, key := range s.db.nodesToRevert { txn.Delete(key[:HashLength]) } txn.Commit() s.pastTries = s.pastTries[:toIndex+1] s.Root = toOldRoot s.db.liveCache = make(map[Hash][][]byte) s.db.updatedNodes = make(map[Hash][][]byte) if isShortcut { // If toOldRoot is a shortcut batch, it is possible that // revert has deleted it if the key was ever stored at height0 // because in leafHash byte(0) = byte(256) s.db.Store.Set(toOldRoot, s.db.serializeBatch(batch)) } return nil } // maybeDeleteSubTree compares the subtree nodes of 2 tries and keeps only the older one func (s *Trie) maybeDeleteSubTree(original, maybeDelete []byte, height, iBatch int, batch, batch2 [][]byte, ch chan<- (error)) { if height == 0 { if !bytes.Equal(original, maybeDelete) && len(maybeDelete) != 0 { s.maybeDeleteRevertedNode(maybeDelete, 0) } ch <- nil return } if bytes.Equal(original, maybeDelete) || len(maybeDelete) == 0 { ch <- nil return } // if this point os reached, then the root of the batch is same // so the batch is also same. batch, iBatch, lnode, rnode, isShortcut, lerr := s.loadChildren(original, height, iBatch, batch) if lerr != nil { ch <- lerr return } batch2, _, lnode2, rnode2, isShortcut2, rerr := s.loadChildren(maybeDelete, height, iBatch, batch2) if rerr != nil { ch <- rerr return } if isShortcut != isShortcut2 { if isShortcut { ch1 := make(chan error, 1) s.deleteSubTree(maybeDelete, height, iBatch, batch2, ch1) err := <-ch1 if err != nil { ch <- err return } } else { s.maybeDeleteRevertedNode(maybeDelete, iBatch) } } else { if isShortcut { // Delete shortcut if not equal if !bytes.Equal(lnode, lnode2) || !bytes.Equal(rnode, rnode2) { s.maybeDeleteRevertedNode(maybeDelete, iBatch) } } else { // Delete subtree if not equal s.maybeDeleteRevertedNode(maybeDelete, iBatch) ch1 := make(chan error, 1) ch2 := make(chan error, 1) go s.maybeDeleteSubTree(lnode, lnode2, height-1, 2*iBatch+1, batch, batch2, ch1) go s.maybeDeleteSubTree(rnode, rnode2, height-1, 2*iBatch+2, batch, batch2, ch2) err1, err2 := <-ch1, <-ch2 if err1 != nil { ch <- err1 return } if err2 != nil { ch <- err2 return } } } ch <- nil } // deleteSubTree deletes all the nodes contained in a tree func (s *Trie) deleteSubTree(root []byte, height, iBatch int, batch [][]byte, ch chan<- (error)) { if len(root) == 0 || height == 0 { if height == 0 { s.maybeDeleteRevertedNode(root, 0) } ch <- nil return } batch, iBatch, lnode, rnode, isShortcut, err := s.loadChildren(root, height, iBatch, batch) if err != nil { ch <- err return } if !isShortcut { ch1 := make(chan error, 1) ch2 := make(chan error, 1) go s.deleteSubTree(lnode, height-1, 2*iBatch+1, batch, ch1) go s.deleteSubTree(rnode, height-1, 2*iBatch+2, batch, ch2) lerr := <-ch1 rerr := <-ch2 if lerr != nil { ch <- lerr return } if rerr != nil { ch <- rerr return } } s.maybeDeleteRevertedNode(root, iBatch) ch <- nil } // maybeDeleteRevertedNode adds the node to updatedNodes to be reverted // if it is a batch node at height%4 == 0 func (s *Trie) maybeDeleteRevertedNode(root []byte, iBatch int) { if iBatch == 0 { s.db.revertMux.Lock() s.db.nodesToRevert = append(s.db.nodesToRevert, root) s.db.revertMux.Unlock() } }