github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/libs/cosmos-sdk/store/mpt/store.go (about)

     1  package mpt
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"sync"
     7  
     8  	"github.com/VictoriaMetrics/fastcache"
     9  	ethcmn "github.com/ethereum/go-ethereum/common"
    10  	"github.com/ethereum/go-ethereum/common/prque"
    11  	"github.com/ethereum/go-ethereum/core/rawdb"
    12  	ethstate "github.com/ethereum/go-ethereum/core/state"
    13  	ethtypes "github.com/ethereum/go-ethereum/core/types"
    14  	"github.com/ethereum/go-ethereum/crypto"
    15  	"github.com/ethereum/go-ethereum/ethdb"
    16  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/codec"
    17  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/store/cachekv"
    18  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/store/tracekv"
    19  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/store/types"
    20  	sdkerrors "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types/errors"
    21  	"github.com/fibonacci-chain/fbc/libs/iavl"
    22  	abci "github.com/fibonacci-chain/fbc/libs/tendermint/abci/types"
    23  	"github.com/fibonacci-chain/fbc/libs/tendermint/crypto/merkle"
    24  	tmlog "github.com/fibonacci-chain/fbc/libs/tendermint/libs/log"
    25  	tmtypes "github.com/fibonacci-chain/fbc/libs/tendermint/types"
    26  )
    27  
    28  const (
    29  	FlagTrieAccStoreCache = "trie.account-store-cache"
    30  )
    31  
    32  var (
    33  	TrieAccStoreCache uint = 32 // MB
    34  )
    35  
    36  var cdc = codec.New()
    37  
    38  var (
    39  	_ types.KVStore       = (*MptStore)(nil)
    40  	_ types.CommitStore   = (*MptStore)(nil)
    41  	_ types.CommitKVStore = (*MptStore)(nil)
    42  	_ types.Queryable     = (*MptStore)(nil)
    43  )
    44  
    45  // MptStore Implements types.KVStore and CommitKVStore.
    46  // Its main purpose is to own the same interface as iavl store in libs/cosmos-sdk/store/iavl/iavl_store.go
    47  type MptStore struct {
    48  	trie    ethstate.Trie
    49  	db      ethstate.Database
    50  	triegc  *prque.Prque
    51  	logger  tmlog.Logger
    52  	kvCache *fastcache.Cache
    53  
    54  	prefetcher   *TriePrefetcher
    55  	originalRoot ethcmn.Hash
    56  	exitSignal   chan struct{}
    57  
    58  	version      int64
    59  	startVersion int64
    60  	cmLock       sync.Mutex
    61  }
    62  
    63  func (ms *MptStore) CommitterCommitMap(deltaMap iavl.TreeDeltaMap) (_ types.CommitID, _ iavl.TreeDeltaMap) {
    64  	return
    65  }
    66  
    67  func (ms *MptStore) GetFlatKVReadTime() int {
    68  	return 0
    69  }
    70  
    71  func (ms *MptStore) GetFlatKVWriteTime() int {
    72  	return 0
    73  }
    74  
    75  func (ms *MptStore) GetFlatKVReadCount() int {
    76  	return 0
    77  }
    78  
    79  func (ms *MptStore) GetFlatKVWriteCount() int {
    80  	return 0
    81  }
    82  
    83  func NewMptStore(logger tmlog.Logger, id types.CommitID) (*MptStore, error) {
    84  	db := InstanceOfMptStore()
    85  	return generateMptStore(logger, id, db)
    86  }
    87  
    88  func generateMptStore(logger tmlog.Logger, id types.CommitID, db ethstate.Database) (*MptStore, error) {
    89  	triegc := prque.New(nil)
    90  	mptStore := &MptStore{
    91  		db:         db,
    92  		triegc:     triegc,
    93  		logger:     logger,
    94  		kvCache:    fastcache.New(int(TrieAccStoreCache) * 1024 * 1024),
    95  		exitSignal: make(chan struct{}),
    96  	}
    97  	err := mptStore.openTrie(id)
    98  
    99  	return mptStore, err
   100  }
   101  
   102  func mockMptStore(logger tmlog.Logger, id types.CommitID) (*MptStore, error) {
   103  	db := ethstate.NewDatabase(rawdb.NewMemoryDatabase())
   104  	return generateMptStore(logger, id, db)
   105  }
   106  
   107  func (ms *MptStore) openTrie(id types.CommitID) error {
   108  	latestStoredHeight := ms.GetLatestStoredBlockHeight()
   109  	openHeight := uint64(id.Version)
   110  	if latestStoredHeight > 0 && openHeight > latestStoredHeight {
   111  		return fmt.Errorf("fail to open mpt trie, the target version is: %d, the latest stored version is: %d, "+
   112  			"please repair", openHeight, latestStoredHeight)
   113  	}
   114  
   115  	openedRootHash := ms.GetMptRootHash(openHeight)
   116  	tr, err := ms.db.OpenTrie(openedRootHash)
   117  	if err != nil {
   118  		panic("Fail to open root mpt: " + err.Error())
   119  	}
   120  
   121  	ms.trie = tr
   122  	ms.version = id.Version
   123  	ms.startVersion = id.Version
   124  	ms.originalRoot = openedRootHash
   125  
   126  	if ms.logger != nil {
   127  		ms.logger.Info("open acc mpt trie", "version", openHeight, "trieHash", openedRootHash)
   128  	}
   129  
   130  	ms.StartPrefetcher("mptStore")
   131  	ms.prefetchData()
   132  
   133  	return nil
   134  }
   135  
   136  func (ms *MptStore) GetImmutable(height int64) (*MptStore, error) {
   137  	rootHash := ms.GetMptRootHash(uint64(height))
   138  	tr, err := ms.db.OpenTrie(rootHash)
   139  	if err != nil {
   140  		return nil, fmt.Errorf("Fail to open root mpt: " + err.Error())
   141  	}
   142  	mptStore := &MptStore{
   143  		db:           ms.db,
   144  		trie:         tr,
   145  		originalRoot: rootHash,
   146  		exitSignal:   make(chan struct{}),
   147  		version:      height,
   148  		startVersion: height,
   149  	}
   150  
   151  	return mptStore, nil
   152  }
   153  
   154  /*
   155  *  implement KVStore
   156   */
   157  func (ms *MptStore) GetStoreType() types.StoreType {
   158  	return StoreTypeMPT
   159  }
   160  
   161  func (ms *MptStore) CacheWrap() types.CacheWrap {
   162  	//TODO implement me
   163  	return cachekv.NewStore(ms)
   164  }
   165  
   166  func (ms *MptStore) CacheWrapWithTrace(w io.Writer, tc types.TraceContext) types.CacheWrap {
   167  	//TODO implement me
   168  	return cachekv.NewStore(tracekv.NewStore(ms, w, tc))
   169  }
   170  
   171  func (ms *MptStore) Get(key []byte) []byte {
   172  	if ms.kvCache != nil {
   173  		if enc := ms.kvCache.Get(nil, key); len(enc) > 0 {
   174  			return enc
   175  		}
   176  	}
   177  
   178  	value, err := ms.trie.TryGet(key)
   179  	if err != nil {
   180  		return nil
   181  	}
   182  	if ms.kvCache != nil && value != nil {
   183  		ms.kvCache.Set(key, value)
   184  	}
   185  
   186  	return value
   187  }
   188  
   189  func (ms *MptStore) Has(key []byte) bool {
   190  	if ms.kvCache != nil {
   191  		if ms.kvCache.Has(key) {
   192  			return true
   193  		}
   194  	}
   195  
   196  	return ms.Get(key) != nil
   197  }
   198  
   199  func (ms *MptStore) Set(key, value []byte) {
   200  	types.AssertValidValue(value)
   201  
   202  	if ms.prefetcher != nil {
   203  		ms.prefetcher.Used(ms.originalRoot, [][]byte{key})
   204  	}
   205  	if ms.kvCache != nil {
   206  		ms.kvCache.Set(key, value)
   207  	}
   208  	err := ms.trie.TryUpdate(key, value)
   209  	if err != nil {
   210  		return
   211  	}
   212  	return
   213  }
   214  
   215  func (ms *MptStore) Delete(key []byte) {
   216  	if ms.prefetcher != nil {
   217  		ms.prefetcher.Used(ms.originalRoot, [][]byte{key})
   218  	}
   219  
   220  	if ms.kvCache != nil {
   221  		ms.kvCache.Del(key)
   222  	}
   223  	err := ms.trie.TryDelete(key)
   224  	if err != nil {
   225  		return
   226  	}
   227  }
   228  
   229  func (ms *MptStore) Iterator(start, end []byte) types.Iterator {
   230  	return newMptIterator(ms.trie, start, end)
   231  }
   232  
   233  func (ms *MptStore) ReverseIterator(start, end []byte) types.Iterator {
   234  	return newMptIterator(ms.trie, start, end)
   235  }
   236  
   237  /*
   238  *  implement CommitStore, CommitKVStore
   239   */
   240  func (ms *MptStore) CommitterCommit(delta *iavl.TreeDelta) (types.CommitID, *iavl.TreeDelta) {
   241  	ms.version++
   242  
   243  	// stop pre round prefetch
   244  	ms.StopPrefetcher()
   245  
   246  	root, err := ms.trie.Commit(nil)
   247  	if err != nil {
   248  		panic("fail to commit trie data: " + err.Error())
   249  	}
   250  	ms.SetMptRootHash(uint64(ms.version), root)
   251  	ms.originalRoot = root
   252  
   253  	// TODO: use a thread to push data to database
   254  	// push data to database
   255  	ms.PushData2Database(ms.version)
   256  
   257  	// start next found prefetch
   258  	ms.StartPrefetcher("mptStore")
   259  
   260  	return types.CommitID{
   261  		Version: ms.version,
   262  		Hash:    root.Bytes(),
   263  	}, nil
   264  }
   265  
   266  func (ms *MptStore) LastCommitID() types.CommitID {
   267  	return types.CommitID{
   268  		Version: ms.version,
   269  		Hash:    ms.trie.Hash().Bytes(),
   270  	}
   271  }
   272  
   273  func (ms *MptStore) LastCommitVersion() int64 {
   274  	return ms.version
   275  }
   276  
   277  func (ms *MptStore) SetPruning(options types.PruningOptions) {
   278  	panic("cannot set pruning options on an initialized MPT store")
   279  }
   280  
   281  func (ms *MptStore) GetDBWriteCount() int {
   282  	return 0
   283  }
   284  
   285  func (ms *MptStore) GetDBReadCount() int {
   286  	return 0
   287  }
   288  
   289  func (ms *MptStore) GetNodeReadCount() int {
   290  	return 0
   291  }
   292  
   293  func (ms *MptStore) ResetCount() {
   294  	return
   295  }
   296  
   297  func (ms *MptStore) GetDBReadTime() int {
   298  	return 0
   299  }
   300  
   301  // PushData2Database writes all associated state in cache to the database
   302  func (ms *MptStore) PushData2Database(curHeight int64) {
   303  	ms.cmLock.Lock()
   304  	defer ms.cmLock.Unlock()
   305  
   306  	curMptRoot := ms.GetMptRootHash(uint64(curHeight))
   307  	if TrieDirtyDisabled {
   308  		// If we're running an archive node, always flush
   309  		ms.fullNodePersist(curMptRoot, curHeight)
   310  	} else {
   311  		ms.otherNodePersist(curMptRoot, curHeight)
   312  	}
   313  }
   314  
   315  // fullNodePersist persist data without pruning
   316  func (ms *MptStore) fullNodePersist(curMptRoot ethcmn.Hash, curHeight int64) {
   317  	if curMptRoot == (ethcmn.Hash{}) || curMptRoot == ethtypes.EmptyRootHash {
   318  		curMptRoot = ethcmn.Hash{}
   319  	} else {
   320  		if err := ms.db.TrieDB().Commit(curMptRoot, false, nil); err != nil {
   321  			panic("fail to commit mpt data: " + err.Error())
   322  		}
   323  	}
   324  	ms.SetLatestStoredBlockHeight(uint64(curHeight))
   325  	if ms.logger != nil {
   326  		ms.logger.Info("sync push acc data to db", "block", curHeight, "trieHash", curMptRoot)
   327  	}
   328  }
   329  
   330  // otherNodePersist persist data with pruning
   331  func (ms *MptStore) otherNodePersist(curMptRoot ethcmn.Hash, curHeight int64) {
   332  	triedb := ms.db.TrieDB()
   333  
   334  	// Full but not archive node, do proper garbage collection
   335  	triedb.Reference(curMptRoot, ethcmn.Hash{}) // metadata reference to keep trie alive
   336  	ms.triegc.Push(curMptRoot, -int64(curHeight))
   337  
   338  	if curHeight > TriesInMemory {
   339  		// If we exceeded our memory allowance, flush matured singleton nodes to disk
   340  		var (
   341  			nodes, imgs = triedb.Size()
   342  			nodesLimit  = ethcmn.StorageSize(TrieNodesLimit) * 1024 * 1024
   343  			imgsLimit   = ethcmn.StorageSize(TrieImgsLimit) * 1024 * 1024
   344  		)
   345  
   346  		if nodes > nodesLimit || imgs > imgsLimit {
   347  			triedb.Cap(nodesLimit - ethdb.IdealBatchSize)
   348  		}
   349  		// Find the next state trie we need to commit
   350  		chosen := curHeight - TriesInMemory
   351  
   352  		// we start at startVersion, but the chosen height may be startVersion - triesInMemory
   353  		if chosen <= ms.startVersion {
   354  			return
   355  		}
   356  
   357  		// If we exceeded out time allowance, flush an entire trie to disk
   358  		if chosen%TrieCommitGap == 0 {
   359  			// If the header is missing (canonical chain behind), we're reorging a low
   360  			// diff sidechain. Suspend committing until this operation is completed.
   361  			chRoot := ms.GetMptRootHash(uint64(chosen))
   362  			if chRoot == (ethcmn.Hash{}) || chRoot == ethtypes.EmptyRootHash {
   363  				chRoot = ethcmn.Hash{}
   364  			} else {
   365  				// Flush an entire trie and restart the counters, it's not a thread safe process,
   366  				// cannot use a go thread to run, or it will lead 'fatal error: concurrent map read and map write' error
   367  				if err := triedb.Commit(chRoot, true, nil); err != nil {
   368  					panic("fail to commit mpt data: " + err.Error())
   369  				}
   370  			}
   371  			ms.SetLatestStoredBlockHeight(uint64(chosen))
   372  			if ms.logger != nil {
   373  				ms.logger.Info("async push acc data to db", "block", chosen, "trieHash", chRoot)
   374  			}
   375  		}
   376  
   377  		// Garbage collect anything below our required write retention
   378  		for !ms.triegc.Empty() {
   379  			root, number := ms.triegc.Pop()
   380  			if int64(-number) > chosen {
   381  				ms.triegc.Push(root, number)
   382  				break
   383  			}
   384  			triedb.Dereference(root.(ethcmn.Hash))
   385  		}
   386  	}
   387  }
   388  func (ms *MptStore) CurrentVersion() int64 {
   389  	return ms.version
   390  }
   391  
   392  func (ms *MptStore) OnStop() error {
   393  	return ms.StopWithVersion(ms.version)
   394  }
   395  
   396  // Stop stops the blockchain service. If any imports are currently in progress
   397  // it will abort them using the procInterrupt.
   398  func (ms *MptStore) StopWithVersion(targetVersion int64) error {
   399  	curVersion := uint64(targetVersion)
   400  	ms.exitSignal <- struct{}{}
   401  	ms.StopPrefetcher()
   402  
   403  	ms.cmLock.Lock()
   404  	defer ms.cmLock.Unlock()
   405  
   406  	if !tmtypes.HigherThanMars(ms.version) && !TrieWriteAhead {
   407  		return nil
   408  	}
   409  
   410  	// Ensure the state of a recent block is also stored to disk before exiting.
   411  	if !TrieDirtyDisabled {
   412  		triedb := ms.db.TrieDB()
   413  		oecStartHeight := uint64(tmtypes.GetStartBlockHeight()) // start height of oec
   414  
   415  		latestStoreVersion := ms.GetLatestStoredBlockHeight()
   416  
   417  		for version := latestStoreVersion; version <= curVersion; version++ {
   418  			if version <= oecStartHeight || version <= uint64(ms.startVersion) {
   419  				continue
   420  			}
   421  
   422  			recentMptRoot := ms.GetMptRootHash(version)
   423  			if recentMptRoot == (ethcmn.Hash{}) || recentMptRoot == ethtypes.EmptyRootHash {
   424  				recentMptRoot = ethcmn.Hash{}
   425  			} else {
   426  				if err := triedb.Commit(recentMptRoot, true, nil); err != nil {
   427  					if ms.logger != nil {
   428  						ms.logger.Error("Failed to commit recent state trie", "err", err)
   429  					}
   430  					break
   431  				}
   432  			}
   433  			ms.SetLatestStoredBlockHeight(version)
   434  			if ms.logger != nil {
   435  				ms.logger.Info("Writing acc cached state to disk", "block", version, "trieHash", recentMptRoot)
   436  			}
   437  		}
   438  
   439  		for !ms.triegc.Empty() {
   440  			ms.db.TrieDB().Dereference(ms.triegc.PopItem().(ethcmn.Hash))
   441  		}
   442  	}
   443  
   444  	return nil
   445  }
   446  
   447  /*
   448  *  implement Queryable
   449   */
   450  func (ms *MptStore) Query(req abci.RequestQuery) (res abci.ResponseQuery) {
   451  	if len(req.Data) == 0 {
   452  		return sdkerrors.QueryResult(sdkerrors.Wrap(sdkerrors.ErrTxDecode, "query cannot be zero length"))
   453  	}
   454  
   455  	height := ms.getlatestHeight(uint64(req.Height))
   456  	res.Height = int64(height)
   457  
   458  	// store the height we chose in the response, with 0 being changed to the
   459  	// latest height
   460  	trie, err := ms.getTrieByHeight(height)
   461  	if err != nil {
   462  		return sdkerrors.QueryResult(sdkerrors.Wrapf(sdkerrors.ErrInvalidVersion, "open trie failed: %s", err.Error()))
   463  	}
   464  
   465  	switch req.Path {
   466  	case "/key": // get by key
   467  		key := req.Data // data holds the key bytes
   468  
   469  		res.Key = key
   470  		if req.Prove {
   471  			value, proof, err := getVersionedWithProof(trie, key)
   472  			if err != nil {
   473  				res.Log = err.Error()
   474  				break
   475  			}
   476  			if proof == nil {
   477  				// Proof == nil implies that the store is empty.
   478  				if value != nil {
   479  					panic("unexpected value for an empty proof")
   480  				}
   481  			}
   482  			if value != nil {
   483  				// value was found
   484  				res.Value = value
   485  				res.Proof = &merkle.Proof{Ops: []merkle.ProofOp{newProofOpMptValue(key, proof)}}
   486  			} else {
   487  				// value wasn't found
   488  				res.Value = nil
   489  				res.Proof = &merkle.Proof{Ops: []merkle.ProofOp{newProofOpMptAbsence(key, proof)}}
   490  			}
   491  		} else {
   492  			res.Value, err = trie.TryGet(key)
   493  			if err != nil {
   494  				return sdkerrors.QueryResult(sdkerrors.Wrapf(sdkerrors.ErrKeyNotFound, "failed to query in trie: %s", err.Error()))
   495  			}
   496  		}
   497  
   498  	case "/subspace":
   499  		return sdkerrors.QueryResult(sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "not supported query subspace path: %v in mptStore", req.Path))
   500  
   501  	default:
   502  		return sdkerrors.QueryResult(sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unexpected query path: %v", req.Path))
   503  	}
   504  
   505  	return res
   506  }
   507  
   508  // Handle latest the latest height - 1  (committed), if height is 0
   509  func (ms *MptStore) getlatestHeight(height uint64) uint64 {
   510  	if height == 0 {
   511  		height = uint64(ms.version)
   512  	}
   513  	return height
   514  }
   515  
   516  func (ms *MptStore) getTrieByHeight(height uint64) (ethstate.Trie, error) {
   517  	latestRootHash := ms.GetMptRootHash(height)
   518  	if latestRootHash == NilHash {
   519  		return nil, fmt.Errorf("header %d not found", height)
   520  	}
   521  	return ms.db.OpenTrie(latestRootHash)
   522  }
   523  
   524  // getVersionedWithProof returns the Merkle proof for given storage slot.
   525  func getVersionedWithProof(trie ethstate.Trie, key []byte) ([]byte, [][]byte, error) {
   526  	value, err := trie.TryGet(key)
   527  	if err != nil {
   528  		return nil, nil, err
   529  	}
   530  
   531  	var proof ProofList
   532  	err = trie.Prove(crypto.Keccak256(key), 0, &proof)
   533  	return value, proof, err
   534  }
   535  
   536  func (ms *MptStore) StartPrefetcher(namespace string) {
   537  	if !tmtypes.HigherThanMars(ms.version) {
   538  		return
   539  	}
   540  
   541  	if ms.prefetcher != nil {
   542  		ms.prefetcher.Close()
   543  		ms.prefetcher = nil
   544  	}
   545  
   546  	ms.prefetcher = NewTriePrefetcher(ms.db, ms.originalRoot, namespace)
   547  }
   548  
   549  // StopPrefetcher terminates a running prefetcher and reports any leftover stats
   550  // from the gathered metrics.
   551  func (ms *MptStore) StopPrefetcher() {
   552  	if ms.prefetcher != nil {
   553  		ms.prefetcher.Close()
   554  		ms.prefetcher = nil
   555  	}
   556  }
   557  
   558  func (ms *MptStore) prefetchData() {
   559  	go func() {
   560  		for {
   561  			select {
   562  			case <-ms.exitSignal:
   563  				return
   564  			case <-GAccTryUpdateTrieChannel:
   565  				if ms.prefetcher != nil {
   566  					if trie := ms.prefetcher.Trie(ms.originalRoot); trie != nil {
   567  						ms.trie = trie
   568  					}
   569  				}
   570  				GAccTrieUpdatedChannel <- struct{}{}
   571  			case addr := <-GAccToPrefetchChannel:
   572  				if ms.prefetcher != nil {
   573  					ms.prefetcher.Prefetch(ms.originalRoot, addr)
   574  				}
   575  			}
   576  		}
   577  	}()
   578  }
   579  
   580  func (ms *MptStore) SetUpgradeVersion(i int64) {}