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

     1  package stateroot
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"errors"
     7  	"fmt"
     8  	"sync"
     9  	"sync/atomic"
    10  	"time"
    11  
    12  	"github.com/nspcc-dev/neo-go/pkg/config"
    13  	"github.com/nspcc-dev/neo-go/pkg/config/netmode"
    14  	"github.com/nspcc-dev/neo-go/pkg/core/mpt"
    15  	"github.com/nspcc-dev/neo-go/pkg/core/state"
    16  	"github.com/nspcc-dev/neo-go/pkg/core/storage"
    17  	"github.com/nspcc-dev/neo-go/pkg/core/transaction"
    18  	"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
    19  	"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
    20  	"github.com/nspcc-dev/neo-go/pkg/smartcontract"
    21  	"github.com/nspcc-dev/neo-go/pkg/util"
    22  	"go.uber.org/zap"
    23  )
    24  
    25  type (
    26  	// VerifierFunc is a function that allows to check witness of account
    27  	// for Hashable item with GAS limit.
    28  	VerifierFunc func(util.Uint160, hash.Hashable, *transaction.Witness, int64) (int64, error)
    29  	// Module represents module for local processing of state roots.
    30  	Module struct {
    31  		Store    *storage.MemCachedStore
    32  		network  netmode.Magic
    33  		srInHead bool
    34  		mode     mpt.TrieMode
    35  		mpt      *mpt.Trie
    36  		verifier VerifierFunc
    37  		log      *zap.Logger
    38  
    39  		currentLocal    atomic.Value
    40  		localHeight     atomic.Uint32
    41  		validatedHeight atomic.Uint32
    42  
    43  		mtx  sync.RWMutex
    44  		keys []keyCache
    45  
    46  		updateValidatorsCb func(height uint32, publicKeys keys.PublicKeys)
    47  	}
    48  
    49  	keyCache struct {
    50  		height           uint32
    51  		validatorsKeys   keys.PublicKeys
    52  		validatorsHash   util.Uint160
    53  		validatorsScript []byte
    54  	}
    55  )
    56  
    57  // NewModule returns new instance of stateroot module.
    58  func NewModule(cfg config.Blockchain, verif VerifierFunc, log *zap.Logger, s *storage.MemCachedStore) *Module {
    59  	var mode mpt.TrieMode
    60  	if cfg.Ledger.KeepOnlyLatestState {
    61  		mode |= mpt.ModeLatest
    62  	}
    63  	if cfg.Ledger.RemoveUntraceableBlocks {
    64  		mode |= mpt.ModeGC
    65  	}
    66  	return &Module{
    67  		network:  cfg.Magic,
    68  		srInHead: cfg.StateRootInHeader,
    69  		mode:     mode,
    70  		verifier: verif,
    71  		log:      log,
    72  		Store:    s,
    73  	}
    74  }
    75  
    76  // GetState returns value at the specified key fom the MPT with the specified root.
    77  func (s *Module) GetState(root util.Uint256, key []byte) ([]byte, error) {
    78  	// Allow accessing old values, it's RO thing.
    79  	tr := mpt.NewTrie(mpt.NewHashNode(root), s.mode&^mpt.ModeGCFlag, storage.NewMemCachedStore(s.Store))
    80  	return tr.Get(key)
    81  }
    82  
    83  // FindStates returns a set of key-value pairs with keys matching the prefix starting
    84  // from the `prefix`+`start` path from MPT with the specified root. `max` is
    85  // the maximum number of elements to be returned. If nil `start` is specified, then the
    86  // item with the key equal to the prefix is included into the result; if empty `start` is specified,
    87  // then the item with the key equal to the prefix is not included into the result.
    88  // In case there are no results (prefix is unused, start is after the last available
    89  // element) mpt.ErrNotFound is returned.
    90  func (s *Module) FindStates(root util.Uint256, prefix, start []byte, max int) ([]storage.KeyValue, error) {
    91  	// Allow accessing old values, it's RO thing.
    92  	tr := mpt.NewTrie(mpt.NewHashNode(root), s.mode&^mpt.ModeGCFlag, storage.NewMemCachedStore(s.Store))
    93  	return tr.Find(prefix, start, max)
    94  }
    95  
    96  // SeekStates traverses over contract storage with the state based on the
    97  // specified root. `prefix` is expected to consist of contract ID and the desired
    98  // storage items prefix. `cont` is called for every matching key-value pair;
    99  // the resulting key does not include contract ID and the desired storage item
   100  // prefix (they are stripped to match the Blockchain's SeekStorage behaviour.
   101  // The result includes item with the key that equals to the `prefix` (if
   102  // such item is found in the storage). Traversal process is stopped when `false`
   103  // is returned from `cont`.
   104  func (s *Module) SeekStates(root util.Uint256, prefix []byte, cont func(k, v []byte) bool) {
   105  	// Allow accessing old values, it's RO thing.
   106  	store := mpt.NewTrieStore(root, s.mode&^mpt.ModeGCFlag, storage.NewMemCachedStore(s.Store))
   107  
   108  	// Tiny hack to satisfy TrieStore with the given prefix. This
   109  	// storage.STStorage prefix is a stub that will be stripped by the
   110  	// TrieStore.Seek while performing MPT traversal and isn't actually relevant
   111  	// here.
   112  	key := make([]byte, len(prefix)+1)
   113  	key[0] = byte(storage.STStorage)
   114  	copy(key[1:], prefix)
   115  
   116  	store.Seek(storage.SeekRange{Prefix: key}, func(k, v []byte) bool {
   117  		// Cut the prefix to match the Blockchain's SeekStorage behaviour.
   118  		return cont(k[len(key):], v)
   119  	})
   120  }
   121  
   122  // GetStateProof returns proof of having key in the MPT with the specified root.
   123  func (s *Module) GetStateProof(root util.Uint256, key []byte) ([][]byte, error) {
   124  	// Allow accessing old values, it's RO thing.
   125  	tr := mpt.NewTrie(mpt.NewHashNode(root), s.mode&^mpt.ModeGCFlag, storage.NewMemCachedStore(s.Store))
   126  	return tr.GetProof(key)
   127  }
   128  
   129  // GetStateRoot returns state root for a given height.
   130  func (s *Module) GetStateRoot(height uint32) (*state.MPTRoot, error) {
   131  	return s.getStateRoot(makeStateRootKey(height))
   132  }
   133  
   134  // GetLatestStateHeight returns the latest blockchain height by the given stateroot.
   135  func (s *Module) GetLatestStateHeight(root util.Uint256) (uint32, error) {
   136  	rootBytes := root.BytesBE()
   137  	rootStartOffset := 1 + 4 // stateroot version (1 byte) + stateroot index (4 bytes)
   138  	rootEndOffset := rootStartOffset + util.Uint256Size
   139  	var (
   140  		h       uint32
   141  		found   bool
   142  		rootKey = makeStateRootKey(s.localHeight.Load())
   143  	)
   144  	s.Store.Seek(storage.SeekRange{
   145  		Prefix:    []byte{rootKey[0]}, // DataMPTAux
   146  		Start:     rootKey[1:],        // Start is a value that should be appended to the Prefix
   147  		Backwards: true,
   148  	}, func(k, v []byte) bool {
   149  		if len(k) == 5 && bytes.Equal(v[rootStartOffset:rootEndOffset], rootBytes) {
   150  			h = binary.BigEndian.Uint32(k[1:]) // cut prefix DataMPTAux
   151  			found = true
   152  			return false
   153  		}
   154  		return true
   155  	})
   156  	if found {
   157  		return h, nil
   158  	}
   159  	return h, storage.ErrKeyNotFound
   160  }
   161  
   162  // CurrentLocalStateRoot returns hash of the local state root.
   163  func (s *Module) CurrentLocalStateRoot() util.Uint256 {
   164  	return s.currentLocal.Load().(util.Uint256)
   165  }
   166  
   167  // CurrentLocalHeight returns height of the local state root.
   168  func (s *Module) CurrentLocalHeight() uint32 {
   169  	return s.localHeight.Load()
   170  }
   171  
   172  // CurrentValidatedHeight returns current state root validated height.
   173  func (s *Module) CurrentValidatedHeight() uint32 {
   174  	return s.validatedHeight.Load()
   175  }
   176  
   177  // Init initializes state root module at the given height.
   178  func (s *Module) Init(height uint32) error {
   179  	data, err := s.Store.Get([]byte{byte(storage.DataMPTAux), prefixValidated})
   180  	if err == nil {
   181  		h := binary.LittleEndian.Uint32(data)
   182  		s.validatedHeight.Store(h)
   183  		updateStateHeightMetric(h)
   184  	}
   185  
   186  	if height == 0 {
   187  		s.mpt = mpt.NewTrie(nil, s.mode, s.Store)
   188  		s.currentLocal.Store(util.Uint256{})
   189  		return nil
   190  	}
   191  	r, err := s.getStateRoot(makeStateRootKey(height))
   192  	if err != nil {
   193  		return err
   194  	}
   195  	s.currentLocal.Store(r.Root)
   196  	s.localHeight.Store(r.Index)
   197  	s.mpt = mpt.NewTrie(mpt.NewHashNode(r.Root), s.mode, s.Store)
   198  	return nil
   199  }
   200  
   201  // CleanStorage removes all MPT-related data from the storage (MPT nodes, validated stateroots)
   202  // except local stateroot for the current height and GC flag. This method is aimed to clean
   203  // outdated MPT data before state sync process can be started.
   204  // Note: this method is aimed to be called for genesis block only, an error is returned otherwise.
   205  func (s *Module) CleanStorage() error {
   206  	lH := s.localHeight.Load()
   207  	if lH != 0 {
   208  		return fmt.Errorf("can't clean MPT data for non-genesis block: expected local stateroot height 0, got %d", lH)
   209  	}
   210  	b := storage.NewMemCachedStore(s.Store)
   211  	s.Store.Seek(storage.SeekRange{Prefix: []byte{byte(storage.DataMPT)}}, func(k, _ []byte) bool {
   212  		// #1468, but don't need to copy here, because it is done by Store.
   213  		b.Delete(k)
   214  		return true
   215  	})
   216  	_, err := b.Persist()
   217  	if err != nil {
   218  		return fmt.Errorf("failed to remove outdated MPT-reated items: %w", err)
   219  	}
   220  	return nil
   221  }
   222  
   223  // JumpToState performs jump to the state specified by given stateroot index.
   224  func (s *Module) JumpToState(sr *state.MPTRoot) {
   225  	s.addLocalStateRoot(s.Store, sr)
   226  
   227  	data := make([]byte, 4)
   228  	binary.LittleEndian.PutUint32(data, sr.Index)
   229  	s.Store.Put([]byte{byte(storage.DataMPTAux), prefixValidated}, data)
   230  	s.validatedHeight.Store(sr.Index)
   231  
   232  	s.currentLocal.Store(sr.Root)
   233  	s.localHeight.Store(sr.Index)
   234  	s.mpt = mpt.NewTrie(mpt.NewHashNode(sr.Root), s.mode, s.Store)
   235  }
   236  
   237  // ResetState resets MPT state to the given height.
   238  func (s *Module) ResetState(height uint32, cache *storage.MemCachedStore) error {
   239  	// Update local stateroot.
   240  	sr, err := s.GetStateRoot(height)
   241  	if err != nil {
   242  		return fmt.Errorf("failed to retrieve state root for height %d: %w", height, err)
   243  	}
   244  	s.addLocalStateRoot(cache, sr)
   245  
   246  	// Remove all stateroots newer than the given height.
   247  	srKey := makeStateRootKey(height)
   248  	var srSeen bool
   249  	cache.Seek(storage.SeekRange{
   250  		Prefix:    srKey[0:1],
   251  		Start:     srKey[1:5],
   252  		Backwards: false,
   253  	}, func(k, v []byte) bool {
   254  		if len(k) == 5 {
   255  			if srSeen {
   256  				cache.Delete(k)
   257  			} else if bytes.Equal(k, srKey) {
   258  				srSeen = true
   259  			}
   260  		}
   261  		return true
   262  	})
   263  
   264  	// Retrieve the most recent validated stateroot before the given height.
   265  	witnessesLenOffset := 1 /* version */ + 4 /* index */ + smartcontract.Hash256Len /* root */
   266  	var validated *uint32
   267  	cache.Seek(storage.SeekRange{
   268  		Prefix:    srKey[0:1],
   269  		Start:     srKey[1:5],
   270  		Backwards: true,
   271  	}, func(k, v []byte) bool {
   272  		if len(k) == 5 {
   273  			if len(v) > witnessesLenOffset && v[witnessesLenOffset] != 0 {
   274  				i := binary.BigEndian.Uint32(k[1:])
   275  				validated = &i
   276  				return false
   277  			}
   278  		}
   279  		return true
   280  	})
   281  	if validated != nil {
   282  		validatedBytes := make([]byte, 4)
   283  		binary.LittleEndian.PutUint32(validatedBytes, *validated)
   284  		cache.Put([]byte{byte(storage.DataMPTAux), prefixValidated}, validatedBytes)
   285  		s.validatedHeight.Store(*validated)
   286  	} else {
   287  		cache.Delete([]byte{byte(storage.DataMPTAux), prefixValidated})
   288  	}
   289  
   290  	s.currentLocal.Store(sr.Root)
   291  	s.localHeight.Store(sr.Index)
   292  	s.mpt = mpt.NewTrie(mpt.NewHashNode(sr.Root), s.mode, s.Store)
   293  
   294  	// Do not reset MPT nodes, leave the trie state itself as is.
   295  	return nil
   296  }
   297  
   298  // GC performs garbage collection.
   299  func (s *Module) GC(index uint32, store storage.Store) time.Duration {
   300  	if !s.mode.GC() {
   301  		panic("stateroot: GC invoked, but not enabled")
   302  	}
   303  	var removed int
   304  	var stored int64
   305  	s.log.Info("starting MPT garbage collection", zap.Uint32("index", index))
   306  	start := time.Now()
   307  	err := store.SeekGC(storage.SeekRange{
   308  		Prefix: []byte{byte(storage.DataMPT)},
   309  	}, func(k, v []byte) bool {
   310  		stored++
   311  		if !mpt.IsActiveValue(v) {
   312  			h := binary.LittleEndian.Uint32(v[len(v)-4:])
   313  			if h <= index {
   314  				removed++
   315  				stored--
   316  				return false
   317  			}
   318  		}
   319  		return true
   320  	})
   321  	dur := time.Since(start)
   322  	if err != nil {
   323  		s.log.Error("failed to flush MPT GC changeset", zap.Duration("time", dur), zap.Error(err))
   324  	} else {
   325  		s.log.Info("finished MPT garbage collection",
   326  			zap.Int("removed", removed),
   327  			zap.Int64("kept", stored),
   328  			zap.Duration("time", dur))
   329  	}
   330  	return dur
   331  }
   332  
   333  // AddMPTBatch updates using provided batch.
   334  func (s *Module) AddMPTBatch(index uint32, b mpt.Batch, cache *storage.MemCachedStore) (*mpt.Trie, *state.MPTRoot, error) {
   335  	mpt := *s.mpt
   336  	mpt.Store = cache
   337  	if _, err := mpt.PutBatch(b); err != nil {
   338  		return nil, nil, err
   339  	}
   340  	mpt.Flush(index)
   341  	sr := &state.MPTRoot{
   342  		Index: index,
   343  		Root:  mpt.StateRoot(),
   344  	}
   345  	s.addLocalStateRoot(cache, sr)
   346  	return &mpt, sr, nil
   347  }
   348  
   349  // UpdateCurrentLocal updates local caches using provided state root.
   350  func (s *Module) UpdateCurrentLocal(mpt *mpt.Trie, sr *state.MPTRoot) {
   351  	s.mpt = mpt
   352  	s.currentLocal.Store(sr.Root)
   353  	s.localHeight.Store(sr.Index)
   354  	if s.srInHead {
   355  		s.validatedHeight.Store(sr.Index)
   356  		updateStateHeightMetric(sr.Index)
   357  	}
   358  }
   359  
   360  // VerifyStateRoot checks if state root is valid.
   361  func (s *Module) VerifyStateRoot(r *state.MPTRoot) error {
   362  	_, err := s.getStateRoot(makeStateRootKey(r.Index - 1))
   363  	if err != nil {
   364  		return errors.New("can't get previous state root")
   365  	}
   366  	if len(r.Witness) != 1 {
   367  		return errors.New("no witness")
   368  	}
   369  	return s.verifyWitness(r)
   370  }
   371  
   372  const maxVerificationGAS = 2_00000000
   373  
   374  // verifyWitness verifies state root witness.
   375  func (s *Module) verifyWitness(r *state.MPTRoot) error {
   376  	s.mtx.Lock()
   377  	h := s.getKeyCacheForHeight(r.Index).validatorsHash
   378  	s.mtx.Unlock()
   379  	_, err := s.verifier(h, r, &r.Witness[0], maxVerificationGAS)
   380  	return err
   381  }