arnaucube
arnaucube/asmtPublic Self-hosted repository
asmt/trie_revert.go
trie_revert.goRaw
  1/**
  2 *  @file
  3 *  @copyright defined in aergo/LICENSE.txt
  4 */
  5
  6package trie
  7
  8import (
  9	"bytes"
 10	"fmt"
 11)
 12
 13// Revert rewinds the state tree to a previous version
 14// All the nodes (subtree roots and values) reverted are deleted from the database.
 15func (s *Trie) Revert(toOldRoot []byte) error {
 16	s.lock.RLock()
 17	defer s.lock.RUnlock()
 18
 19	// safety precaution if reverting to a shortcut batch that might have been deleted
 20	s.atomicUpdate = false // so loadChildren doesnt return a copy
 21	batch, _, _, _, isShortcut, err := s.loadChildren(toOldRoot, s.TrieHeight, 0, nil)
 22	if err != nil {
 23		return err
 24	}
 25
 26	//check if toOldRoot is in s.pastTries
 27	canRevert := false
 28	toIndex := 0
 29	for i, r := range s.pastTries {
 30		if bytes.Equal(r, toOldRoot) {
 31			canRevert = true
 32			toIndex = i
 33			break
 34		}
 35	}
 36	if !canRevert || bytes.Equal(s.Root, toOldRoot) {
 37		return fmt.Errorf("The root cannot be reverted, because already latest of not in pastTries : current : %x, target : %x", s.Root, toOldRoot)
 38	}
 39
 40	// 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
 41	s.db.nodesToRevert = make([][]byte, 0)
 42	for i := toIndex + 1; i < len(s.pastTries); i++ {
 43		ch := make(chan error, 1)
 44		s.maybeDeleteSubTree(toOldRoot, s.pastTries[i], s.TrieHeight, 0, nil, nil, ch)
 45		err := <-ch
 46		if err != nil {
 47			return err
 48		}
 49	}
 50	// NOTE The tx interface doesnt handle ErrTxnTooBig
 51	txn := s.db.Store.NewTx()
 52	for _, key := range s.db.nodesToRevert {
 53		txn.Delete(key[:HashLength])
 54	}
 55	txn.Commit()
 56
 57	s.pastTries = s.pastTries[:toIndex+1]
 58	s.Root = toOldRoot
 59	s.db.liveCache = make(map[Hash][][]byte)
 60	s.db.updatedNodes = make(map[Hash][][]byte)
 61	if isShortcut {
 62		// If toOldRoot is a shortcut batch, it is possible that
 63		// revert has deleted it if the key was ever stored at height0
 64		// because in leafHash byte(0) = byte(256)
 65		s.db.Store.Set(toOldRoot, s.db.serializeBatch(batch))
 66	}
 67	return nil
 68}
 69
 70// maybeDeleteSubTree compares the subtree nodes of 2 tries and keeps only the older one
 71func (s *Trie) maybeDeleteSubTree(original, maybeDelete []byte, height, iBatch int, batch, batch2 [][]byte, ch chan<- (error)) {
 72	if height == 0 {
 73		if !bytes.Equal(original, maybeDelete) && len(maybeDelete) != 0 {
 74			s.maybeDeleteRevertedNode(maybeDelete, 0)
 75		}
 76		ch <- nil
 77		return
 78	}
 79	if bytes.Equal(original, maybeDelete) || len(maybeDelete) == 0 {
 80		ch <- nil
 81		return
 82	}
 83	// if this point os reached, then the root of the batch is same
 84	// so the batch is also same.
 85	batch, iBatch, lnode, rnode, isShortcut, lerr := s.loadChildren(original, height, iBatch, batch)
 86	if lerr != nil {
 87		ch <- lerr
 88		return
 89	}
 90	batch2, _, lnode2, rnode2, isShortcut2, rerr := s.loadChildren(maybeDelete, height, iBatch, batch2)
 91	if rerr != nil {
 92		ch <- rerr
 93		return
 94	}
 95
 96	if isShortcut != isShortcut2 {
 97		if isShortcut {
 98			ch1 := make(chan error, 1)
 99			s.deleteSubTree(maybeDelete, height, iBatch, batch2, ch1)
100			err := <-ch1
101			if err != nil {
102				ch <- err
103				return
104			}
105		} else {
106			s.maybeDeleteRevertedNode(maybeDelete, iBatch)
107		}
108	} else {
109		if isShortcut {
110			// Delete shortcut if not equal
111			if !bytes.Equal(lnode, lnode2) || !bytes.Equal(rnode, rnode2) {
112				s.maybeDeleteRevertedNode(maybeDelete, iBatch)
113			}
114		} else {
115			// Delete subtree if not equal
116			s.maybeDeleteRevertedNode(maybeDelete, iBatch)
117			ch1 := make(chan error, 1)
118			ch2 := make(chan error, 1)
119			go s.maybeDeleteSubTree(lnode, lnode2, height-1, 2*iBatch+1, batch, batch2, ch1)
120			go s.maybeDeleteSubTree(rnode, rnode2, height-1, 2*iBatch+2, batch, batch2, ch2)
121			err1, err2 := <-ch1, <-ch2
122			if err1 != nil {
123				ch <- err1
124				return
125			}
126			if err2 != nil {
127				ch <- err2
128				return
129			}
130		}
131	}
132	ch <- nil
133}
134
135// deleteSubTree deletes all the nodes contained in a tree
136func (s *Trie) deleteSubTree(root []byte, height, iBatch int, batch [][]byte, ch chan<- (error)) {
137	if len(root) == 0 || height == 0 {
138		if height == 0 {
139			s.maybeDeleteRevertedNode(root, 0)
140		}
141		ch <- nil
142		return
143	}
144	batch, iBatch, lnode, rnode, isShortcut, err := s.loadChildren(root, height, iBatch, batch)
145	if err != nil {
146		ch <- err
147		return
148	}
149	if !isShortcut {
150		ch1 := make(chan error, 1)
151		ch2 := make(chan error, 1)
152		go s.deleteSubTree(lnode, height-1, 2*iBatch+1, batch, ch1)
153		go s.deleteSubTree(rnode, height-1, 2*iBatch+2, batch, ch2)
154		lerr := <-ch1
155		rerr := <-ch2
156
157		if lerr != nil {
158			ch <- lerr
159			return
160		}
161		if rerr != nil {
162			ch <- rerr
163			return
164		}
165	}
166	s.maybeDeleteRevertedNode(root, iBatch)
167	ch <- nil
168}
169
170// maybeDeleteRevertedNode adds the node to updatedNodes to be reverted
171// if it is a batch node at height%4 == 0
172func (s *Trie) maybeDeleteRevertedNode(root []byte, iBatch int) {
173	if iBatch == 0 {
174		s.db.revertMux.Lock()
175		s.db.nodesToRevert = append(s.db.nodesToRevert, root)
176		s.db.revertMux.Unlock()
177	}
178}