github.com/MetalBlockchain/subnet-evm@v0.4.9/core/state_manager.go (about) 1 // (c) 2019-2020, Ava Labs, Inc. 2 // 3 // This file is a derived work, based on the go-ethereum library whose original 4 // notices appear below. 5 // 6 // It is distributed under a license compatible with the licensing terms of the 7 // original code from which it is derived. 8 // 9 // Much love to the original authors for their work. 10 // ********** 11 // Copyright 2014 The go-ethereum Authors 12 // This file is part of the go-ethereum library. 13 // 14 // The go-ethereum library is free software: you can redistribute it and/or modify 15 // it under the terms of the GNU Lesser General Public License as published by 16 // the Free Software Foundation, either version 3 of the License, or 17 // (at your option) any later version. 18 // 19 // The go-ethereum library is distributed in the hope that it will be useful, 20 // but WITHOUT ANY WARRANTY; without even the implied warranty of 21 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 // GNU Lesser General Public License for more details. 23 // 24 // You should have received a copy of the GNU Lesser General Public License 25 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 26 27 package core 28 29 import ( 30 "fmt" 31 "math/rand" 32 "time" 33 34 "github.com/MetalBlockchain/subnet-evm/core/types" 35 "github.com/MetalBlockchain/subnet-evm/ethdb" 36 "github.com/ethereum/go-ethereum/common" 37 ) 38 39 func init() { 40 rand.Seed(time.Now().UnixNano()) 41 } 42 43 const ( 44 // tipBufferSize is the number of recent accepted tries to keep in the TrieDB 45 // dirties cache at tip (only applicable in [pruning] mode). 46 // 47 // Keeping extra tries around at tip enables clients to query data from 48 // recent trie roots. 49 tipBufferSize = 32 50 51 // flushWindow is the distance to the [commitInterval] when we start 52 // optimistically flushing trie nodes to disk (only applicable in [pruning] 53 // mode). 54 // 55 // We perform this optimistic flushing to reduce synchronized database IO at the 56 // [commitInterval]. 57 flushWindow = 768 58 ) 59 60 type TrieWriter interface { 61 InsertTrie(block *types.Block) error // Handle inserted trie reference of [root] 62 AcceptTrie(block *types.Block) error // Mark [root] as part of an accepted block 63 RejectTrie(block *types.Block) error // Notify TrieWriter that the block containing [root] has been rejected 64 Shutdown() error 65 } 66 67 type TrieDB interface { 68 Dereference(root common.Hash) 69 Commit(root common.Hash, report bool, callback func(common.Hash)) error 70 Size() (common.StorageSize, common.StorageSize) 71 Cap(limit common.StorageSize) error 72 } 73 74 func NewTrieWriter(db TrieDB, config *CacheConfig) TrieWriter { 75 if config.Pruning { 76 cm := &cappedMemoryTrieWriter{ 77 TrieDB: db, 78 memoryCap: common.StorageSize(config.TrieDirtyLimit) * 1024 * 1024, 79 targetCommitSize: common.StorageSize(config.TrieDirtyCommitTarget) * 1024 * 1024, 80 imageCap: 4 * 1024 * 1024, 81 commitInterval: config.CommitInterval, 82 tipBuffer: NewBoundedBuffer(tipBufferSize, db.Dereference), 83 } 84 cm.flushStepSize = (cm.memoryCap - cm.targetCommitSize) / common.StorageSize(flushWindow) 85 return cm 86 } else { 87 return &noPruningTrieWriter{ 88 TrieDB: db, 89 } 90 } 91 } 92 93 type noPruningTrieWriter struct { 94 TrieDB 95 } 96 97 func (np *noPruningTrieWriter) InsertTrie(block *types.Block) error { 98 // We don't attempt to [Cap] here because we should never have 99 // a significant amount of [TrieDB.Dirties] (we commit each block). 100 return nil 101 } 102 103 func (np *noPruningTrieWriter) AcceptTrie(block *types.Block) error { 104 // We don't need to call [Dereference] on the block root at the end of this 105 // function because it is removed from the [TrieDB.Dirties] map in [Commit]. 106 return np.TrieDB.Commit(block.Root(), false, nil) 107 } 108 109 func (np *noPruningTrieWriter) RejectTrie(block *types.Block) error { 110 np.TrieDB.Dereference(block.Root()) 111 return nil 112 } 113 114 func (np *noPruningTrieWriter) Shutdown() error { return nil } 115 116 type cappedMemoryTrieWriter struct { 117 TrieDB 118 memoryCap common.StorageSize 119 targetCommitSize common.StorageSize 120 flushStepSize common.StorageSize 121 imageCap common.StorageSize 122 commitInterval uint64 123 124 tipBuffer *BoundedBuffer[common.Hash] 125 } 126 127 func (cm *cappedMemoryTrieWriter) InsertTrie(block *types.Block) error { 128 // The use of [Cap] in [InsertTrie] prevents exceeding the configured memory 129 // limit (and OOM) in case there is a large backlog of processing (unaccepted) blocks. 130 nodes, imgs := cm.TrieDB.Size() 131 if nodes <= cm.memoryCap && imgs <= cm.imageCap { 132 return nil 133 } 134 if err := cm.TrieDB.Cap(cm.memoryCap - ethdb.IdealBatchSize); err != nil { 135 return fmt.Errorf("failed to cap trie for block %s: %w", block.Hash().Hex(), err) 136 } 137 138 return nil 139 } 140 141 func (cm *cappedMemoryTrieWriter) AcceptTrie(block *types.Block) error { 142 root := block.Root() 143 144 // Attempt to dereference roots at least [tipBufferSize] old (so queries at tip 145 // can still be completed). 146 // 147 // Note: It is safe to dereference roots that have been committed to disk 148 // (they are no-ops). 149 cm.tipBuffer.Insert(root) 150 151 // Commit this root if we have reached the [commitInterval]. 152 modCommitInterval := block.NumberU64() % cm.commitInterval 153 if modCommitInterval == 0 { 154 if err := cm.TrieDB.Commit(root, true, nil); err != nil { 155 return fmt.Errorf("failed to commit trie for block %s: %w", block.Hash().Hex(), err) 156 } 157 return nil 158 } 159 160 // Write at least [flushStepSize] of the oldest nodes in the trie database 161 // dirty cache to disk as we approach the [commitInterval] to reduce the number of trie nodes 162 // that will need to be written at once on [Commit] (to roughly [targetCommitSize]). 163 // 164 // To reduce the number of useless trie nodes that are committed during this 165 // capping, we only optimistically flush within the [flushWindow]. During 166 // this period, the [targetMemory] decreases stepwise by [flushStepSize] 167 // as we get closer to the commit boundary. 168 // 169 // Most trie nodes are 300B, so we will write at least ~1000 trie nodes in 170 // a single optimistic flush (with the default [flushStepSize]=312KB). 171 distanceFromCommit := cm.commitInterval - modCommitInterval // this cannot be 0 172 if distanceFromCommit > flushWindow { 173 return nil 174 } 175 targetMemory := cm.targetCommitSize + cm.flushStepSize*common.StorageSize(distanceFromCommit) 176 nodes, _ := cm.TrieDB.Size() 177 if nodes <= targetMemory { 178 return nil 179 } 180 targetCap := targetMemory - ethdb.IdealBatchSize 181 if err := cm.TrieDB.Cap(targetCap); err != nil { 182 return fmt.Errorf("failed to cap trie for block %s (target=%s): %w", block.Hash().Hex(), targetCap, err) 183 } 184 return nil 185 } 186 187 func (cm *cappedMemoryTrieWriter) RejectTrie(block *types.Block) error { 188 cm.TrieDB.Dereference(block.Root()) 189 return nil 190 } 191 192 func (cm *cappedMemoryTrieWriter) Shutdown() error { 193 // If [tipBuffer] entry is empty, no need to do any cleanup on 194 // shutdown. 195 last, exists := cm.tipBuffer.Last() 196 if !exists { 197 return nil 198 } 199 200 // Attempt to commit last item added to [dereferenceQueue] on shutdown to avoid 201 // re-processing the state on the next startup. 202 return cm.TrieDB.Commit(last, true, nil) 203 }