github.com/skoak/go-ethereum@v1.9.7/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/atomic"
    26  	"time"
    27  
    28  	"github.com/ethereum/go-ethereum/common"
    29  	"github.com/ethereum/go-ethereum/ethdb"
    30  	"github.com/ethereum/go-ethereum/log"
    31  	"github.com/ethereum/go-ethereum/metrics"
    32  	"github.com/ethereum/go-ethereum/params"
    33  	"github.com/prometheus/tsdb/fileutil"
    34  )
    35  
    36  var (
    37  	// errUnknownTable is returned if the user attempts to read from a table that is
    38  	// not tracked by the freezer.
    39  	errUnknownTable = errors.New("unknown table")
    40  
    41  	// errOutOrderInsertion is returned if the user attempts to inject out-of-order
    42  	// binary blobs into the freezer.
    43  	errOutOrderInsertion = errors.New("the append operation is out-order")
    44  
    45  	// errSymlinkDatadir is returned if the ancient directory specified by user
    46  	// is a symbolic link.
    47  	errSymlinkDatadir = errors.New("symbolic link datadir is not supported")
    48  )
    49  
    50  const (
    51  	// freezerRecheckInterval is the frequency to check the key-value database for
    52  	// chain progression that might permit new blocks to be frozen into immutable
    53  	// storage.
    54  	freezerRecheckInterval = time.Minute
    55  
    56  	// freezerBatchLimit is the maximum number of blocks to freeze in one batch
    57  	// before doing an fsync and deleting it from the key-value store.
    58  	freezerBatchLimit = 30000
    59  )
    60  
    61  // freezer is an memory mapped append-only database to store immutable chain data
    62  // into flat files:
    63  //
    64  // - The append only nature ensures that disk writes are minimized.
    65  // - The memory mapping ensures we can max out system memory for caching without
    66  //   reserving it for go-ethereum. This would also reduce the memory requirements
    67  //   of Geth, and thus also GC overhead.
    68  type freezer struct {
    69  	// WARNING: The `frozen` field is accessed atomically. On 32 bit platforms, only
    70  	// 64-bit aligned fields can be atomic. The struct is guaranteed to be so aligned,
    71  	// so take advantage of that (https://golang.org/pkg/sync/atomic/#pkg-note-BUG).
    72  	frozen uint64 // Number of blocks already frozen
    73  
    74  	tables       map[string]*freezerTable // Data tables for storing everything
    75  	instanceLock fileutil.Releaser        // File-system lock to prevent double opens
    76  }
    77  
    78  // newFreezer creates a chain freezer that moves ancient chain data into
    79  // append-only flat file containers.
    80  func newFreezer(datadir string, namespace string) (*freezer, error) {
    81  	// Create the initial freezer object
    82  	var (
    83  		readMeter  = metrics.NewRegisteredMeter(namespace+"ancient/read", nil)
    84  		writeMeter = metrics.NewRegisteredMeter(namespace+"ancient/write", nil)
    85  		sizeGauge  = metrics.NewRegisteredGauge(namespace+"ancient/size", nil)
    86  	)
    87  	// Ensure the datadir is not a symbolic link if it exists.
    88  	if info, err := os.Lstat(datadir); !os.IsNotExist(err) {
    89  		if info.Mode()&os.ModeSymlink != 0 {
    90  			log.Warn("Symbolic link ancient database is not supported", "path", datadir)
    91  			return nil, errSymlinkDatadir
    92  		}
    93  	}
    94  	// Leveldb uses LOCK as the filelock filename. To prevent the
    95  	// name collision, we use FLOCK as the lock name.
    96  	lock, _, err := fileutil.Flock(filepath.Join(datadir, "FLOCK"))
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  	// Open all the supported data tables
   101  	freezer := &freezer{
   102  		tables:       make(map[string]*freezerTable),
   103  		instanceLock: lock,
   104  	}
   105  	for name, disableSnappy := range freezerNoSnappy {
   106  		table, err := newTable(datadir, name, readMeter, writeMeter, sizeGauge, disableSnappy)
   107  		if err != nil {
   108  			for _, table := range freezer.tables {
   109  				table.Close()
   110  			}
   111  			lock.Release()
   112  			return nil, err
   113  		}
   114  		freezer.tables[name] = table
   115  	}
   116  	if err := freezer.repair(); err != nil {
   117  		for _, table := range freezer.tables {
   118  			table.Close()
   119  		}
   120  		lock.Release()
   121  		return nil, err
   122  	}
   123  	log.Info("Opened ancient database", "database", datadir)
   124  	return freezer, nil
   125  }
   126  
   127  // Close terminates the chain freezer, unmapping all the data files.
   128  func (f *freezer) Close() error {
   129  	var errs []error
   130  	for _, table := range f.tables {
   131  		if err := table.Close(); err != nil {
   132  			errs = append(errs, err)
   133  		}
   134  	}
   135  	if err := f.instanceLock.Release(); err != nil {
   136  		errs = append(errs, err)
   137  	}
   138  	if errs != nil {
   139  		return fmt.Errorf("%v", errs)
   140  	}
   141  	return nil
   142  }
   143  
   144  // HasAncient returns an indicator whether the specified ancient data exists
   145  // in the freezer.
   146  func (f *freezer) HasAncient(kind string, number uint64) (bool, error) {
   147  	if table := f.tables[kind]; table != nil {
   148  		return table.has(number), nil
   149  	}
   150  	return false, nil
   151  }
   152  
   153  // Ancient retrieves an ancient binary blob from the append-only immutable files.
   154  func (f *freezer) Ancient(kind string, number uint64) ([]byte, error) {
   155  	if table := f.tables[kind]; table != nil {
   156  		return table.Retrieve(number)
   157  	}
   158  	return nil, errUnknownTable
   159  }
   160  
   161  // Ancients returns the length of the frozen items.
   162  func (f *freezer) Ancients() (uint64, error) {
   163  	return atomic.LoadUint64(&f.frozen), nil
   164  }
   165  
   166  // AncientSize returns the ancient size of the specified category.
   167  func (f *freezer) AncientSize(kind string) (uint64, error) {
   168  	if table := f.tables[kind]; table != nil {
   169  		return table.size()
   170  	}
   171  	return 0, errUnknownTable
   172  }
   173  
   174  // AppendAncient injects all binary blobs belong to block at the end of the
   175  // append-only immutable table files.
   176  //
   177  // Notably, this function is lock free but kind of thread-safe. All out-of-order
   178  // injection will be rejected. But if two injections with same number happen at
   179  // the same time, we can get into the trouble.
   180  func (f *freezer) AppendAncient(number uint64, hash, header, body, receipts, td []byte) (err error) {
   181  	// Ensure the binary blobs we are appending is continuous with freezer.
   182  	if atomic.LoadUint64(&f.frozen) != number {
   183  		return errOutOrderInsertion
   184  	}
   185  	// Rollback all inserted data if any insertion below failed to ensure
   186  	// the tables won't out of sync.
   187  	defer func() {
   188  		if err != nil {
   189  			rerr := f.repair()
   190  			if rerr != nil {
   191  				log.Crit("Failed to repair freezer", "err", rerr)
   192  			}
   193  			log.Info("Append ancient failed", "number", number, "err", err)
   194  		}
   195  	}()
   196  	// Inject all the components into the relevant data tables
   197  	if err := f.tables[freezerHashTable].Append(f.frozen, hash[:]); err != nil {
   198  		log.Error("Failed to append ancient hash", "number", f.frozen, "hash", hash, "err", err)
   199  		return err
   200  	}
   201  	if err := f.tables[freezerHeaderTable].Append(f.frozen, header); err != nil {
   202  		log.Error("Failed to append ancient header", "number", f.frozen, "hash", hash, "err", err)
   203  		return err
   204  	}
   205  	if err := f.tables[freezerBodiesTable].Append(f.frozen, body); err != nil {
   206  		log.Error("Failed to append ancient body", "number", f.frozen, "hash", hash, "err", err)
   207  		return err
   208  	}
   209  	if err := f.tables[freezerReceiptTable].Append(f.frozen, receipts); err != nil {
   210  		log.Error("Failed to append ancient receipts", "number", f.frozen, "hash", hash, "err", err)
   211  		return err
   212  	}
   213  	if err := f.tables[freezerDifficultyTable].Append(f.frozen, td); err != nil {
   214  		log.Error("Failed to append ancient difficulty", "number", f.frozen, "hash", hash, "err", err)
   215  		return err
   216  	}
   217  	atomic.AddUint64(&f.frozen, 1) // Only modify atomically
   218  	return nil
   219  }
   220  
   221  // Truncate discards any recent data above the provided threshold number.
   222  func (f *freezer) TruncateAncients(items uint64) error {
   223  	if atomic.LoadUint64(&f.frozen) <= items {
   224  		return nil
   225  	}
   226  	for _, table := range f.tables {
   227  		if err := table.truncate(items); err != nil {
   228  			return err
   229  		}
   230  	}
   231  	atomic.StoreUint64(&f.frozen, items)
   232  	return nil
   233  }
   234  
   235  // sync flushes all data tables to disk.
   236  func (f *freezer) Sync() error {
   237  	var errs []error
   238  	for _, table := range f.tables {
   239  		if err := table.Sync(); err != nil {
   240  			errs = append(errs, err)
   241  		}
   242  	}
   243  	if errs != nil {
   244  		return fmt.Errorf("%v", errs)
   245  	}
   246  	return nil
   247  }
   248  
   249  // freeze is a background thread that periodically checks the blockchain for any
   250  // import progress and moves ancient data from the fast database into the freezer.
   251  //
   252  // This functionality is deliberately broken off from block importing to avoid
   253  // incurring additional data shuffling delays on block propagation.
   254  func (f *freezer) freeze(db ethdb.KeyValueStore) {
   255  	nfdb := &nofreezedb{KeyValueStore: db}
   256  
   257  	for {
   258  		// Retrieve the freezing threshold.
   259  		hash := ReadHeadBlockHash(nfdb)
   260  		if hash == (common.Hash{}) {
   261  			log.Debug("Current full block hash unavailable") // new chain, empty database
   262  			time.Sleep(freezerRecheckInterval)
   263  			continue
   264  		}
   265  		number := ReadHeaderNumber(nfdb, hash)
   266  		switch {
   267  		case number == nil:
   268  			log.Error("Current full block number unavailable", "hash", hash)
   269  			time.Sleep(freezerRecheckInterval)
   270  			continue
   271  
   272  		case *number < params.ImmutabilityThreshold:
   273  			log.Debug("Current full block not old enough", "number", *number, "hash", hash, "delay", params.ImmutabilityThreshold)
   274  			time.Sleep(freezerRecheckInterval)
   275  			continue
   276  
   277  		case *number-params.ImmutabilityThreshold <= f.frozen:
   278  			log.Debug("Ancient blocks frozen already", "number", *number, "hash", hash, "frozen", f.frozen)
   279  			time.Sleep(freezerRecheckInterval)
   280  			continue
   281  		}
   282  		head := ReadHeader(nfdb, hash, *number)
   283  		if head == nil {
   284  			log.Error("Current full block unavailable", "number", *number, "hash", hash)
   285  			time.Sleep(freezerRecheckInterval)
   286  			continue
   287  		}
   288  		// Seems we have data ready to be frozen, process in usable batches
   289  		limit := *number - params.ImmutabilityThreshold
   290  		if limit-f.frozen > freezerBatchLimit {
   291  			limit = f.frozen + freezerBatchLimit
   292  		}
   293  		var (
   294  			start    = time.Now()
   295  			first    = f.frozen
   296  			ancients = make([]common.Hash, 0, limit)
   297  		)
   298  		for f.frozen < limit {
   299  			// Retrieves all the components of the canonical block
   300  			hash := ReadCanonicalHash(nfdb, f.frozen)
   301  			if hash == (common.Hash{}) {
   302  				log.Error("Canonical hash missing, can't freeze", "number", f.frozen)
   303  				break
   304  			}
   305  			header := ReadHeaderRLP(nfdb, hash, f.frozen)
   306  			if len(header) == 0 {
   307  				log.Error("Block header missing, can't freeze", "number", f.frozen, "hash", hash)
   308  				break
   309  			}
   310  			body := ReadBodyRLP(nfdb, hash, f.frozen)
   311  			if len(body) == 0 {
   312  				log.Error("Block body missing, can't freeze", "number", f.frozen, "hash", hash)
   313  				break
   314  			}
   315  			receipts := ReadReceiptsRLP(nfdb, hash, f.frozen)
   316  			if len(receipts) == 0 {
   317  				log.Error("Block receipts missing, can't freeze", "number", f.frozen, "hash", hash)
   318  				break
   319  			}
   320  			td := ReadTdRLP(nfdb, hash, f.frozen)
   321  			if len(td) == 0 {
   322  				log.Error("Total difficulty missing, can't freeze", "number", f.frozen, "hash", hash)
   323  				break
   324  			}
   325  			log.Trace("Deep froze ancient block", "number", f.frozen, "hash", hash)
   326  			// Inject all the components into the relevant data tables
   327  			if err := f.AppendAncient(f.frozen, hash[:], header, body, receipts, td); err != nil {
   328  				break
   329  			}
   330  			ancients = append(ancients, hash)
   331  		}
   332  		// Batch of blocks have been frozen, flush them before wiping from leveldb
   333  		if err := f.Sync(); err != nil {
   334  			log.Crit("Failed to flush frozen tables", "err", err)
   335  		}
   336  		// Wipe out all data from the active database
   337  		batch := db.NewBatch()
   338  		for i := 0; i < len(ancients); i++ {
   339  			// Always keep the genesis block in active database
   340  			if first+uint64(i) != 0 {
   341  				DeleteBlockWithoutNumber(batch, ancients[i], first+uint64(i))
   342  				DeleteCanonicalHash(batch, first+uint64(i))
   343  			}
   344  		}
   345  		if err := batch.Write(); err != nil {
   346  			log.Crit("Failed to delete frozen canonical blocks", "err", err)
   347  		}
   348  		batch.Reset()
   349  		// Wipe out side chain also.
   350  		for number := first; number < f.frozen; number++ {
   351  			// Always keep the genesis block in active database
   352  			if number != 0 {
   353  				for _, hash := range ReadAllHashes(db, number) {
   354  					DeleteBlock(batch, hash, number)
   355  				}
   356  			}
   357  		}
   358  		if err := batch.Write(); err != nil {
   359  			log.Crit("Failed to delete frozen side blocks", "err", err)
   360  		}
   361  		// Log something friendly for the user
   362  		context := []interface{}{
   363  			"blocks", f.frozen - first, "elapsed", common.PrettyDuration(time.Since(start)), "number", f.frozen - 1,
   364  		}
   365  		if n := len(ancients); n > 0 {
   366  			context = append(context, []interface{}{"hash", ancients[n-1]}...)
   367  		}
   368  		log.Info("Deep froze chain segment", context...)
   369  
   370  		// Avoid database thrashing with tiny writes
   371  		if f.frozen-first < freezerBatchLimit {
   372  			time.Sleep(freezerRecheckInterval)
   373  		}
   374  	}
   375  }
   376  
   377  // repair truncates all data tables to the same length.
   378  func (f *freezer) repair() error {
   379  	min := uint64(math.MaxUint64)
   380  	for _, table := range f.tables {
   381  		items := atomic.LoadUint64(&table.items)
   382  		if min > items {
   383  			min = items
   384  		}
   385  	}
   386  	for _, table := range f.tables {
   387  		if err := table.truncate(min); err != nil {
   388  			return err
   389  		}
   390  	}
   391  	atomic.StoreUint64(&f.frozen, min)
   392  	return nil
   393  }