github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/x/evm/keeper/keeper_mpt.go (about)

     1  package keeper
     2  
     3  import (
     4  	"encoding/binary"
     5  	"fmt"
     6  
     7  	ethcmn "github.com/ethereum/go-ethereum/common"
     8  	ethtypes "github.com/ethereum/go-ethereum/core/types"
     9  	"github.com/ethereum/go-ethereum/ethdb"
    10  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/store/mpt"
    11  	sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types"
    12  	"github.com/fibonacci-chain/fbc/libs/tendermint/libs/log"
    13  	tmtypes "github.com/fibonacci-chain/fbc/libs/tendermint/types"
    14  	"github.com/fibonacci-chain/fbc/x/evm/types"
    15  )
    16  
    17  // GetMptRootHash gets root mpt hash from block height
    18  func (k *Keeper) GetMptRootHash(height uint64) ethcmn.Hash {
    19  	heightBytes := sdk.Uint64ToBigEndian(height)
    20  	rst, err := k.db.TrieDB().DiskDB().Get(append(mpt.KeyPrefixEvmRootMptHash, heightBytes...))
    21  	if err != nil || len(rst) == 0 {
    22  		return ethcmn.Hash{}
    23  	}
    24  	return ethcmn.BytesToHash(rst)
    25  }
    26  
    27  // SetMptRootHash sets the mapping from block height to root mpt hash
    28  func (k *Keeper) SetMptRootHash(ctx sdk.Context, hash ethcmn.Hash) {
    29  	heightBytes := sdk.Uint64ToBigEndian(uint64(ctx.BlockHeight()))
    30  	k.db.TrieDB().DiskDB().Put(append(mpt.KeyPrefixEvmRootMptHash, heightBytes...), hash.Bytes())
    31  
    32  	// put root hash to iavl and participate the process of calculate appHash
    33  	if tmtypes.HigherThanMars(ctx.BlockHeight()) {
    34  		store := k.paramSpace.CustomKVStore(ctx)
    35  		store.Set(types.KeyPrefixEvmRootHash, hash.Bytes())
    36  	}
    37  }
    38  
    39  // GetLatestStoredBlockHeight get latest stored mpt storage height
    40  func (k *Keeper) GetLatestStoredBlockHeight() uint64 {
    41  	rst, err := k.db.TrieDB().DiskDB().Get(mpt.KeyPrefixEvmLatestStoredHeight)
    42  	if err != nil || len(rst) == 0 {
    43  		return 0
    44  	}
    45  	return binary.BigEndian.Uint64(rst)
    46  }
    47  
    48  // SetLatestStoredBlockHeight sets the latest stored storage height
    49  func (k *Keeper) SetLatestStoredBlockHeight(height uint64) {
    50  	heightBytes := sdk.Uint64ToBigEndian(height)
    51  	k.db.TrieDB().DiskDB().Put(mpt.KeyPrefixEvmLatestStoredHeight, heightBytes)
    52  }
    53  
    54  func (k *Keeper) OpenTrie() {
    55  	//startHeight := types2.GetStartBlockHeight() // start height of oec
    56  	latestStoredHeight := k.GetLatestStoredBlockHeight()
    57  	latestStoredRootHash := k.GetMptRootHash(latestStoredHeight)
    58  
    59  	tr, err := k.db.OpenTrie(latestStoredRootHash)
    60  	if err != nil {
    61  		panic("Fail to open root mpt: " + err.Error())
    62  	}
    63  	k.rootTrie = tr
    64  	k.rootHash = latestStoredRootHash
    65  	k.startHeight = latestStoredHeight
    66  
    67  	if latestStoredHeight == 0 {
    68  		k.startHeight = uint64(tmtypes.GetStartBlockHeight())
    69  	}
    70  }
    71  
    72  func (k *Keeper) SetTargetMptVersion(targetVersion int64) {
    73  	if !tmtypes.HigherThanMars(targetVersion) {
    74  		return
    75  	}
    76  
    77  	latestStoredHeight := k.GetLatestStoredBlockHeight()
    78  	if latestStoredHeight < uint64(targetVersion) {
    79  		panic(fmt.Sprintf("The target mpt height is: %v, but the latest stored evm height is: %v", targetVersion, latestStoredHeight))
    80  	}
    81  	targetMptRootHash := k.GetMptRootHash(uint64(targetVersion))
    82  
    83  	tr, err := k.db.OpenTrie(targetMptRootHash)
    84  	if err != nil {
    85  		panic("Fail to open root mpt: " + err.Error())
    86  	}
    87  	k.rootTrie = tr
    88  	k.rootHash = targetMptRootHash
    89  	k.startHeight = uint64(targetVersion)
    90  	if targetVersion == 0 {
    91  		k.startHeight = uint64(tmtypes.GetStartBlockHeight())
    92  	}
    93  
    94  	k.EvmStateDb = types.NewCommitStateDB(k.GenerateCSDBParams())
    95  }
    96  
    97  // Stop stops the blockchain service. If any imports are currently in progress
    98  // it will abort them using the procInterrupt.
    99  func (k *Keeper) OnStop(ctx sdk.Context) error {
   100  	if !mpt.TrieDirtyDisabled {
   101  		k.cmLock.Lock()
   102  		defer k.cmLock.Unlock()
   103  
   104  		triedb := k.db.TrieDB()
   105  		oecStartHeight := uint64(tmtypes.GetStartBlockHeight()) // start height of oec
   106  
   107  		latestStoreVersion := k.GetLatestStoredBlockHeight()
   108  		curVersion := uint64(ctx.BlockHeight())
   109  		for version := latestStoreVersion; version <= curVersion; version++ {
   110  			if version <= oecStartHeight || version <= k.startHeight {
   111  				continue
   112  			}
   113  
   114  			recentMptRoot := k.GetMptRootHash(version)
   115  			if recentMptRoot == (ethcmn.Hash{}) || recentMptRoot == ethtypes.EmptyRootHash {
   116  				recentMptRoot = ethcmn.Hash{}
   117  			} else {
   118  				if err := triedb.Commit(recentMptRoot, true, nil); err != nil {
   119  					k.Logger().Error("Failed to commit recent state trie", "err", err)
   120  					break
   121  				}
   122  			}
   123  			k.SetLatestStoredBlockHeight(version)
   124  			k.Logger().Info("Writing evm cached state to disk", "block", version, "trieHash", recentMptRoot)
   125  		}
   126  
   127  		for !k.triegc.Empty() {
   128  			k.db.TrieDB().Dereference(k.triegc.PopItem().(ethcmn.Hash))
   129  		}
   130  	}
   131  
   132  	return nil
   133  }
   134  
   135  // PushData2Database writes all associated state in cache to the database
   136  func (k *Keeper) PushData2Database(height int64, log log.Logger) {
   137  	k.cmLock.Lock()
   138  	defer k.cmLock.Unlock()
   139  
   140  	curMptRoot := k.GetMptRootHash(uint64(height))
   141  	if mpt.TrieDirtyDisabled {
   142  		// If we're running an archive node, always flush
   143  		k.fullNodePersist(curMptRoot, height, log)
   144  	} else {
   145  		k.otherNodePersist(curMptRoot, height, log)
   146  	}
   147  }
   148  
   149  // fullNodePersist persist data without pruning
   150  func (k *Keeper) fullNodePersist(curMptRoot ethcmn.Hash, curHeight int64, log log.Logger) {
   151  	if curMptRoot == (ethcmn.Hash{}) || curMptRoot == ethtypes.EmptyRootHash {
   152  		curMptRoot = ethcmn.Hash{}
   153  	} else {
   154  		// Commit all cached state changes into underlying memory database.
   155  		if err := k.db.TrieDB().Commit(curMptRoot, false, nil); err != nil {
   156  			panic("fail to commit mpt data: " + err.Error())
   157  		}
   158  	}
   159  	k.SetLatestStoredBlockHeight(uint64(curHeight))
   160  	log.Info("sync push evm data to db", "block", curHeight, "trieHash", curMptRoot)
   161  }
   162  
   163  // otherNodePersist persist data with pruning
   164  func (k *Keeper) otherNodePersist(curMptRoot ethcmn.Hash, curHeight int64, log log.Logger) {
   165  	triedb := k.db.TrieDB()
   166  
   167  	// Full but not archive node, do proper garbage collection
   168  	triedb.Reference(curMptRoot, ethcmn.Hash{}) // metadata reference to keep trie alive
   169  	k.triegc.Push(curMptRoot, -int64(curHeight))
   170  
   171  	if curHeight > mpt.TriesInMemory {
   172  		// If we exceeded our memory allowance, flush matured singleton nodes to disk
   173  		var (
   174  			nodes, imgs = triedb.Size()
   175  			nodesLimit  = ethcmn.StorageSize(mpt.TrieNodesLimit) * 1024 * 1024
   176  			imgsLimit   = ethcmn.StorageSize(mpt.TrieImgsLimit) * 1024 * 1024
   177  		)
   178  
   179  		if nodes > nodesLimit || imgs > imgsLimit {
   180  			triedb.Cap(nodesLimit - ethdb.IdealBatchSize)
   181  		}
   182  		// Find the next state trie we need to commit
   183  		chosen := curHeight - mpt.TriesInMemory
   184  
   185  		if chosen <= int64(k.startHeight) {
   186  			return
   187  		}
   188  
   189  		if chosen%mpt.TrieCommitGap == 0 {
   190  			// If the header is missing (canonical chain behind), we're reorging a low
   191  			// diff sidechain. Suspend committing until this operation is completed.
   192  			chRoot := k.GetMptRootHash(uint64(chosen))
   193  			if chRoot == (ethcmn.Hash{}) || chRoot == ethtypes.EmptyRootHash {
   194  				chRoot = ethcmn.Hash{}
   195  			} else {
   196  				// Flush an entire trie and restart the counters, it's not a thread safe process,
   197  				// cannot use a go thread to run, or it will lead 'fatal error: concurrent map read and map write' error
   198  				if err := triedb.Commit(chRoot, true, nil); err != nil {
   199  					panic("fail to commit mpt data: " + err.Error())
   200  				}
   201  			}
   202  			k.SetLatestStoredBlockHeight(uint64(chosen))
   203  			log.Info("async push evm data to db", "block", chosen, "trieHash", chRoot)
   204  		}
   205  
   206  		// Garbage collect anything below our required write retention
   207  		for !k.triegc.Empty() {
   208  			root, number := k.triegc.Pop()
   209  			if -number > chosen {
   210  				k.triegc.Push(root, number)
   211  				break
   212  			}
   213  			triedb.Dereference(root.(ethcmn.Hash))
   214  		}
   215  	}
   216  }
   217  
   218  func (k *Keeper) Commit(ctx sdk.Context) {
   219  	// commit contract storage mpt trie
   220  	k.EvmStateDb.WithContext(ctx).Commit(true)
   221  	k.EvmStateDb.StopPrefetcher()
   222  
   223  	if tmtypes.HigherThanMars(ctx.BlockHeight()) || mpt.TrieWriteAhead {
   224  		k.rootTrie = k.EvmStateDb.GetRootTrie()
   225  
   226  		// The onleaf func is called _serially_, so we can reuse the same account
   227  		// for unmarshalling every time.
   228  		var storageRoot ethcmn.Hash
   229  		root, _ := k.rootTrie.Commit(func(_ [][]byte, _ []byte, leaf []byte, parent ethcmn.Hash) error {
   230  			storageRoot.SetBytes(leaf)
   231  			if storageRoot != ethtypes.EmptyRootHash {
   232  				k.db.TrieDB().Reference(storageRoot, parent)
   233  			}
   234  
   235  			return nil
   236  		})
   237  		k.SetMptRootHash(ctx, root)
   238  		k.rootHash = root
   239  	}
   240  }
   241  
   242  /*
   243   * Getters for keys in x/evm/types/keys.go
   244   * TODO: these interfaces are used for setting/getting data in rawdb, instead of iavl.
   245   * TODO: delete these if we decide persist data in iavl.
   246   */
   247  func (k Keeper) getBlockHashInDiskDB(hash []byte) (int64, bool) {
   248  	key := types.AppendBlockHashKey(hash)
   249  	bz, err := k.db.TrieDB().DiskDB().Get(key)
   250  	if err != nil {
   251  		return 0, false
   252  	}
   253  	if len(bz) == 0 {
   254  		return 0, false
   255  	}
   256  
   257  	height := binary.BigEndian.Uint64(bz)
   258  	return int64(height), true
   259  }
   260  
   261  func (k Keeper) setBlockHashInDiskDB(hash []byte, height int64) {
   262  	key := types.AppendBlockHashKey(hash)
   263  	bz := sdk.Uint64ToBigEndian(uint64(height))
   264  	k.db.TrieDB().DiskDB().Put(key, bz)
   265  }
   266  
   267  func (k Keeper) iterateBlockHashInDiskDB(fn func(key []byte, value []byte) (stop bool)) {
   268  	iterator := k.db.TrieDB().DiskDB().NewIterator(types.KeyPrefixBlockHash, nil)
   269  	defer iterator.Release()
   270  	for iterator.Next() {
   271  		if !types.IsBlockHashKey(iterator.Key()) {
   272  			continue
   273  		}
   274  		key, value := iterator.Key(), iterator.Value()
   275  		if stop := fn(key, value); stop {
   276  			break
   277  		}
   278  	}
   279  }
   280  
   281  func (k Keeper) getBlockBloomInDiskDB(height int64) ethtypes.Bloom {
   282  	key := types.AppendBloomKey(height)
   283  	bz, err := k.db.TrieDB().DiskDB().Get(key)
   284  	if err != nil {
   285  		return ethtypes.Bloom{}
   286  	}
   287  
   288  	return ethtypes.BytesToBloom(bz)
   289  }
   290  
   291  func (k Keeper) setBlockBloomInDiskDB(height int64, bloom ethtypes.Bloom) {
   292  	key := types.AppendBloomKey(height)
   293  	k.db.TrieDB().DiskDB().Put(key, bloom.Bytes())
   294  }
   295  
   296  func (k Keeper) iterateBlockBloomInDiskDB(fn func(key []byte, value []byte) (stop bool)) {
   297  	iterator := k.db.TrieDB().DiskDB().NewIterator(types.KeyPrefixBloom, nil)
   298  	defer iterator.Release()
   299  	for iterator.Next() {
   300  		if !types.IsBloomKey(iterator.Key()) {
   301  			continue
   302  		}
   303  		key, value := iterator.Key(), iterator.Value()
   304  		if stop := fn(key, value); stop {
   305  			break
   306  		}
   307  	}
   308  }