github.com/ethereum/go-ethereum@v1.16.1/core/rawdb/freezer_memory.go (about) 1 // Copyright 2024 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 rawdb 18 19 import ( 20 "errors" 21 "fmt" 22 "math" 23 "sync" 24 25 "github.com/ethereum/go-ethereum/common" 26 "github.com/ethereum/go-ethereum/ethdb" 27 "github.com/ethereum/go-ethereum/log" 28 "github.com/ethereum/go-ethereum/rlp" 29 ) 30 31 // memoryTable is used to store a list of sequential items in memory. 32 type memoryTable struct { 33 items uint64 // Number of stored items in the table, including the deleted ones 34 offset uint64 // Number of deleted items from the table 35 data [][]byte // List of rlp-encoded items, sort in order 36 size uint64 // Total memory size occupied by the table 37 lock sync.RWMutex 38 39 name string 40 config freezerTableConfig 41 } 42 43 // newMemoryTable initializes the memory table. 44 func newMemoryTable(name string, config freezerTableConfig) *memoryTable { 45 return &memoryTable{name: name, config: config} 46 } 47 48 // retrieve retrieves multiple items in sequence, starting from the index 'start'. 49 // It will return: 50 // - at most 'count' items, 51 // - if maxBytes is specified: at least 1 item (even if exceeding the maxByteSize), 52 // but will otherwise return as many items as fit into maxByteSize. 53 // - if maxBytes is not specified, 'count' items will be returned if they are present 54 func (t *memoryTable) retrieve(start uint64, count, maxBytes uint64) ([][]byte, error) { 55 t.lock.RLock() 56 defer t.lock.RUnlock() 57 58 var ( 59 size uint64 60 batch [][]byte 61 ) 62 // Ensure the start is written, not deleted from the tail, and that the 63 // caller actually wants something. 64 if t.items <= start || t.offset > start || count == 0 { 65 return nil, errOutOfBounds 66 } 67 // Cap the item count if the retrieval is out of bound. 68 if start+count > t.items { 69 count = t.items - start 70 } 71 for n := start; n < start+count; n++ { 72 index := n - t.offset 73 if len(batch) != 0 && maxBytes != 0 && size+uint64(len(t.data[index])) > maxBytes { 74 return batch, nil 75 } 76 batch = append(batch, t.data[index]) 77 size += uint64(len(t.data[index])) 78 } 79 return batch, nil 80 } 81 82 // truncateHead discards any recent data above the provided threshold number. 83 func (t *memoryTable) truncateHead(items uint64) error { 84 t.lock.Lock() 85 defer t.lock.Unlock() 86 87 // Short circuit if nothing to delete. 88 if t.items <= items { 89 return nil 90 } 91 if items < t.offset { 92 return errors.New("truncation below tail") 93 } 94 t.data = t.data[:items-t.offset] 95 t.items = items 96 return nil 97 } 98 99 // truncateTail discards any recent data before the provided threshold number. 100 func (t *memoryTable) truncateTail(items uint64) error { 101 t.lock.Lock() 102 defer t.lock.Unlock() 103 104 // Short circuit if nothing to delete. 105 if t.offset >= items { 106 return nil 107 } 108 if t.items < items { 109 return errors.New("truncation above head") 110 } 111 t.data = t.data[items-t.offset:] 112 t.offset = items 113 return nil 114 } 115 116 // commit merges the given item batch into table. It's presumed that the 117 // batch is ordered and continuous with table. 118 func (t *memoryTable) commit(batch [][]byte) error { 119 t.lock.Lock() 120 defer t.lock.Unlock() 121 122 for _, item := range batch { 123 t.size += uint64(len(item)) 124 } 125 t.data = append(t.data, batch...) 126 t.items += uint64(len(batch)) 127 return nil 128 } 129 130 // memoryBatch is the singleton batch used for ancient write. 131 type memoryBatch struct { 132 data map[string][][]byte 133 next map[string]uint64 134 size map[string]int64 135 } 136 137 func newMemoryBatch() *memoryBatch { 138 return &memoryBatch{ 139 data: make(map[string][][]byte), 140 next: make(map[string]uint64), 141 size: make(map[string]int64), 142 } 143 } 144 145 func (b *memoryBatch) reset(freezer *MemoryFreezer) { 146 b.data = make(map[string][][]byte) 147 b.next = make(map[string]uint64) 148 b.size = make(map[string]int64) 149 150 for name, table := range freezer.tables { 151 b.next[name] = table.items 152 } 153 } 154 155 // Append adds an RLP-encoded item. 156 func (b *memoryBatch) Append(kind string, number uint64, item interface{}) error { 157 if b.next[kind] != number { 158 return errOutOrderInsertion 159 } 160 blob, err := rlp.EncodeToBytes(item) 161 if err != nil { 162 return err 163 } 164 b.data[kind] = append(b.data[kind], blob) 165 b.next[kind]++ 166 b.size[kind] += int64(len(blob)) 167 return nil 168 } 169 170 // AppendRaw adds an item without RLP-encoding it. 171 func (b *memoryBatch) AppendRaw(kind string, number uint64, blob []byte) error { 172 if b.next[kind] != number { 173 return errOutOrderInsertion 174 } 175 b.data[kind] = append(b.data[kind], common.CopyBytes(blob)) 176 b.next[kind]++ 177 b.size[kind] += int64(len(blob)) 178 return nil 179 } 180 181 // commit is called at the end of a write operation and writes all remaining 182 // data to tables. 183 func (b *memoryBatch) commit(freezer *MemoryFreezer) (items uint64, writeSize int64, err error) { 184 // Check that count agrees on all batches. 185 items = math.MaxUint64 186 for name, next := range b.next { 187 if items < math.MaxUint64 && next != items { 188 return 0, 0, fmt.Errorf("table %s is at item %d, want %d", name, next, items) 189 } 190 items = next 191 } 192 // Commit all table batches. 193 for name, batch := range b.data { 194 table := freezer.tables[name] 195 if err := table.commit(batch); err != nil { 196 return 0, 0, err 197 } 198 writeSize += b.size[name] 199 } 200 return items, writeSize, nil 201 } 202 203 // MemoryFreezer is an ephemeral ancient store. It implements the ethdb.AncientStore 204 // interface and can be used along with ephemeral key-value store. 205 type MemoryFreezer struct { 206 items uint64 // Number of items stored 207 tail uint64 // Number of the first stored item in the freezer 208 readonly bool // Flag if the freezer is only for reading 209 lock sync.RWMutex // Lock to protect fields 210 tables map[string]*memoryTable // Tables for storing everything 211 writeBatch *memoryBatch // Pre-allocated write batch 212 } 213 214 // NewMemoryFreezer initializes an in-memory freezer instance. 215 func NewMemoryFreezer(readonly bool, tableName map[string]freezerTableConfig) *MemoryFreezer { 216 tables := make(map[string]*memoryTable) 217 for name, cfg := range tableName { 218 tables[name] = newMemoryTable(name, cfg) 219 } 220 return &MemoryFreezer{ 221 writeBatch: newMemoryBatch(), 222 readonly: readonly, 223 tables: tables, 224 } 225 } 226 227 // Ancient retrieves an ancient binary blob from the in-memory freezer. 228 func (f *MemoryFreezer) Ancient(kind string, number uint64) ([]byte, error) { 229 f.lock.RLock() 230 defer f.lock.RUnlock() 231 232 t := f.tables[kind] 233 if t == nil { 234 return nil, errUnknownTable 235 } 236 data, err := t.retrieve(number, 1, 0) 237 if err != nil { 238 return nil, err 239 } 240 return data[0], nil 241 } 242 243 // AncientRange retrieves multiple items in sequence, starting from the index 'start'. 244 // It will return 245 // - at most 'count' items, 246 // - if maxBytes is specified: at least 1 item (even if exceeding the maxByteSize), 247 // but will otherwise return as many items as fit into maxByteSize. 248 // - if maxBytes is not specified, 'count' items will be returned if they are present 249 func (f *MemoryFreezer) AncientRange(kind string, start, count, maxBytes uint64) ([][]byte, error) { 250 f.lock.RLock() 251 defer f.lock.RUnlock() 252 253 t := f.tables[kind] 254 if t == nil { 255 return nil, errUnknownTable 256 } 257 return t.retrieve(start, count, maxBytes) 258 } 259 260 // Ancients returns the ancient item numbers in the freezer. 261 func (f *MemoryFreezer) Ancients() (uint64, error) { 262 f.lock.RLock() 263 defer f.lock.RUnlock() 264 265 return f.items, nil 266 } 267 268 // Tail returns the number of first stored item in the freezer. 269 // This number can also be interpreted as the total deleted item numbers. 270 func (f *MemoryFreezer) Tail() (uint64, error) { 271 f.lock.RLock() 272 defer f.lock.RUnlock() 273 274 return f.tail, nil 275 } 276 277 // AncientSize returns the ancient size of the specified category. 278 func (f *MemoryFreezer) AncientSize(kind string) (uint64, error) { 279 f.lock.RLock() 280 defer f.lock.RUnlock() 281 282 if table := f.tables[kind]; table != nil { 283 return table.size, nil 284 } 285 return 0, errUnknownTable 286 } 287 288 // ReadAncients runs the given read operation while ensuring that no writes take place 289 // on the underlying freezer. 290 func (f *MemoryFreezer) ReadAncients(fn func(ethdb.AncientReaderOp) error) (err error) { 291 f.lock.RLock() 292 defer f.lock.RUnlock() 293 294 return fn(f) 295 } 296 297 // ModifyAncients runs the given write operation. 298 func (f *MemoryFreezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) (writeSize int64, err error) { 299 f.lock.Lock() 300 defer f.lock.Unlock() 301 302 if f.readonly { 303 return 0, errReadOnly 304 } 305 // Roll back all tables to the starting position in case of error. 306 defer func(old uint64) { 307 if err == nil { 308 return 309 } 310 // The write operation has failed. Go back to the previous item position. 311 for name, table := range f.tables { 312 err := table.truncateHead(old) 313 if err != nil { 314 log.Error("Freezer table roll-back failed", "table", name, "index", old, "err", err) 315 } 316 } 317 }(f.items) 318 319 // Modify the ancients in batch. 320 f.writeBatch.reset(f) 321 if err := fn(f.writeBatch); err != nil { 322 return 0, err 323 } 324 item, writeSize, err := f.writeBatch.commit(f) 325 if err != nil { 326 return 0, err 327 } 328 f.items = item 329 return writeSize, nil 330 } 331 332 // TruncateHead discards any recent data above the provided threshold number. 333 // It returns the previous head number. 334 func (f *MemoryFreezer) TruncateHead(items uint64) (uint64, error) { 335 f.lock.Lock() 336 defer f.lock.Unlock() 337 338 if f.readonly { 339 return 0, errReadOnly 340 } 341 old := f.items 342 if old <= items { 343 return old, nil 344 } 345 for _, table := range f.tables { 346 if err := table.truncateHead(items); err != nil { 347 return 0, err 348 } 349 } 350 f.items = items 351 return old, nil 352 } 353 354 // TruncateTail discards all data below the provided threshold number. 355 // Note this will only truncate 'prunable' tables. Block headers and canonical 356 // hashes cannot be truncated at this time. 357 func (f *MemoryFreezer) TruncateTail(tail uint64) (uint64, error) { 358 f.lock.Lock() 359 defer f.lock.Unlock() 360 361 if f.readonly { 362 return 0, errReadOnly 363 } 364 old := f.tail 365 if old >= tail { 366 return old, nil 367 } 368 for _, table := range f.tables { 369 if table.config.prunable { 370 if err := table.truncateTail(tail); err != nil { 371 return 0, err 372 } 373 } 374 } 375 f.tail = tail 376 return old, nil 377 } 378 379 // SyncAncient flushes all data tables to disk. 380 func (f *MemoryFreezer) SyncAncient() error { 381 return nil 382 } 383 384 // Close releases all the sources held by the memory freezer. It will panic if 385 // any following invocation is made to a closed freezer. 386 func (f *MemoryFreezer) Close() error { 387 f.lock.Lock() 388 defer f.lock.Unlock() 389 390 f.tables = nil 391 f.writeBatch = nil 392 return nil 393 } 394 395 // Reset drops all the data cached in the memory freezer and reset itself 396 // back to default state. 397 func (f *MemoryFreezer) Reset() error { 398 f.lock.Lock() 399 defer f.lock.Unlock() 400 401 tables := make(map[string]*memoryTable) 402 for name, table := range f.tables { 403 tables[name] = newMemoryTable(name, table.config) 404 } 405 f.tables = tables 406 f.items, f.tail = 0, 0 407 return nil 408 } 409 410 // AncientDatadir returns the path of the ancient store. 411 // Since the memory freezer is ephemeral, an empty string is returned. 412 func (f *MemoryFreezer) AncientDatadir() (string, error) { 413 return "", nil 414 }