github.com/eris-ltd/erisdb@v0.25.0/storage/immutable_forest.go (about)

     1  package storage
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/xlab/treeprint"
     7  
     8  	lru "github.com/hashicorp/golang-lru"
     9  	dbm "github.com/tendermint/tendermint/libs/db"
    10  )
    11  
    12  type ImmutableForest struct {
    13  	// Store of tree prefix -> last commitID (version + hash) - serves as a set of all known trees and provides a global hash
    14  	commitsTree KVCallbackIterableReader
    15  	treeDB      dbm.DB
    16  	// Cache for frequently used trees
    17  	treeCache *lru.Cache
    18  	// Cache size is used in multiple places - for the LRU cache and node cache for any trees created - it probably
    19  	// makes sense for them to be roughly the same size
    20  	cacheSize int
    21  	// Determines whether we use LoadVersionForOverwriting on underlying MutableTrees - since ImmutableForest is used
    22  	// by MutableForest in a writing context sometimes we do need to load a version destructively
    23  	overwriting bool
    24  }
    25  
    26  type ForestOption func(*ImmutableForest)
    27  
    28  var WithOverwriting ForestOption = func(imf *ImmutableForest) { imf.overwriting = true }
    29  
    30  func NewImmutableForest(commitsTree KVCallbackIterableReader, treeDB dbm.DB, cacheSize int,
    31  	options ...ForestOption) (*ImmutableForest, error) {
    32  	cache, err := lru.New(cacheSize)
    33  	if err != nil {
    34  		return nil, fmt.Errorf("NewImmutableForest() could not create cache: %v", err)
    35  	}
    36  	imf := &ImmutableForest{
    37  		commitsTree: commitsTree,
    38  		treeDB:      treeDB,
    39  		treeCache:   cache,
    40  		cacheSize:   cacheSize,
    41  	}
    42  	for _, opt := range options {
    43  		opt(imf)
    44  	}
    45  	return imf, nil
    46  }
    47  
    48  func (imf *ImmutableForest) Iterate(start, end []byte, ascending bool, fn func(prefix []byte, tree KVCallbackIterableReader) error) error {
    49  	return imf.commitsTree.Iterate(start, end, ascending, func(prefix []byte, _ []byte) error {
    50  		rwt, err := imf.tree(prefix)
    51  		if err != nil {
    52  			return err
    53  		}
    54  		return fn(prefix, rwt)
    55  	})
    56  }
    57  
    58  func (imf *ImmutableForest) IterateRWTree(start, end []byte, ascending bool, fn func(prefix []byte, tree *RWTree) error) error {
    59  	return imf.commitsTree.Iterate(start, end, ascending, func(prefix []byte, _ []byte) error {
    60  		rwt, err := imf.tree(prefix)
    61  		if err != nil {
    62  			return err
    63  		}
    64  		return fn(prefix, rwt)
    65  	})
    66  }
    67  
    68  // Get the tree at prefix for making reads
    69  func (imf *ImmutableForest) Reader(prefix []byte) (KVCallbackIterableReader, error) {
    70  	return imf.tree(prefix)
    71  }
    72  
    73  // Lazy load tree
    74  func (imf *ImmutableForest) tree(prefix []byte) (*RWTree, error) {
    75  	// Try cache
    76  	if value, ok := imf.treeCache.Get(string(prefix)); ok {
    77  		return value.(*RWTree), nil
    78  	}
    79  	// Not in caches but non-negative version - we should be able to load into memory
    80  	return imf.loadOrCreateTree(prefix)
    81  }
    82  
    83  func (imf *ImmutableForest) commitID(prefix []byte) (*CommitID, error) {
    84  	bs := imf.commitsTree.Get(prefix)
    85  	if bs == nil {
    86  		return new(CommitID), nil
    87  	}
    88  	commitID, err := UnmarshalCommitID(bs)
    89  	if err != nil {
    90  		return nil, fmt.Errorf("could not get commitID for prefix %X: %v", prefix, err)
    91  	}
    92  	return commitID, nil
    93  }
    94  
    95  func (imf *ImmutableForest) loadOrCreateTree(prefix []byte) (*RWTree, error) {
    96  	const errHeader = "ImmutableForest.loadOrCreateTree():"
    97  	tree := imf.newTree(prefix)
    98  	commitID, err := imf.commitID(prefix)
    99  	if err != nil {
   100  		return nil, fmt.Errorf("%s %v", errHeader, err)
   101  	}
   102  	if commitID.Version == 0 {
   103  		// This is the first time we have been asked to load this tree
   104  		return imf.newTree(prefix), nil
   105  	}
   106  	err = tree.Load(commitID.Version, imf.overwriting)
   107  	if err != nil {
   108  		return nil, fmt.Errorf("%s could not load tree: %v", errHeader, err)
   109  	}
   110  	return tree, nil
   111  }
   112  
   113  // Create a new in-memory IAVL tree
   114  func (imf *ImmutableForest) newTree(prefix []byte) *RWTree {
   115  	p := string(prefix)
   116  	tree := NewRWTree(NewPrefixDB(imf.treeDB, p), imf.cacheSize)
   117  	imf.treeCache.Add(p, tree)
   118  	return tree
   119  }
   120  
   121  func (imf *ImmutableForest) Dump() string {
   122  	dump := treeprint.New()
   123  	AddTreePrintTree("Commits", dump, imf.commitsTree)
   124  	imf.Iterate(nil, nil, true, func(prefix []byte, tree KVCallbackIterableReader) error {
   125  		AddTreePrintTree(string(prefix), dump, tree)
   126  		return nil
   127  	})
   128  	return dump.String()
   129  }