github.com/ethereum/go-ethereum@v1.16.1/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  
    28  	"github.com/ethereum/go-ethereum/ethdb"
    29  	"github.com/ethereum/go-ethereum/log"
    30  	"github.com/ethereum/go-ethereum/metrics"
    31  	"github.com/gofrs/flock"
    32  )
    33  
    34  var (
    35  	// errReadOnly is returned if the freezer is opened in read only mode. All the
    36  	// mutations are disallowed.
    37  	errReadOnly = errors.New("read only")
    38  
    39  	// errUnknownTable is returned if the user attempts to read from a table that is
    40  	// not tracked by the freezer.
    41  	errUnknownTable = errors.New("unknown table")
    42  
    43  	// errOutOrderInsertion is returned if the user attempts to inject out-of-order
    44  	// binary blobs into the freezer.
    45  	errOutOrderInsertion = errors.New("the append operation is out-order")
    46  
    47  	// errSymlinkDatadir is returned if the ancient directory specified by user
    48  	// is a symbolic link.
    49  	errSymlinkDatadir = errors.New("symbolic link datadir is not supported")
    50  )
    51  
    52  // freezerTableSize defines the maximum size of freezer data files.
    53  const freezerTableSize = 2 * 1000 * 1000 * 1000
    54  
    55  // Freezer is an append-only database to store immutable ordered data into
    56  // flat files:
    57  //
    58  // - The append-only nature ensures that disk writes are minimized.
    59  // - The in-order data ensures that disk reads are always optimized.
    60  type Freezer struct {
    61  	datadir string
    62  	frozen  atomic.Uint64 // Number of items already frozen
    63  	tail    atomic.Uint64 // Number of the first stored item in the freezer
    64  
    65  	// This lock synchronizes writers and the truncate operation, as well as
    66  	// the "atomic" (batched) read operations.
    67  	writeLock  sync.RWMutex
    68  	writeBatch *freezerBatch
    69  
    70  	readonly     bool
    71  	tables       map[string]*freezerTable // Data tables for storing everything
    72  	instanceLock *flock.Flock             // File-system lock to prevent double opens
    73  	closeOnce    sync.Once
    74  }
    75  
    76  // NewFreezer creates a freezer instance for maintaining immutable ordered
    77  // data according to the given parameters.
    78  //
    79  // The 'tables' argument defines the data tables. If the value of a map
    80  // entry is true, snappy compression is disabled for the table.
    81  func NewFreezer(datadir string, namespace string, readonly bool, maxTableSize uint32, tables map[string]freezerTableConfig) (*Freezer, error) {
    82  	// Create the initial freezer object
    83  	var (
    84  		readMeter  = metrics.NewRegisteredMeter(namespace+"ancient/read", nil)
    85  		writeMeter = metrics.NewRegisteredMeter(namespace+"ancient/write", nil)
    86  		sizeGauge  = metrics.NewRegisteredGauge(namespace+"ancient/size", nil)
    87  	)
    88  	// Ensure the datadir is not a symbolic link if it exists.
    89  	if info, err := os.Lstat(datadir); !os.IsNotExist(err) {
    90  		if info == nil {
    91  			log.Warn("Could not Lstat the database", "path", datadir)
    92  			return nil, errors.New("lstat failed")
    93  		}
    94  		if info.Mode()&os.ModeSymlink != 0 {
    95  			log.Warn("Symbolic link ancient database is not supported", "path", datadir)
    96  			return nil, errSymlinkDatadir
    97  		}
    98  	}
    99  	flockFile := filepath.Join(datadir, "FLOCK")
   100  	if err := os.MkdirAll(filepath.Dir(flockFile), 0755); err != nil {
   101  		return nil, err
   102  	}
   103  	// Leveldb uses LOCK as the filelock filename. To prevent the
   104  	// name collision, we use FLOCK as the lock name.
   105  	lock := flock.New(flockFile)
   106  	tryLock := lock.TryLock
   107  	if readonly {
   108  		tryLock = lock.TryRLock
   109  	}
   110  	if locked, err := tryLock(); err != nil {
   111  		return nil, err
   112  	} else if !locked {
   113  		return nil, errors.New("locking failed")
   114  	}
   115  	// Open all the supported data tables
   116  	freezer := &Freezer{
   117  		datadir:      datadir,
   118  		readonly:     readonly,
   119  		tables:       make(map[string]*freezerTable),
   120  		instanceLock: lock,
   121  	}
   122  
   123  	// Create the tables.
   124  	for name, config := range tables {
   125  		table, err := newTable(datadir, name, readMeter, writeMeter, sizeGauge, maxTableSize, config, readonly)
   126  		if err != nil {
   127  			for _, table := range freezer.tables {
   128  				table.Close()
   129  			}
   130  			lock.Unlock()
   131  			return nil, err
   132  		}
   133  		freezer.tables[name] = table
   134  	}
   135  	var err error
   136  	if freezer.readonly {
   137  		// In readonly mode only validate, don't truncate.
   138  		// validate also sets `freezer.frozen`.
   139  		err = freezer.validate()
   140  	} else {
   141  		// Truncate all tables to common length.
   142  		err = freezer.repair()
   143  	}
   144  	if err != nil {
   145  		for _, table := range freezer.tables {
   146  			table.Close()
   147  		}
   148  		lock.Unlock()
   149  		return nil, err
   150  	}
   151  
   152  	// Create the write batch.
   153  	freezer.writeBatch = newFreezerBatch(freezer)
   154  
   155  	log.Info("Opened ancient database", "database", datadir, "readonly", readonly)
   156  	return freezer, nil
   157  }
   158  
   159  // Close terminates the chain freezer, closing all the data files.
   160  func (f *Freezer) Close() error {
   161  	f.writeLock.Lock()
   162  	defer f.writeLock.Unlock()
   163  
   164  	var errs []error
   165  	f.closeOnce.Do(func() {
   166  		for _, table := range f.tables {
   167  			if err := table.Close(); err != nil {
   168  				errs = append(errs, err)
   169  			}
   170  		}
   171  		if err := f.instanceLock.Unlock(); err != nil {
   172  			errs = append(errs, err)
   173  		}
   174  	})
   175  	return errors.Join(errs...)
   176  }
   177  
   178  // AncientDatadir returns the path of the ancient store.
   179  func (f *Freezer) AncientDatadir() (string, error) {
   180  	return f.datadir, nil
   181  }
   182  
   183  // Ancient retrieves an ancient binary blob from the append-only immutable files.
   184  func (f *Freezer) Ancient(kind string, number uint64) ([]byte, error) {
   185  	if table := f.tables[kind]; table != nil {
   186  		return table.Retrieve(number)
   187  	}
   188  	return nil, errUnknownTable
   189  }
   190  
   191  // AncientRange retrieves multiple items in sequence, starting from the index 'start'.
   192  // It will return
   193  //   - at most 'count' items,
   194  //   - if maxBytes is specified: at least 1 item (even if exceeding the maxByteSize),
   195  //     but will otherwise return as many items as fit into maxByteSize.
   196  //   - if maxBytes is not specified, 'count' items will be returned if they are present.
   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 f.frozen.Load(), nil
   207  }
   208  
   209  // Tail returns the number of first stored item in the freezer.
   210  func (f *Freezer) Tail() (uint64, error) {
   211  	return f.tail.Load(), 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 := f.frozen.Load()
   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  	f.frozen.Store(item)
   267  	return writeSize, nil
   268  }
   269  
   270  // TruncateHead discards any recent data above the provided threshold number.
   271  // It returns the previous head number.
   272  func (f *Freezer) TruncateHead(items uint64) (uint64, error) {
   273  	if f.readonly {
   274  		return 0, errReadOnly
   275  	}
   276  	f.writeLock.Lock()
   277  	defer f.writeLock.Unlock()
   278  
   279  	oitems := f.frozen.Load()
   280  	if oitems <= items {
   281  		return oitems, nil
   282  	}
   283  	for _, table := range f.tables {
   284  		if err := table.truncateHead(items); err != nil {
   285  			return 0, err
   286  		}
   287  	}
   288  	f.frozen.Store(items)
   289  	return oitems, nil
   290  }
   291  
   292  // TruncateTail discards all data below the specified threshold. Note that only
   293  // 'prunable' tables will be truncated.
   294  func (f *Freezer) TruncateTail(tail uint64) (uint64, error) {
   295  	if f.readonly {
   296  		return 0, errReadOnly
   297  	}
   298  	f.writeLock.Lock()
   299  	defer f.writeLock.Unlock()
   300  
   301  	old := f.tail.Load()
   302  	if old >= tail {
   303  		return old, nil
   304  	}
   305  	for _, table := range f.tables {
   306  		if table.config.prunable {
   307  			if err := table.truncateTail(tail); err != nil {
   308  				return 0, err
   309  			}
   310  		}
   311  	}
   312  	f.tail.Store(tail)
   313  	return old, nil
   314  }
   315  
   316  // SyncAncient flushes all data tables to disk.
   317  func (f *Freezer) SyncAncient() error {
   318  	var errs []error
   319  	for _, table := range f.tables {
   320  		if err := table.Sync(); err != nil {
   321  			errs = append(errs, err)
   322  		}
   323  	}
   324  	if errs != nil {
   325  		return fmt.Errorf("%v", errs)
   326  	}
   327  	return nil
   328  }
   329  
   330  // validate checks that every table has the same boundary.
   331  // Used instead of `repair` in readonly mode.
   332  func (f *Freezer) validate() error {
   333  	if len(f.tables) == 0 {
   334  		return nil
   335  	}
   336  	var (
   337  		head       uint64
   338  		prunedTail *uint64
   339  	)
   340  	// get any head value
   341  	for _, table := range f.tables {
   342  		head = table.items.Load()
   343  		break
   344  	}
   345  	for kind, table := range f.tables {
   346  		// all tables have to have the same head
   347  		if head != table.items.Load() {
   348  			return fmt.Errorf("freezer table %s has a differing head: %d != %d", kind, table.items.Load(), head)
   349  		}
   350  		if !table.config.prunable {
   351  			// non-prunable tables have to start at 0
   352  			if table.itemHidden.Load() != 0 {
   353  				return fmt.Errorf("non-prunable freezer table '%s' has a non-zero tail: %d", kind, table.itemHidden.Load())
   354  			}
   355  		} else {
   356  			// prunable tables have to have the same length
   357  			if prunedTail == nil {
   358  				tmp := table.itemHidden.Load()
   359  				prunedTail = &tmp
   360  			}
   361  			if *prunedTail != table.itemHidden.Load() {
   362  				return fmt.Errorf("freezer table %s has differing tail: %d != %d", kind, table.itemHidden.Load(), *prunedTail)
   363  			}
   364  		}
   365  	}
   366  
   367  	if prunedTail == nil {
   368  		tmp := uint64(0)
   369  		prunedTail = &tmp
   370  	}
   371  
   372  	f.frozen.Store(head)
   373  	f.tail.Store(*prunedTail)
   374  	return nil
   375  }
   376  
   377  // repair truncates all data tables to the same length.
   378  func (f *Freezer) repair() error {
   379  	var (
   380  		head       = uint64(math.MaxUint64)
   381  		prunedTail = uint64(0)
   382  	)
   383  	// get the minimal head and the maximum tail
   384  	for _, table := range f.tables {
   385  		head = min(head, table.items.Load())
   386  		prunedTail = max(prunedTail, table.itemHidden.Load())
   387  	}
   388  	// apply the pruning
   389  	for kind, table := range f.tables {
   390  		// all tables need to have the same head
   391  		if err := table.truncateHead(head); err != nil {
   392  			return err
   393  		}
   394  		if !table.config.prunable {
   395  			// non-prunable tables have to start at 0
   396  			if table.itemHidden.Load() != 0 {
   397  				panic(fmt.Sprintf("non-prunable freezer table %s has non-zero tail: %v", kind, table.itemHidden.Load()))
   398  			}
   399  		} else {
   400  			// prunable tables have to have the same length
   401  			if err := table.truncateTail(prunedTail); err != nil {
   402  				return err
   403  			}
   404  		}
   405  	}
   406  
   407  	f.frozen.Store(head)
   408  	f.tail.Store(prunedTail)
   409  	return nil
   410  }