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