github.com/authcall/reference-optimistic-geth@v0.0.0-20220816224302-06313bfeb8d2/core/rawdb/freezer.go (about) 1 // Copyright 2019 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 "os" 24 "path/filepath" 25 "sync" 26 "sync/atomic" 27 "time" 28 29 "github.com/ethereum/go-ethereum/common" 30 "github.com/ethereum/go-ethereum/ethdb" 31 "github.com/ethereum/go-ethereum/log" 32 "github.com/ethereum/go-ethereum/metrics" 33 "github.com/prometheus/tsdb/fileutil" 34 ) 35 36 var ( 37 // errReadOnly is returned if the freezer is opened in read only mode. All the 38 // mutations are disallowed. 39 errReadOnly = errors.New("read only") 40 41 // errUnknownTable is returned if the user attempts to read from a table that is 42 // not tracked by the freezer. 43 errUnknownTable = errors.New("unknown table") 44 45 // errOutOrderInsertion is returned if the user attempts to inject out-of-order 46 // binary blobs into the freezer. 47 errOutOrderInsertion = errors.New("the append operation is out-order") 48 49 // errSymlinkDatadir is returned if the ancient directory specified by user 50 // is a symbolic link. 51 errSymlinkDatadir = errors.New("symbolic link datadir is not supported") 52 ) 53 54 // freezerTableSize defines the maximum size of freezer data files. 55 const freezerTableSize = 2 * 1000 * 1000 * 1000 56 57 // Freezer is a memory mapped append-only database to store immutable ordered 58 // data into flat files: 59 // 60 // - The append-only nature ensures that disk writes are minimized. 61 // - The memory mapping ensures we can max out system memory for caching without 62 // reserving it for go-ethereum. This would also reduce the memory requirements 63 // of Geth, and thus also GC overhead. 64 type Freezer struct { 65 // WARNING: The `frozen` and `tail` fields are accessed atomically. On 32 bit platforms, only 66 // 64-bit aligned fields can be atomic. The struct is guaranteed to be so aligned, 67 // so take advantage of that (https://golang.org/pkg/sync/atomic/#pkg-note-BUG). 68 frozen uint64 // Number of blocks already frozen 69 tail uint64 // Number of the first stored item in the freezer 70 71 datadir string // Path of root directory of ancient store 72 73 // This lock synchronizes writers and the truncate operation, as well as 74 // the "atomic" (batched) read operations. 75 writeLock sync.RWMutex 76 writeBatch *freezerBatch 77 78 readonly bool 79 tables map[string]*freezerTable // Data tables for storing everything 80 instanceLock fileutil.Releaser // File-system lock to prevent double opens 81 closeOnce sync.Once 82 } 83 84 // NewFreezer creates a freezer instance for maintaining immutable ordered 85 // data according to the given parameters. 86 // 87 // The 'tables' argument defines the data tables. If the value of a map 88 // entry is true, snappy compression is disabled for the table. 89 func NewFreezer(datadir string, namespace string, readonly bool, maxTableSize uint32, tables map[string]bool) (*Freezer, error) { 90 // Create the initial freezer object 91 var ( 92 readMeter = metrics.NewRegisteredMeter(namespace+"ancient/read", nil) 93 writeMeter = metrics.NewRegisteredMeter(namespace+"ancient/write", nil) 94 sizeGauge = metrics.NewRegisteredGauge(namespace+"ancient/size", nil) 95 ) 96 // Ensure the datadir is not a symbolic link if it exists. 97 if info, err := os.Lstat(datadir); !os.IsNotExist(err) { 98 if info.Mode()&os.ModeSymlink != 0 { 99 log.Warn("Symbolic link ancient database is not supported", "path", datadir) 100 return nil, errSymlinkDatadir 101 } 102 } 103 // Leveldb uses LOCK as the filelock filename. To prevent the 104 // name collision, we use FLOCK as the lock name. 105 lock, _, err := fileutil.Flock(filepath.Join(datadir, "FLOCK")) 106 if err != nil { 107 return nil, err 108 } 109 // Open all the supported data tables 110 freezer := &Freezer{ 111 readonly: readonly, 112 tables: make(map[string]*freezerTable), 113 instanceLock: lock, 114 datadir: datadir, 115 } 116 117 // Create the tables. 118 for name, disableSnappy := range tables { 119 table, err := newTable(datadir, name, readMeter, writeMeter, sizeGauge, maxTableSize, disableSnappy, readonly) 120 if err != nil { 121 for _, table := range freezer.tables { 122 table.Close() 123 } 124 lock.Release() 125 return nil, err 126 } 127 freezer.tables[name] = table 128 } 129 130 if freezer.readonly { 131 // In readonly mode only validate, don't truncate. 132 // validate also sets `freezer.frozen`. 133 err = freezer.validate() 134 } else { 135 // Truncate all tables to common length. 136 err = freezer.repair() 137 } 138 if err != nil { 139 for _, table := range freezer.tables { 140 table.Close() 141 } 142 lock.Release() 143 return nil, err 144 } 145 146 // Create the write batch. 147 freezer.writeBatch = newFreezerBatch(freezer) 148 149 log.Info("Opened ancient database", "database", datadir, "readonly", readonly) 150 return freezer, nil 151 } 152 153 // Close terminates the chain freezer, unmapping all the data files. 154 func (f *Freezer) Close() error { 155 f.writeLock.Lock() 156 defer f.writeLock.Unlock() 157 158 var errs []error 159 f.closeOnce.Do(func() { 160 for _, table := range f.tables { 161 if err := table.Close(); err != nil { 162 errs = append(errs, err) 163 } 164 } 165 if err := f.instanceLock.Release(); err != nil { 166 errs = append(errs, err) 167 } 168 }) 169 if errs != nil { 170 return fmt.Errorf("%v", errs) 171 } 172 return nil 173 } 174 175 // HasAncient returns an indicator whether the specified ancient data exists 176 // in the freezer. 177 func (f *Freezer) HasAncient(kind string, number uint64) (bool, error) { 178 if table := f.tables[kind]; table != nil { 179 return table.has(number), nil 180 } 181 return false, nil 182 } 183 184 // Ancient retrieves an ancient binary blob from the append-only immutable files. 185 func (f *Freezer) Ancient(kind string, number uint64) ([]byte, error) { 186 if table := f.tables[kind]; table != nil { 187 return table.Retrieve(number) 188 } 189 return nil, errUnknownTable 190 } 191 192 // AncientRange retrieves multiple items in sequence, starting from the index 'start'. 193 // It will return 194 // - at most 'max' items, 195 // - at least 1 item (even if exceeding the maxByteSize), but will otherwise 196 // return as many items as fit into maxByteSize. 197 func (f *Freezer) AncientRange(kind string, start, count, maxBytes uint64) ([][]byte, error) { 198 if table := f.tables[kind]; table != nil { 199 return table.RetrieveItems(start, count, maxBytes) 200 } 201 return nil, errUnknownTable 202 } 203 204 // Ancients returns the length of the frozen items. 205 func (f *Freezer) Ancients() (uint64, error) { 206 return atomic.LoadUint64(&f.frozen), nil 207 } 208 209 // Tail returns the number of first stored item in the freezer. 210 func (f *Freezer) Tail() (uint64, error) { 211 return atomic.LoadUint64(&f.tail), nil 212 } 213 214 // AncientSize returns the ancient size of the specified category. 215 func (f *Freezer) AncientSize(kind string) (uint64, error) { 216 // This needs the write lock to avoid data races on table fields. 217 // Speed doesn't matter here, AncientSize is for debugging. 218 f.writeLock.RLock() 219 defer f.writeLock.RUnlock() 220 221 if table := f.tables[kind]; table != nil { 222 return table.size() 223 } 224 return 0, errUnknownTable 225 } 226 227 // ReadAncients runs the given read operation while ensuring that no writes take place 228 // on the underlying freezer. 229 func (f *Freezer) ReadAncients(fn func(ethdb.AncientReaderOp) error) (err error) { 230 f.writeLock.RLock() 231 defer f.writeLock.RUnlock() 232 233 return fn(f) 234 } 235 236 // ModifyAncients runs the given write operation. 237 func (f *Freezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) (writeSize int64, err error) { 238 if f.readonly { 239 return 0, errReadOnly 240 } 241 f.writeLock.Lock() 242 defer f.writeLock.Unlock() 243 244 // Roll back all tables to the starting position in case of error. 245 prevItem := atomic.LoadUint64(&f.frozen) 246 defer func() { 247 if err != nil { 248 // The write operation has failed. Go back to the previous item position. 249 for name, table := range f.tables { 250 err := table.truncateHead(prevItem) 251 if err != nil { 252 log.Error("Freezer table roll-back failed", "table", name, "index", prevItem, "err", err) 253 } 254 } 255 } 256 }() 257 258 f.writeBatch.reset() 259 if err := fn(f.writeBatch); err != nil { 260 return 0, err 261 } 262 item, writeSize, err := f.writeBatch.commit() 263 if err != nil { 264 return 0, err 265 } 266 atomic.StoreUint64(&f.frozen, item) 267 return writeSize, nil 268 } 269 270 // TruncateHead discards any recent data above the provided threshold number. 271 func (f *Freezer) TruncateHead(items uint64) error { 272 if f.readonly { 273 return errReadOnly 274 } 275 f.writeLock.Lock() 276 defer f.writeLock.Unlock() 277 278 if atomic.LoadUint64(&f.frozen) <= items { 279 return nil 280 } 281 for _, table := range f.tables { 282 if err := table.truncateHead(items); err != nil { 283 return err 284 } 285 } 286 atomic.StoreUint64(&f.frozen, items) 287 return nil 288 } 289 290 // TruncateTail discards any recent data below the provided threshold number. 291 func (f *Freezer) TruncateTail(tail uint64) error { 292 if f.readonly { 293 return errReadOnly 294 } 295 f.writeLock.Lock() 296 defer f.writeLock.Unlock() 297 298 if atomic.LoadUint64(&f.tail) >= tail { 299 return nil 300 } 301 for _, table := range f.tables { 302 if err := table.truncateTail(tail); err != nil { 303 return err 304 } 305 } 306 atomic.StoreUint64(&f.tail, tail) 307 return nil 308 } 309 310 // Sync flushes all data tables to disk. 311 func (f *Freezer) Sync() error { 312 var errs []error 313 for _, table := range f.tables { 314 if err := table.Sync(); err != nil { 315 errs = append(errs, err) 316 } 317 } 318 if errs != nil { 319 return fmt.Errorf("%v", errs) 320 } 321 return nil 322 } 323 324 // validate checks that every table has the same length. 325 // Used instead of `repair` in readonly mode. 326 func (f *Freezer) validate() error { 327 if len(f.tables) == 0 { 328 return nil 329 } 330 var ( 331 length uint64 332 name string 333 ) 334 // Hack to get length of any table 335 for kind, table := range f.tables { 336 length = atomic.LoadUint64(&table.items) 337 name = kind 338 break 339 } 340 // Now check every table against that length 341 for kind, table := range f.tables { 342 items := atomic.LoadUint64(&table.items) 343 if length != items { 344 return fmt.Errorf("freezer tables %s and %s have differing lengths: %d != %d", kind, name, items, length) 345 } 346 } 347 atomic.StoreUint64(&f.frozen, length) 348 return nil 349 } 350 351 // repair truncates all data tables to the same length. 352 func (f *Freezer) repair() error { 353 var ( 354 head = uint64(math.MaxUint64) 355 tail = uint64(0) 356 ) 357 for _, table := range f.tables { 358 items := atomic.LoadUint64(&table.items) 359 if head > items { 360 head = items 361 } 362 hidden := atomic.LoadUint64(&table.itemHidden) 363 if hidden > tail { 364 tail = hidden 365 } 366 } 367 for _, table := range f.tables { 368 if err := table.truncateHead(head); err != nil { 369 return err 370 } 371 if err := table.truncateTail(tail); err != nil { 372 return err 373 } 374 } 375 atomic.StoreUint64(&f.frozen, head) 376 atomic.StoreUint64(&f.tail, tail) 377 return nil 378 } 379 380 // convertLegacyFn takes a raw freezer entry in an older format and 381 // returns it in the new format. 382 type convertLegacyFn = func([]byte) ([]byte, error) 383 384 // MigrateTable processes the entries in a given table in sequence 385 // converting them to a new format if they're of an old format. 386 func (f *Freezer) MigrateTable(kind string, convert convertLegacyFn) error { 387 if f.readonly { 388 return errReadOnly 389 } 390 f.writeLock.Lock() 391 defer f.writeLock.Unlock() 392 393 table, ok := f.tables[kind] 394 if !ok { 395 return errUnknownTable 396 } 397 // forEach iterates every entry in the table serially and in order, calling `fn` 398 // with the item as argument. If `fn` returns an error the iteration stops 399 // and that error will be returned. 400 forEach := func(t *freezerTable, offset uint64, fn func(uint64, []byte) error) error { 401 var ( 402 items = atomic.LoadUint64(&t.items) 403 batchSize = uint64(1024) 404 maxBytes = uint64(1024 * 1024) 405 ) 406 for i := offset; i < items; { 407 if i+batchSize > items { 408 batchSize = items - i 409 } 410 data, err := t.RetrieveItems(i, batchSize, maxBytes) 411 if err != nil { 412 return err 413 } 414 for j, item := range data { 415 if err := fn(i+uint64(j), item); err != nil { 416 return err 417 } 418 } 419 i += uint64(len(data)) 420 } 421 return nil 422 } 423 // TODO(s1na): This is a sanity-check since as of now no process does tail-deletion. But the migration 424 // process assumes no deletion at tail and needs to be modified to account for that. 425 if table.itemOffset > 0 || table.itemHidden > 0 { 426 return fmt.Errorf("migration not supported for tail-deleted freezers") 427 } 428 ancientsPath := filepath.Dir(table.index.Name()) 429 // Set up new dir for the migrated table, the content of which 430 // we'll at the end move over to the ancients dir. 431 migrationPath := filepath.Join(ancientsPath, "migration") 432 newTable, err := NewFreezerTable(migrationPath, kind, table.noCompression, false) 433 if err != nil { 434 return err 435 } 436 var ( 437 batch = newTable.newBatch() 438 out []byte 439 start = time.Now() 440 logged = time.Now() 441 offset = newTable.items 442 ) 443 if offset > 0 { 444 log.Info("found previous migration attempt", "migrated", offset) 445 } 446 // Iterate through entries and transform them 447 if err := forEach(table, offset, func(i uint64, blob []byte) error { 448 if i%10000 == 0 && time.Since(logged) > 16*time.Second { 449 log.Info("Processing legacy elements", "count", i, "elapsed", common.PrettyDuration(time.Since(start))) 450 logged = time.Now() 451 } 452 out, err = convert(blob) 453 if err != nil { 454 return err 455 } 456 if err := batch.AppendRaw(i, out); err != nil { 457 return err 458 } 459 return nil 460 }); err != nil { 461 return err 462 } 463 if err := batch.commit(); err != nil { 464 return err 465 } 466 log.Info("Replacing old table files with migrated ones", "elapsed", common.PrettyDuration(time.Since(start))) 467 // Release and delete old table files. Note this won't 468 // delete the index file. 469 table.releaseFilesAfter(0, true) 470 471 if err := newTable.Close(); err != nil { 472 return err 473 } 474 files, err := os.ReadDir(migrationPath) 475 if err != nil { 476 return err 477 } 478 // Move migrated files to ancients dir. 479 for _, f := range files { 480 // This will replace the old index file as a side-effect. 481 if err := os.Rename(filepath.Join(migrationPath, f.Name()), filepath.Join(ancientsPath, f.Name())); err != nil { 482 return err 483 } 484 } 485 // Delete by now empty dir. 486 if err := os.Remove(migrationPath); err != nil { 487 return err 488 } 489 490 return nil 491 } 492 493 // AncientDatadir returns the root directory path of the ancient store. 494 func (f *Freezer) AncientDatadir() (string, error) { 495 return f.datadir, nil 496 }