github.1485827954.workers.dev/ethereum/go-ethereum@v1.14.3/triedb/pathdb/nodebuffer.go (about) 1 // Copyright 2022 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package pathdb 18 19 import ( 20 "bytes" 21 "fmt" 22 "time" 23 24 "github.com/VictoriaMetrics/fastcache" 25 "github.com/ethereum/go-ethereum/common" 26 "github.com/ethereum/go-ethereum/core/rawdb" 27 "github.com/ethereum/go-ethereum/crypto" 28 "github.com/ethereum/go-ethereum/ethdb" 29 "github.com/ethereum/go-ethereum/log" 30 "github.com/ethereum/go-ethereum/trie/trienode" 31 ) 32 33 // nodebuffer is a collection of modified trie nodes to aggregate the disk 34 // write. The content of the nodebuffer must be checked before diving into 35 // disk (since it basically is not-yet-written data). 36 type nodebuffer struct { 37 layers uint64 // The number of diff layers aggregated inside 38 size uint64 // The size of aggregated writes 39 limit uint64 // The maximum memory allowance in bytes 40 nodes map[common.Hash]map[string]*trienode.Node // The dirty node set, mapped by owner and path 41 } 42 43 // newNodeBuffer initializes the node buffer with the provided nodes. 44 func newNodeBuffer(limit int, nodes map[common.Hash]map[string]*trienode.Node, layers uint64) *nodebuffer { 45 if nodes == nil { 46 nodes = make(map[common.Hash]map[string]*trienode.Node) 47 } 48 var size uint64 49 for _, subset := range nodes { 50 for path, n := range subset { 51 size += uint64(len(n.Blob) + len(path)) 52 } 53 } 54 return &nodebuffer{ 55 layers: layers, 56 nodes: nodes, 57 size: size, 58 limit: uint64(limit), 59 } 60 } 61 62 // node retrieves the trie node with given node info. 63 func (b *nodebuffer) node(owner common.Hash, path []byte) (*trienode.Node, bool) { 64 subset, ok := b.nodes[owner] 65 if !ok { 66 return nil, false 67 } 68 n, ok := subset[string(path)] 69 if !ok { 70 return nil, false 71 } 72 return n, true 73 } 74 75 // commit merges the dirty nodes into the nodebuffer. This operation won't take 76 // the ownership of the nodes map which belongs to the bottom-most diff layer. 77 // It will just hold the node references from the given map which are safe to 78 // copy. 79 func (b *nodebuffer) commit(nodes map[common.Hash]map[string]*trienode.Node) *nodebuffer { 80 var ( 81 delta int64 82 overwrite int64 83 overwriteSize int64 84 ) 85 for owner, subset := range nodes { 86 current, exist := b.nodes[owner] 87 if !exist { 88 // Allocate a new map for the subset instead of claiming it directly 89 // from the passed map to avoid potential concurrent map read/write. 90 // The nodes belong to original diff layer are still accessible even 91 // after merging, thus the ownership of nodes map should still belong 92 // to original layer and any mutation on it should be prevented. 93 current = make(map[string]*trienode.Node, len(subset)) 94 for path, n := range subset { 95 current[path] = n 96 delta += int64(len(n.Blob) + len(path)) 97 } 98 b.nodes[owner] = current 99 continue 100 } 101 for path, n := range subset { 102 if orig, exist := current[path]; !exist { 103 delta += int64(len(n.Blob) + len(path)) 104 } else { 105 delta += int64(len(n.Blob) - len(orig.Blob)) 106 overwrite++ 107 overwriteSize += int64(len(orig.Blob) + len(path)) 108 } 109 current[path] = n 110 } 111 b.nodes[owner] = current 112 } 113 b.updateSize(delta) 114 b.layers++ 115 gcNodesMeter.Mark(overwrite) 116 gcBytesMeter.Mark(overwriteSize) 117 return b 118 } 119 120 // revert is the reverse operation of commit. It also merges the provided nodes 121 // into the nodebuffer, the difference is that the provided node set should 122 // revert the changes made by the last state transition. 123 func (b *nodebuffer) revert(db ethdb.KeyValueReader, nodes map[common.Hash]map[string]*trienode.Node) error { 124 // Short circuit if no embedded state transition to revert. 125 if b.layers == 0 { 126 return errStateUnrecoverable 127 } 128 b.layers-- 129 130 // Reset the entire buffer if only a single transition left. 131 if b.layers == 0 { 132 b.reset() 133 return nil 134 } 135 var delta int64 136 for owner, subset := range nodes { 137 current, ok := b.nodes[owner] 138 if !ok { 139 panic(fmt.Sprintf("non-existent subset (%x)", owner)) 140 } 141 for path, n := range subset { 142 orig, ok := current[path] 143 if !ok { 144 // There is a special case in MPT that one child is removed from 145 // a fullNode which only has two children, and then a new child 146 // with different position is immediately inserted into the fullNode. 147 // In this case, the clean child of the fullNode will also be 148 // marked as dirty because of node collapse and expansion. 149 // 150 // In case of database rollback, don't panic if this "clean" 151 // node occurs which is not present in buffer. 152 var blob []byte 153 if owner == (common.Hash{}) { 154 blob = rawdb.ReadAccountTrieNode(db, []byte(path)) 155 } else { 156 blob = rawdb.ReadStorageTrieNode(db, owner, []byte(path)) 157 } 158 // Ignore the clean node in the case described above. 159 if bytes.Equal(blob, n.Blob) { 160 continue 161 } 162 panic(fmt.Sprintf("non-existent node (%x %v) blob: %v", owner, path, crypto.Keccak256Hash(n.Blob).Hex())) 163 } 164 current[path] = n 165 delta += int64(len(n.Blob)) - int64(len(orig.Blob)) 166 } 167 } 168 b.updateSize(delta) 169 return nil 170 } 171 172 // updateSize updates the total cache size by the given delta. 173 func (b *nodebuffer) updateSize(delta int64) { 174 size := int64(b.size) + delta 175 if size >= 0 { 176 b.size = uint64(size) 177 return 178 } 179 s := b.size 180 b.size = 0 181 log.Error("Invalid pathdb buffer size", "prev", common.StorageSize(s), "delta", common.StorageSize(delta)) 182 } 183 184 // reset cleans up the disk cache. 185 func (b *nodebuffer) reset() { 186 b.layers = 0 187 b.size = 0 188 b.nodes = make(map[common.Hash]map[string]*trienode.Node) 189 } 190 191 // empty returns an indicator if nodebuffer contains any state transition inside. 192 func (b *nodebuffer) empty() bool { 193 return b.layers == 0 194 } 195 196 // setSize sets the buffer size to the provided number, and invokes a flush 197 // operation if the current memory usage exceeds the new limit. 198 func (b *nodebuffer) setSize(size int, db ethdb.KeyValueStore, clean *fastcache.Cache, id uint64) error { 199 b.limit = uint64(size) 200 return b.flush(db, clean, id, false) 201 } 202 203 // allocBatch returns a database batch with pre-allocated buffer. 204 func (b *nodebuffer) allocBatch(db ethdb.KeyValueStore) ethdb.Batch { 205 var metasize int 206 for owner, nodes := range b.nodes { 207 if owner == (common.Hash{}) { 208 metasize += len(nodes) * len(rawdb.TrieNodeAccountPrefix) // database key prefix 209 } else { 210 metasize += len(nodes) * (len(rawdb.TrieNodeStoragePrefix) + common.HashLength) // database key prefix + owner 211 } 212 } 213 return db.NewBatchWithSize((metasize + int(b.size)) * 11 / 10) // extra 10% for potential pebble internal stuff 214 } 215 216 // flush persists the in-memory dirty trie node into the disk if the configured 217 // memory threshold is reached. Note, all data must be written atomically. 218 func (b *nodebuffer) flush(db ethdb.KeyValueStore, clean *fastcache.Cache, id uint64, force bool) error { 219 if b.size <= b.limit && !force { 220 return nil 221 } 222 // Ensure the target state id is aligned with the internal counter. 223 head := rawdb.ReadPersistentStateID(db) 224 if head+b.layers != id { 225 return fmt.Errorf("buffer layers (%d) cannot be applied on top of persisted state id (%d) to reach requested state id (%d)", b.layers, head, id) 226 } 227 var ( 228 start = time.Now() 229 batch = b.allocBatch(db) 230 ) 231 nodes := writeNodes(batch, b.nodes, clean) 232 rawdb.WritePersistentStateID(batch, id) 233 234 // Flush all mutations in a single batch 235 size := batch.ValueSize() 236 if err := batch.Write(); err != nil { 237 return err 238 } 239 commitBytesMeter.Mark(int64(size)) 240 commitNodesMeter.Mark(int64(nodes)) 241 commitTimeTimer.UpdateSince(start) 242 log.Debug("Persisted pathdb nodes", "nodes", len(b.nodes), "bytes", common.StorageSize(size), "elapsed", common.PrettyDuration(time.Since(start))) 243 b.reset() 244 return nil 245 } 246 247 // writeNodes writes the trie nodes into the provided database batch. 248 // Note this function will also inject all the newly written nodes 249 // into clean cache. 250 func writeNodes(batch ethdb.Batch, nodes map[common.Hash]map[string]*trienode.Node, clean *fastcache.Cache) (total int) { 251 for owner, subset := range nodes { 252 for path, n := range subset { 253 if n.IsDeleted() { 254 if owner == (common.Hash{}) { 255 rawdb.DeleteAccountTrieNode(batch, []byte(path)) 256 } else { 257 rawdb.DeleteStorageTrieNode(batch, owner, []byte(path)) 258 } 259 if clean != nil { 260 clean.Del(cacheKey(owner, []byte(path))) 261 } 262 } else { 263 if owner == (common.Hash{}) { 264 rawdb.WriteAccountTrieNode(batch, []byte(path), n.Blob) 265 } else { 266 rawdb.WriteStorageTrieNode(batch, owner, []byte(path), n.Blob) 267 } 268 if clean != nil { 269 clean.Set(cacheKey(owner, []byte(path)), n.Blob) 270 } 271 } 272 } 273 total += len(subset) 274 } 275 return total 276 } 277 278 // cacheKey constructs the unique key of clean cache. 279 func cacheKey(owner common.Hash, path []byte) []byte { 280 if owner == (common.Hash{}) { 281 return path 282 } 283 return append(owner.Bytes(), path...) 284 }