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 }