arnaucube
arnaucube/asmtPublic Self-hosted repository
asmt/smt/trie.go
smt/trie.goRaw
  1/**
  2 *  @file
  3 *  @copyright defined in aergo/LICENSE.txt
  4 */
  5
  6package trie
  7
  8import (
  9	"bytes"
 10	"fmt"
 11	"sync"
 12
 13	"github.com/p4u/asmt/db"
 14)
 15
 16// Trie is a modified sparse Merkle tree.
 17// Instead of storing values at the leaves of the tree,
 18// the values are stored at the highest subtree root that contains only that value.
 19// If the tree is sparse, this requires fewer hashing operations.
 20type Trie struct {
 21	db *CacheDB
 22	// Root is the current root of the smt.
 23	Root []byte
 24	// prevRoot is the root before the last update
 25	prevRoot []byte
 26	// lock is for the whole struct
 27	lock sync.RWMutex
 28	// hash is the hash function used in the trie
 29	hash func(data ...[]byte) []byte
 30	// TrieHeight is the number of bits in a key
 31	TrieHeight int
 32	// LoadDbCounter counts the nb of db reads in on update
 33	LoadDbCounter int
 34	// loadDbMux is a lock for LoadDbCounter
 35	loadDbMux sync.RWMutex
 36	// LoadCacheCounter counts the nb of cache reads in on update
 37	LoadCacheCounter int
 38	// liveCountMux is a lock fo LoadCacheCounter
 39	liveCountMux sync.RWMutex
 40	// counterOn is used to enable/diseable for efficiency
 41	counterOn bool
 42	// CacheHeightLimit is the number of tree levels we want to store in cache
 43	CacheHeightLimit int
 44	// pastTries stores the past maxPastTries trie roots to revert
 45	pastTries [][]byte
 46	// atomicUpdate, commit all the changes made by intermediate update calls
 47	atomicUpdate bool
 48}
 49
 50// NewSMT creates a new SMT given a keySize and a hash function.
 51func NewTrie(root []byte, hash func(data ...[]byte) []byte, store db.DB) *Trie {
 52	s := &Trie{
 53		hash:       hash,
 54		TrieHeight: len(hash([]byte("height"))) * 8, // hash any string to get output length
 55		counterOn:  false,
 56	}
 57	s.db = &CacheDB{
 58		liveCache:    make(map[Hash][][]byte),
 59		updatedNodes: make(map[Hash][][]byte),
 60		Store:        store,
 61	}
 62	// don't store any cache by default (contracts state don't use cache)
 63	s.CacheHeightLimit = s.TrieHeight + 1
 64	s.Root = root
 65	return s
 66}
 67
 68// Update adds and deletes a sorted list of keys and their values to the trie
 69// Adding and deleting can be simultaneous.
 70// To delete, set the value to DefaultLeaf.
 71// If Update is called multiple times, only the state after the last update
 72// is commited.
 73func (s *Trie) Update(keys, values [][]byte) ([]byte, error) {
 74	s.lock.Lock()
 75	defer s.lock.Unlock()
 76	s.atomicUpdate = false
 77	s.LoadDbCounter = 0
 78	s.LoadCacheCounter = 0
 79	ch := make(chan mresult, 1)
 80	s.update(s.Root, keys, values, nil, 0, s.TrieHeight, ch)
 81	result := <-ch
 82	if result.err != nil {
 83		return nil, result.err
 84	}
 85	if len(result.update) != 0 {
 86		s.Root = result.update[:HashLength]
 87	} else {
 88		s.Root = nil
 89	}
 90	return s.Root, nil
 91}
 92
 93// AtomicUpdate can be called multiple times and all the updated nodes will be commited
 94// and roots will be stored in past tries.
 95// Can be used for updating several blocks before committing to DB.
 96func (s *Trie) AtomicUpdate(keys, values [][]byte) ([]byte, error) {
 97	s.lock.Lock()
 98	defer s.lock.Unlock()
 99	s.atomicUpdate = true
100	s.LoadDbCounter = 0
101	s.LoadCacheCounter = 0
102	ch := make(chan mresult, 1)
103	s.update(s.Root, keys, values, nil, 0, s.TrieHeight, ch)
104	result := <-ch
105	if result.err != nil {
106		return nil, result.err
107	}
108	if len(result.update) != 0 {
109		s.Root = result.update[:HashLength]
110	} else {
111		s.Root = nil
112	}
113	s.updatePastTries()
114	return s.Root, nil
115}
116
117// mresult is used to contain the result of goroutines and is sent through a channel.
118type mresult struct {
119	update []byte
120	// flag if a node was deleted and a shortcut node maybe has to move up the tree
121	deleted bool
122	err     error
123}
124
125// update adds and deletes a sorted list of keys and their values to the trie.
126// Adding and deleting can be simultaneous.
127// To delete, set the value to DefaultLeaf.
128// It returns the root of the updated tree.
129func (s *Trie) update(root []byte, keys, values, batch [][]byte, iBatch, height int, ch chan<- (mresult)) {
130	if height == 0 {
131		if bytes.Equal(DefaultLeaf, values[0]) {
132			// Delete the key-value from the trie if it is being set to DefaultLeaf
133			// The value will be set to [] in batch by maybeMoveupShortcut or interiorHash
134			s.deleteOldNode(root, height, false)
135			ch <- mresult{nil, true, nil}
136		} else {
137			// create a new shortcut batch.
138			// simply storing the value will make it hard to move up the
139			// shortcut in case of sibling deletion
140			batch = make([][]byte, 31, 31)
141			node := s.leafHash(keys[0], values[0], root, batch, 0, height)
142			ch <- mresult{node, false, nil}
143		}
144		return
145	}
146
147	// Load the node to update
148	batch, iBatch, lnode, rnode, isShortcut, err := s.loadChildren(root, height, iBatch, batch)
149	if err != nil {
150		ch <- mresult{nil, false, err}
151		return
152	}
153	// Check if the keys are updating the shortcut node
154	if isShortcut {
155		keys, values = s.maybeAddShortcutToKV(keys, values, lnode[:HashLength], rnode[:HashLength])
156		if iBatch == 0 {
157			// shortcut is moving so it's root will change
158			s.deleteOldNode(root, height, false)
159		}
160		// The shortcut node was added to keys and values so consider this subtree default.
161		lnode, rnode = nil, nil
162		// update in the batch (set key, value to default so the next loadChildren is correct)
163		batch[2*iBatch+1] = nil
164		batch[2*iBatch+2] = nil
165		if len(keys) == 0 {
166			// Set true so that a potential sibling shortcut may move up.
167			ch <- mresult{nil, true, nil}
168			return
169		}
170	}
171	// Store shortcut node
172	if (len(lnode) == 0) && (len(rnode) == 0) && (len(keys) == 1) {
173		// We are adding 1 key to an empty subtree so store it as a shortcut
174		if bytes.Equal(DefaultLeaf, values[0]) {
175			ch <- mresult{nil, true, nil}
176		} else {
177			node := s.leafHash(keys[0], values[0], root, batch, iBatch, height)
178			ch <- mresult{node, false, nil}
179		}
180		return
181	}
182
183	// Split the keys array so each branch can be updated in parallel
184	lkeys, rkeys := s.splitKeys(keys, s.TrieHeight-height)
185	splitIndex := len(lkeys)
186	lvalues, rvalues := values[:splitIndex], values[splitIndex:]
187
188	switch {
189	case len(lkeys) == 0 && len(rkeys) > 0:
190		s.updateRight(lnode, rnode, root, keys, values, batch, iBatch, height, ch)
191	case len(lkeys) > 0 && len(rkeys) == 0:
192		s.updateLeft(lnode, rnode, root, keys, values, batch, iBatch, height, ch)
193	default:
194		s.updateParallel(lnode, rnode, root, lkeys, rkeys, lvalues, rvalues, batch, iBatch, height, ch)
195	}
196}
197
198// updateRight updates the right side of the tree
199func (s *Trie) updateRight(lnode, rnode, root []byte, keys, values, batch [][]byte, iBatch, height int, ch chan<- (mresult)) {
200	// all the keys go in the right subtree
201	newch := make(chan mresult, 1)
202	s.update(rnode, keys, values, batch, 2*iBatch+2, height-1, newch)
203	result := <-newch
204	if result.err != nil {
205		ch <- mresult{nil, false, result.err}
206		return
207	}
208	// Move up a shortcut node if necessary.
209	if result.deleted {
210		if s.maybeMoveUpShortcut(lnode, result.update, root, batch, iBatch, height, ch) {
211			return
212		}
213	}
214	node := s.interiorHash(lnode, result.update, root, batch, iBatch, height)
215	ch <- mresult{node, false, nil}
216}
217
218// updateLeft updates the left side of the tree
219func (s *Trie) updateLeft(lnode, rnode, root []byte, keys, values, batch [][]byte, iBatch, height int, ch chan<- (mresult)) {
220	// all the keys go in the left subtree
221	newch := make(chan mresult, 1)
222	s.update(lnode, keys, values, batch, 2*iBatch+1, height-1, newch)
223	result := <-newch
224	if result.err != nil {
225		ch <- mresult{nil, false, result.err}
226		return
227	}
228	// Move up a shortcut node if necessary.
229	if result.deleted {
230		if s.maybeMoveUpShortcut(result.update, rnode, root, batch, iBatch, height, ch) {
231			return
232		}
233	}
234	node := s.interiorHash(result.update, rnode, root, batch, iBatch, height)
235	ch <- mresult{node, false, nil}
236}
237
238// updateParallel updates both sides of the trie simultaneously
239func (s *Trie) updateParallel(lnode, rnode, root []byte, lkeys, rkeys, lvalues, rvalues, batch [][]byte, iBatch, height int, ch chan<- (mresult)) {
240	lch := make(chan mresult, 1)
241	rch := make(chan mresult, 1)
242	go s.update(lnode, lkeys, lvalues, batch, 2*iBatch+1, height-1, lch)
243	go s.update(rnode, rkeys, rvalues, batch, 2*iBatch+2, height-1, rch)
244	lresult := <-lch
245	rresult := <-rch
246	if lresult.err != nil {
247		ch <- mresult{nil, false, lresult.err}
248		return
249	}
250	if rresult.err != nil {
251		ch <- mresult{nil, false, rresult.err}
252		return
253	}
254
255	// Move up a shortcut node if it's sibling is default
256	if lresult.deleted || rresult.deleted {
257		if s.maybeMoveUpShortcut(lresult.update, rresult.update, root, batch, iBatch, height, ch) {
258			return
259		}
260	}
261	node := s.interiorHash(lresult.update, rresult.update, root, batch, iBatch, height)
262	ch <- mresult{node, false, nil}
263}
264
265// deleteOldNode deletes an old node that has been updated
266func (s *Trie) deleteOldNode(root []byte, height int, movingUp bool) {
267	var node Hash
268	copy(node[:], root)
269	if !s.atomicUpdate || movingUp {
270		// dont delete old nodes with atomic updated except when
271		// moving up a shortcut, we dont record every single move
272		s.db.updatedMux.Lock()
273		delete(s.db.updatedNodes, node)
274		s.db.updatedMux.Unlock()
275	}
276	if height >= s.CacheHeightLimit {
277		s.db.liveMux.Lock()
278		delete(s.db.liveCache, node)
279		s.db.liveMux.Unlock()
280	}
281}
282
283// splitKeys devides the array of keys into 2 so they can update left and right branches in parallel
284func (s *Trie) splitKeys(keys [][]byte, height int) ([][]byte, [][]byte) {
285	for i, key := range keys {
286		if bitIsSet(key, height) {
287			return keys[:i], keys[i:]
288		}
289	}
290	return keys, nil
291}
292
293// maybeMoveUpShortcut moves up a shortcut if it's sibling node is default
294func (s *Trie) maybeMoveUpShortcut(left, right, root []byte, batch [][]byte, iBatch, height int, ch chan<- (mresult)) bool {
295	if len(left) == 0 && len(right) == 0 {
296		// Both update and sibling are deleted subtrees
297		if iBatch == 0 {
298			// If the deleted subtrees are at the root, then delete it.
299			s.deleteOldNode(root, height, true)
300		} else {
301			batch[2*iBatch+1] = nil
302			batch[2*iBatch+2] = nil
303		}
304		ch <- mresult{nil, true, nil}
305		return true
306	} else if len(left) == 0 {
307		// If right is a shortcut move it up
308		if right[HashLength] == 1 {
309			s.moveUpShortcut(right, root, batch, iBatch, 2*iBatch+2, height, ch)
310			return true
311		}
312	} else if len(right) == 0 {
313		// If left is a shortcut move it up
314		if left[HashLength] == 1 {
315			s.moveUpShortcut(left, root, batch, iBatch, 2*iBatch+1, height, ch)
316			return true
317		}
318	}
319	return false
320}
321
322func (s *Trie) moveUpShortcut(shortcut, root []byte, batch [][]byte, iBatch, iShortcut, height int, ch chan<- (mresult)) {
323	// it doesn't matter if atomic update is true or false since the batch is node modified
324	_, _, shortcutKey, shortcutVal, _, err := s.loadChildren(shortcut, height-1, iShortcut, batch)
325	if err != nil {
326		ch <- mresult{nil, false, err}
327		return
328	}
329	// when moving up the shortcut, it's hash will change because height is +1
330	newShortcut := s.hash(shortcutKey[:HashLength], shortcutVal[:HashLength], []byte{byte(height)})
331	newShortcut = append(newShortcut, byte(1))
332
333	if iBatch == 0 {
334		// Modify batch to a shortcut batch
335		batch[0] = []byte{1}
336		batch[2*iBatch+1] = shortcutKey
337		batch[2*iBatch+2] = shortcutVal
338		batch[2*iShortcut+1] = nil
339		batch[2*iShortcut+2] = nil
340		// cache and updatedNodes deleted by store node
341		s.storeNode(batch, newShortcut, root, height)
342	} else if (height-1)%4 == 0 {
343		// move up shortcut and delete old batch
344		batch[2*iBatch+1] = shortcutKey
345		batch[2*iBatch+2] = shortcutVal
346		// set true so that AtomicUpdate can also delete a node moving up
347		// otherwise every nodes moved up is recorded
348		s.deleteOldNode(shortcut, height, true)
349	} else {
350		//move up shortcut
351		batch[2*iBatch+1] = shortcutKey
352		batch[2*iBatch+2] = shortcutVal
353		batch[2*iShortcut+1] = nil
354		batch[2*iShortcut+2] = nil
355	}
356	// Return the left sibling node to move it up
357	ch <- mresult{newShortcut, true, nil}
358}
359
360// maybeAddShortcutToKV adds a shortcut key to the keys array to be updated.
361// this is used when a subtree containing a shortcut node is being updated
362func (s *Trie) maybeAddShortcutToKV(keys, values [][]byte, shortcutKey, shortcutVal []byte) ([][]byte, [][]byte) {
363	newKeys := make([][]byte, 0, len(keys)+1)
364	newVals := make([][]byte, 0, len(keys)+1)
365
366	if bytes.Compare(shortcutKey, keys[0]) < 0 {
367		newKeys = append(newKeys, shortcutKey)
368		newKeys = append(newKeys, keys...)
369		newVals = append(newVals, shortcutVal)
370		newVals = append(newVals, values...)
371	} else if bytes.Compare(shortcutKey, keys[len(keys)-1]) > 0 {
372		newKeys = append(newKeys, keys...)
373		newKeys = append(newKeys, shortcutKey)
374		newVals = append(newVals, values...)
375		newVals = append(newVals, shortcutVal)
376	} else {
377		higher := false
378		for i, key := range keys {
379			if bytes.Equal(shortcutKey, key) {
380				if !bytes.Equal(DefaultLeaf, values[i]) {
381					// Do nothing if the shortcut is simply updated
382					return keys, values
383				}
384				// Delete shortcut if it is updated to DefaultLeaf
385				newKeys = append(newKeys, keys[:i]...)
386				newKeys = append(newKeys, keys[i+1:]...)
387				newVals = append(newVals, values[:i]...)
388				newVals = append(newVals, values[i+1:]...)
389			}
390			if !higher && bytes.Compare(shortcutKey, key) > 0 {
391				higher = true
392				continue
393			}
394			if higher && bytes.Compare(shortcutKey, key) < 0 {
395				// insert shortcut in slices
396				newKeys = append(newKeys, keys[:i]...)
397				newKeys = append(newKeys, shortcutKey)
398				newKeys = append(newKeys, keys[i:]...)
399				newVals = append(newVals, values[:i]...)
400				newVals = append(newVals, shortcutVal)
401				newVals = append(newVals, values[i:]...)
402				break
403			}
404		}
405	}
406	return newKeys, newVals
407}
408
409// loadChildren looks for the children of a node.
410// if the node is not stored in cache, it will be loaded from db.
411func (s *Trie) loadChildren(root []byte, height, iBatch int, batch [][]byte) ([][]byte, int, []byte, []byte, bool, error) {
412	isShortcut := false
413	if height%4 == 0 {
414		if len(root) == 0 {
415			// create a new default batch
416			batch = make([][]byte, 31, 31)
417			batch[0] = []byte{0}
418		} else {
419			var err error
420			batch, err = s.loadBatch(root)
421			if err != nil {
422				return nil, 0, nil, nil, false, err
423			}
424		}
425		iBatch = 0
426		if batch[0][0] == 1 {
427			isShortcut = true
428		}
429	} else {
430		if len(batch[iBatch]) != 0 && batch[iBatch][HashLength] == 1 {
431			isShortcut = true
432		}
433	}
434	return batch, iBatch, batch[2*iBatch+1], batch[2*iBatch+2], isShortcut, nil
435}
436
437// loadBatch fetches a batch of nodes in cache or db
438func (s *Trie) loadBatch(root []byte) ([][]byte, error) {
439	var node Hash
440	copy(node[:], root)
441
442	s.db.liveMux.RLock()
443	val, exists := s.db.liveCache[node]
444	s.db.liveMux.RUnlock()
445	if exists {
446		if s.counterOn {
447			s.liveCountMux.Lock()
448			s.LoadCacheCounter++
449			s.liveCountMux.Unlock()
450		}
451		if s.atomicUpdate {
452			// Return a copy so that Commit() doesnt have to be called at
453			// each block and still commit every state transition.
454			// Before Commit, the same batch is in liveCache and in updatedNodes
455			newVal := make([][]byte, 31, 31)
456			copy(newVal, val)
457			return newVal, nil
458		}
459		return val, nil
460	}
461	// checking updated nodes is useful if get() or update() is called twice in a row without db commit
462	s.db.updatedMux.RLock()
463	val, exists = s.db.updatedNodes[node]
464	s.db.updatedMux.RUnlock()
465	if exists {
466		if s.atomicUpdate {
467			// Return a copy so that Commit() doesnt have to be called at
468			// each block and still commit every state transition.
469			newVal := make([][]byte, 31, 31)
470			copy(newVal, val)
471			return newVal, nil
472		}
473		return val, nil
474	}
475	//Fetch node in disk database
476	if s.db.Store == nil {
477		return nil, fmt.Errorf("DB not connected to trie")
478	}
479	if s.counterOn {
480		s.loadDbMux.Lock()
481		s.LoadDbCounter++
482		s.loadDbMux.Unlock()
483	}
484	s.db.lock.Lock()
485	dbval := s.db.Store.Get(root[:HashLength])
486	s.db.lock.Unlock()
487	nodeSize := len(dbval)
488	if nodeSize != 0 {
489		return s.parseBatch(dbval), nil
490	}
491	return nil, fmt.Errorf("the trie node %x is unavailable in the disk db, db may be corrupted", root)
492}
493
494// parseBatch decodes the byte data into a slice of nodes and bitmap
495func (s *Trie) parseBatch(val []byte) [][]byte {
496	batch := make([][]byte, 31, 31)
497	bitmap := val[:4]
498	// check if the batch root is a shortcut
499	if bitIsSet(val, 31) {
500		batch[0] = []byte{1}
501		batch[1] = val[4 : 4+33]
502		batch[2] = val[4+33 : 4+33*2]
503	} else {
504		batch[0] = []byte{0}
505		j := 0
506		for i := 1; i <= 30; i++ {
507			if bitIsSet(bitmap, i-1) {
508				batch[i] = val[4+33*j : 4+33*(j+1)]
509				j++
510			}
511		}
512	}
513	return batch
514}
515
516// leafHash returns the hash of key_value_byte(height) concatenated, stores it in the updatedNodes and maybe in liveCache.
517// leafHash is never called for a default value. Default value should not be stored.
518func (s *Trie) leafHash(key, value, oldRoot []byte, batch [][]byte, iBatch, height int) []byte {
519	// byte(height) is here for 2 reasons.
520	// 1- to prevent potential problems with merkle proofs where if an account
521	// has the same address as a node, it would be possible to prove a
522	// different value for the account.
523	// 2- when accounts are added to the trie, accounts on their path get pushed down the tree
524	// with them. if an old account changes position from a shortcut batch to another
525	// shortcut batch of different height, if would be deleted when reverting.
526	h := s.hash(key, value, []byte{byte(height)})
527	h = append(h, byte(1)) // byte(1) is a flag for the shortcut
528	batch[2*iBatch+2] = append(value, byte(2))
529	batch[2*iBatch+1] = append(key, byte(2))
530	if height%4 == 0 {
531		batch[0] = []byte{1} // byte(1) is a flag for the shortcut batch
532		s.storeNode(batch, h, oldRoot, height)
533	}
534	return h
535}
536
537// storeNode stores a batch and deletes the old node from cache
538func (s *Trie) storeNode(batch [][]byte, h, oldRoot []byte, height int) {
539	if !bytes.Equal(h, oldRoot) {
540		var node Hash
541		copy(node[:], h)
542		// record new node
543		s.db.updatedMux.Lock()
544		s.db.updatedNodes[node] = batch
545		s.db.updatedMux.Unlock()
546		// Cache the shortcut node if it's height is over CacheHeightLimit
547		if height >= s.CacheHeightLimit {
548			s.db.liveMux.Lock()
549			s.db.liveCache[node] = batch
550			s.db.liveMux.Unlock()
551		}
552		s.deleteOldNode(oldRoot, height, false)
553	}
554}
555
556// interiorHash hashes 2 children to get the parent hash and stores it in the updatedNodes and maybe in liveCache.
557func (s *Trie) interiorHash(left, right, oldRoot []byte, batch [][]byte, iBatch, height int) []byte {
558	var h []byte
559	// left and right cannot both be default. It is handled by maybeMoveUpShortcut()
560	if len(left) == 0 {
561		h = s.hash(DefaultLeaf, right[:HashLength])
562	} else if len(right) == 0 {
563		h = s.hash(left[:HashLength], DefaultLeaf)
564	} else {
565		h = s.hash(left[:HashLength], right[:HashLength])
566	}
567	h = append(h, byte(0))
568	batch[2*iBatch+2] = right
569	batch[2*iBatch+1] = left
570	if height%4 == 0 {
571		batch[0] = []byte{0}
572		s.storeNode(batch, h, oldRoot, height)
573	}
574	return h
575}
576
577// updatePastTries appends the current Root to the list of past tries
578func (s *Trie) updatePastTries() {
579	if len(s.pastTries) >= maxPastTries {
580		copy(s.pastTries, s.pastTries[1:])
581		s.pastTries[len(s.pastTries)-1] = s.Root
582	} else {
583		s.pastTries = append(s.pastTries, s.Root)
584	}
585}