github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/core/mpt/trie_store.go (about)

     1  package mpt
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  
     8  	"github.com/nspcc-dev/neo-go/pkg/core/storage"
     9  	"github.com/nspcc-dev/neo-go/pkg/util"
    10  )
    11  
    12  // TrieStore is an MPT-based storage implementation for storing and retrieving
    13  // historic blockchain data. TrieStore is supposed to be used within transaction
    14  // script invocations only, thus only contract storage related operations are
    15  // supported. All storage-related operations are being performed using historical
    16  // storage data retrieved from MPT state. TrieStore is read-only and does not
    17  // support put-related operations, thus, it should always be wrapped into
    18  // MemCachedStore for proper puts handling. TrieStore never changes the provided
    19  // backend store.
    20  type TrieStore struct {
    21  	trie *Trie
    22  }
    23  
    24  // ErrForbiddenTrieStoreOperation is returned when operation is not supposed to
    25  // be performed over MPT-based Store.
    26  var ErrForbiddenTrieStoreOperation = errors.New("operation is not allowed to be performed over TrieStore")
    27  
    28  // NewTrieStore returns a new ready to use MPT-backed storage.
    29  func NewTrieStore(root util.Uint256, mode TrieMode, backed storage.Store) *TrieStore {
    30  	cache, ok := backed.(*storage.MemCachedStore)
    31  	if !ok {
    32  		cache = storage.NewMemCachedStore(backed)
    33  	}
    34  	tr := NewTrie(NewHashNode(root), mode, cache)
    35  	return &TrieStore{
    36  		trie: tr,
    37  	}
    38  }
    39  
    40  // Get implements the Store interface.
    41  func (m *TrieStore) Get(key []byte) ([]byte, error) {
    42  	if len(key) == 0 {
    43  		return nil, fmt.Errorf("%w: Get is supported only for contract storage items", ErrForbiddenTrieStoreOperation)
    44  	}
    45  	switch storage.KeyPrefix(key[0]) {
    46  	case storage.STStorage, storage.STTempStorage:
    47  		res, err := m.trie.Get(key[1:])
    48  		if err != nil && errors.Is(err, ErrNotFound) {
    49  			// Mimic the real storage behaviour.
    50  			return nil, storage.ErrKeyNotFound
    51  		}
    52  		return res, err
    53  	default:
    54  		return nil, fmt.Errorf("%w: Get is supported only for contract storage items", ErrForbiddenTrieStoreOperation)
    55  	}
    56  }
    57  
    58  // PutChangeSet implements the Store interface.
    59  func (m *TrieStore) PutChangeSet(puts map[string][]byte, stor map[string][]byte) error {
    60  	// Only Get and Seek should be supported, as TrieStore is read-only and is always
    61  	// should be wrapped by MemCachedStore to properly support put operations (if any).
    62  	return fmt.Errorf("%w: PutChangeSet is not supported", ErrForbiddenTrieStoreOperation)
    63  }
    64  
    65  // Seek implements the Store interface.
    66  func (m *TrieStore) Seek(rng storage.SeekRange, f func(k, v []byte) bool) {
    67  	prefix := storage.KeyPrefix(rng.Prefix[0])
    68  	if prefix != storage.STStorage && prefix != storage.STTempStorage { // Prefix is always non-empty.
    69  		panic(fmt.Errorf("%w: Seek is supported only for contract storage items", ErrForbiddenTrieStoreOperation))
    70  	}
    71  	prefixP := toNibbles(rng.Prefix[1:])
    72  	fromP := []byte{}
    73  	if len(rng.Start) > 0 {
    74  		fromP = toNibbles(rng.Start)
    75  	}
    76  	_, start, path, err := m.trie.getWithPath(m.trie.root, prefixP, false)
    77  	if err != nil {
    78  		// Failed to determine the start node => no matching items.
    79  		return
    80  	}
    81  	path = path[len(prefixP):]
    82  
    83  	if len(fromP) > 0 {
    84  		if len(path) <= len(fromP) && bytes.HasPrefix(fromP, path) {
    85  			fromP = fromP[len(path):]
    86  		} else if len(path) > len(fromP) && bytes.HasPrefix(path, fromP) {
    87  			fromP = []byte{}
    88  		} else {
    89  			cmp := bytes.Compare(path, fromP)
    90  			if cmp < 0 == rng.Backwards {
    91  				// No matching items.
    92  				return
    93  			}
    94  			fromP = []byte{}
    95  		}
    96  	}
    97  
    98  	b := NewBillet(m.trie.root.Hash(), m.trie.mode, 0, m.trie.Store)
    99  	process := func(pathToNode []byte, node Node, _ []byte) bool {
   100  		if leaf, ok := node.(*LeafNode); ok {
   101  			// (*Billet).traverse includes `from` path into the result if so. It's OK for Seek, so shouldn't be filtered out.
   102  			kv := storage.KeyValue{
   103  				Key:   append(bytes.Clone(rng.Prefix), pathToNode...), // Do not cut prefix.
   104  				Value: bytes.Clone(leaf.value),
   105  			}
   106  			return !f(kv.Key, kv.Value) // Should return whether to stop.
   107  		}
   108  		return false
   109  	}
   110  	_, err = b.traverse(start, path, fromP, process, false, rng.Backwards)
   111  	if err != nil && !errors.Is(err, errStop) {
   112  		panic(fmt.Errorf("failed to perform Seek operation on TrieStore: %w", err))
   113  	}
   114  }
   115  
   116  // SeekGC implements the Store interface.
   117  func (m *TrieStore) SeekGC(rng storage.SeekRange, keep func(k, v []byte) bool) error {
   118  	return fmt.Errorf("%w: SeekGC is not supported", ErrForbiddenTrieStoreOperation)
   119  }
   120  
   121  // Close implements the Store interface.
   122  func (m *TrieStore) Close() error {
   123  	m.trie = nil
   124  	return nil
   125  }