/** * @file * @copyright defined in aergo/LICENSE.txt */ package trie import ( "bytes" "fmt" "sync" "sync/atomic" "github.com/p4u/asmt/db" ) // LoadCache loads the first layers of the merkle tree given a root // This is called after a node restarts so that it doesnt become slow with db reads // LoadCache also updates the Root with the given root. func (s *Trie) LoadCache(root []byte) error { if s.db.Store == nil { return fmt.Errorf("DB not connected to trie") } s.db.liveCache = make(map[Hash][][]byte) ch := make(chan error, 1) s.loadCache(root, nil, 0, s.TrieHeight, ch) s.Root = root return <-ch } // loadCache loads the first layers of the merkle tree given a root func (s *Trie) loadCache(root []byte, batch [][]byte, iBatch, height int, ch chan<- (error)) { if height < s.CacheHeightLimit || len(root) == 0 { ch <- nil return } if height%4 == 0 { // Load the node from db s.db.lock.Lock() dbval := s.db.Store.Get(root[:HashLength]) s.db.lock.Unlock() if len(dbval) == 0 { ch <- fmt.Errorf("the trie node %x is unavailable in the disk db, db may be corrupted", root) return } //Store node in cache. var node Hash copy(node[:], root) batch = s.parseBatch(dbval) s.db.liveMux.Lock() s.db.liveCache[node] = batch s.db.liveMux.Unlock() iBatch = 0 if batch[0][0] == 1 { // if height == 0 this will also return ch <- nil return } } if iBatch != 0 && batch[iBatch][HashLength] == 1 { // Check if node is a leaf node ch <- nil } else { // Load subtree lnode, rnode := batch[2*iBatch+1], batch[2*iBatch+2] lch := make(chan error, 1) rch := make(chan error, 1) go s.loadCache(lnode, batch, 2*iBatch+1, height-1, lch) go s.loadCache(rnode, batch, 2*iBatch+2, height-1, rch) if err := <-lch; err != nil { ch <- err return } if err := <-rch; err != nil { ch <- err return } ch <- nil } } // Get fetches the value of a key by going down the current trie root. func (s *Trie) Get(key []byte) ([]byte, error) { s.lock.RLock() defer s.lock.RUnlock() s.atomicUpdate = false return s.get(s.Root, key, nil, 0, s.TrieHeight) } // GetWithRoot fetches the value of a key by going down for the specified root. func (s *Trie) GetWithRoot(key []byte, root []byte) ([]byte, error) { s.lock.RLock() defer s.lock.RUnlock() s.atomicUpdate = false if root == nil { root = s.Root } return s.get(root, key, nil, 0, s.TrieHeight) } // get fetches the value of a key given a trie root func (s *Trie) get(root, key []byte, batch [][]byte, iBatch, height int) ([]byte, error) { if len(root) == 0 { // the trie does not contain the key return nil, nil } // Fetch the children of the node batch, iBatch, lnode, rnode, isShortcut, err := s.loadChildren(root, height, iBatch, batch) if err != nil { return nil, err } if isShortcut { if bytes.Equal(lnode[:HashLength], key) { return rnode[:HashLength], nil } // also returns nil if height 0 is not a shortcut return nil, nil } if bitIsSet(key, s.TrieHeight-height) { return s.get(rnode, key, batch, 2*iBatch+2, height-1) } return s.get(lnode, key, batch, 2*iBatch+1, height-1) } // WalkResult contains the key and value obtained with a Walk() operation type WalkResult struct { Value []byte Key []byte } // Walk finds all the trie stored values from left to right and calls callback. // If callback returns a number diferent from 0, the walk will stop, else it will continue. func (s *Trie) Walk(root []byte, callback func(*WalkResult) int32) error { walkc := make(chan *WalkResult) s.lock.RLock() defer s.lock.RUnlock() if root == nil { root = s.Root } s.atomicUpdate = false finishedWalk := make(chan (bool), 1) stop := int32(0) wg := sync.WaitGroup{} // WaitGroup to avoid Walk() return before all callback executions are finished. go func() { for { select { case <-finishedWalk: return case value := <-walkc: stopCallback := callback(value) wg.Done() // In order to avoid data races we need to check the current value of stop, while at the // same time we store our callback value. If our callback value is 0 means that we have // override the previous non-zero value, so we need to restore it. if cv := atomic.SwapInt32(&stop, stopCallback); cv != 0 || stopCallback != 0 { if stopCallback == 0 { atomic.StoreInt32(&stop, cv) } // We need to return (instead of break) in order to stop iterating if some callback returns non zero return } } } }() err := s.walk(walkc, &stop, root, nil, 0, s.TrieHeight, &wg) finishedWalk <- true wg.Wait() return err } // walk fetches the value of a key given a trie root func (s *Trie) walk(walkc chan (*WalkResult), stop *int32, root []byte, batch [][]byte, ibatch, height int, wg *sync.WaitGroup) error { if len(root) == 0 || atomic.LoadInt32(stop) != 0 { // The sub tree is empty or stop walking return nil } // Fetch the children of the node batch, ibatch, lnode, rnode, isShortcut, err := s.loadChildren(root, height, ibatch, batch) if err != nil { return err } if isShortcut { wg.Add(1) walkc <- &WalkResult{Value: rnode[:HashLength], Key: lnode[:HashLength]} return nil } // Go left if err := s.walk(walkc, stop, lnode, batch, 2*ibatch+1, height-1, wg); err != nil { return err } // Go Right if err := s.walk(walkc, stop, rnode, batch, 2*ibatch+2, height-1, wg); err != nil { return err } return nil } // TrieRootExists returns true if the root exists in Database. func (s *Trie) TrieRootExists(root []byte) bool { s.db.lock.RLock() dbval := s.db.Store.Get(root) s.db.lock.RUnlock() return len(dbval) != 0 } // Commit stores the updated nodes to disk. // Commit should be called for every block otherwise past tries // are not recorded and it is not possible to revert to them // (except if AtomicUpdate is used, which records every state). func (s *Trie) Commit() error { if s.db.Store == nil { return fmt.Errorf("DB not connected to trie") } // NOTE The tx interface doesnt handle ErrTxnTooBig txn := s.db.Store.NewTx().(DbTx) s.StageUpdates(txn) txn.(db.Transaction).Commit() return nil } // StageUpdates requires a database transaction as input // Unlike Commit(), it doesnt commit the transaction // the database transaction MUST be commited otherwise the // state ROOT will not exist. func (s *Trie) StageUpdates(txn DbTx) { s.lock.Lock() defer s.lock.Unlock() // Commit the new nodes to database, clear updatedNodes and store the Root in pastTries for reverts. if !s.atomicUpdate { // if previously AtomicUpdate was called, then past tries is already updated s.updatePastTries() } s.db.commit(&txn) s.db.updatedNodes = make(map[Hash][][]byte) s.prevRoot = s.Root } // Stash rolls back the changes made by previous updates // and loads the cache from before the rollback. func (s *Trie) Stash(rollbackCache bool) error { s.lock.Lock() defer s.lock.Unlock() s.Root = s.prevRoot if rollbackCache { // Making a temporary liveCache requires it to be copied, so it's quicker // to just load the cache from DB if a block state root was incorrect. s.db.liveCache = make(map[Hash][][]byte) ch := make(chan error, 1) s.loadCache(s.Root, nil, 0, s.TrieHeight, ch) err := <-ch if err != nil { return err } } else { s.db.liveCache = make(map[Hash][][]byte) } s.db.updatedNodes = make(map[Hash][][]byte) // also stash past tries created by Atomic update for i := len(s.pastTries) - 1; i >= 0; i-- { if bytes.Equal(s.pastTries[i], s.Root) { break } else { // remove from past tries s.pastTries = s.pastTries[:len(s.pastTries)-1] } } return nil }