github.com/zhiqiangxu/go-ethereum@v1.9.16-0.20210824055606-be91cfdebc48/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/prometheus/tsdb/fileutil"
    29  	"github.com/zhiqiangxu/go-ethereum/common"
    30  	"github.com/zhiqiangxu/go-ethereum/ethdb"
    31  	"github.com/zhiqiangxu/go-ethereum/log"
    32  	"github.com/zhiqiangxu/go-ethereum/metrics"
    33  	"github.com/zhiqiangxu/go-ethereum/params"
    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  	quit         chan struct{}
    77  }
    78  
    79  // newFreezer creates a chain freezer that moves ancient chain data into
    80  // append-only flat file containers.
    81  func newFreezer(datadir string, namespace string) (*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.Mode()&os.ModeSymlink != 0 {
    91  			log.Warn("Symbolic link ancient database is not supported", "path", datadir)
    92  			return nil, errSymlinkDatadir
    93  		}
    94  	}
    95  	// Leveldb uses LOCK as the filelock filename. To prevent the
    96  	// name collision, we use FLOCK as the lock name.
    97  	lock, _, err := fileutil.Flock(filepath.Join(datadir, "FLOCK"))
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  	// Open all the supported data tables
   102  	freezer := &freezer{
   103  		tables:       make(map[string]*freezerTable),
   104  		instanceLock: lock,
   105  		quit:         make(chan struct{}),
   106  	}
   107  	for name, disableSnappy := range freezerNoSnappy {
   108  		table, err := newTable(datadir, name, readMeter, writeMeter, sizeGauge, disableSnappy)
   109  		if err != nil {
   110  			for _, table := range freezer.tables {
   111  				table.Close()
   112  			}
   113  			lock.Release()
   114  			return nil, err
   115  		}
   116  		freezer.tables[name] = table
   117  	}
   118  	if err := freezer.repair(); err != nil {
   119  		for _, table := range freezer.tables {
   120  			table.Close()
   121  		}
   122  		lock.Release()
   123  		return nil, err
   124  	}
   125  	log.Info("Opened ancient database", "database", datadir)
   126  	return freezer, nil
   127  }
   128  
   129  // Close terminates the chain freezer, unmapping all the data files.
   130  func (f *freezer) Close() error {
   131  	f.quit <- struct{}{}
   132  	var errs []error
   133  	for _, table := range f.tables {
   134  		if err := table.Close(); err != nil {
   135  			errs = append(errs, err)
   136  		}
   137  	}
   138  	if err := f.instanceLock.Release(); err != nil {
   139  		errs = append(errs, err)
   140  	}
   141  	if errs != nil {
   142  		return fmt.Errorf("%v", errs)
   143  	}
   144  	return nil
   145  }
   146  
   147  // HasAncient returns an indicator whether the specified ancient data exists
   148  // in the freezer.
   149  func (f *freezer) HasAncient(kind string, number uint64) (bool, error) {
   150  	if table := f.tables[kind]; table != nil {
   151  		return table.has(number), nil
   152  	}
   153  	return false, nil
   154  }
   155  
   156  // Ancient retrieves an ancient binary blob from the append-only immutable files.
   157  func (f *freezer) Ancient(kind string, number uint64) ([]byte, error) {
   158  	if table := f.tables[kind]; table != nil {
   159  		return table.Retrieve(number)
   160  	}
   161  	return nil, errUnknownTable
   162  }
   163  
   164  // Ancients returns the length of the frozen items.
   165  func (f *freezer) Ancients() (uint64, error) {
   166  	return atomic.LoadUint64(&f.frozen), nil
   167  }
   168  
   169  // AncientSize returns the ancient size of the specified category.
   170  func (f *freezer) AncientSize(kind string) (uint64, error) {
   171  	if table := f.tables[kind]; table != nil {
   172  		return table.size()
   173  	}
   174  	return 0, errUnknownTable
   175  }
   176  
   177  // AppendAncient injects all binary blobs belong to block at the end of the
   178  // append-only immutable table files.
   179  //
   180  // Notably, this function is lock free but kind of thread-safe. All out-of-order
   181  // injection will be rejected. But if two injections with same number happen at
   182  // the same time, we can get into the trouble.
   183  func (f *freezer) AppendAncient(number uint64, hash, header, body, receipts, td []byte) (err error) {
   184  	// Ensure the binary blobs we are appending is continuous with freezer.
   185  	if atomic.LoadUint64(&f.frozen) != number {
   186  		return errOutOrderInsertion
   187  	}
   188  	// Rollback all inserted data if any insertion below failed to ensure
   189  	// the tables won't out of sync.
   190  	defer func() {
   191  		if err != nil {
   192  			rerr := f.repair()
   193  			if rerr != nil {
   194  				log.Crit("Failed to repair freezer", "err", rerr)
   195  			}
   196  			log.Info("Append ancient failed", "number", number, "err", err)
   197  		}
   198  	}()
   199  	// Inject all the components into the relevant data tables
   200  	if err := f.tables[freezerHashTable].Append(f.frozen, hash[:]); err != nil {
   201  		log.Error("Failed to append ancient hash", "number", f.frozen, "hash", hash, "err", err)
   202  		return err
   203  	}
   204  	if err := f.tables[freezerHeaderTable].Append(f.frozen, header); err != nil {
   205  		log.Error("Failed to append ancient header", "number", f.frozen, "hash", hash, "err", err)
   206  		return err
   207  	}
   208  	if err := f.tables[freezerBodiesTable].Append(f.frozen, body); err != nil {
   209  		log.Error("Failed to append ancient body", "number", f.frozen, "hash", hash, "err", err)
   210  		return err
   211  	}
   212  	if err := f.tables[freezerReceiptTable].Append(f.frozen, receipts); err != nil {
   213  		log.Error("Failed to append ancient receipts", "number", f.frozen, "hash", hash, "err", err)
   214  		return err
   215  	}
   216  	if err := f.tables[freezerDifficultyTable].Append(f.frozen, td); err != nil {
   217  		log.Error("Failed to append ancient difficulty", "number", f.frozen, "hash", hash, "err", err)
   218  		return err
   219  	}
   220  	atomic.AddUint64(&f.frozen, 1) // Only modify atomically
   221  	return nil
   222  }
   223  
   224  // Truncate discards any recent data above the provided threshold number.
   225  func (f *freezer) TruncateAncients(items uint64) error {
   226  	if atomic.LoadUint64(&f.frozen) <= items {
   227  		return nil
   228  	}
   229  	for _, table := range f.tables {
   230  		if err := table.truncate(items); err != nil {
   231  			return err
   232  		}
   233  	}
   234  	atomic.StoreUint64(&f.frozen, items)
   235  	return nil
   236  }
   237  
   238  // sync flushes all data tables to disk.
   239  func (f *freezer) Sync() error {
   240  	var errs []error
   241  	for _, table := range f.tables {
   242  		if err := table.Sync(); err != nil {
   243  			errs = append(errs, err)
   244  		}
   245  	}
   246  	if errs != nil {
   247  		return fmt.Errorf("%v", errs)
   248  	}
   249  	return nil
   250  }
   251  
   252  // freeze is a background thread that periodically checks the blockchain for any
   253  // import progress and moves ancient data from the fast database into the freezer.
   254  //
   255  // This functionality is deliberately broken off from block importing to avoid
   256  // incurring additional data shuffling delays on block propagation.
   257  func (f *freezer) freeze(db ethdb.KeyValueStore) {
   258  	nfdb := &nofreezedb{KeyValueStore: db}
   259  
   260  	backoff := false
   261  	for {
   262  		select {
   263  		case <-f.quit:
   264  			log.Info("Freezer shutting down")
   265  			return
   266  		default:
   267  		}
   268  		if backoff {
   269  			select {
   270  			case <-time.NewTimer(freezerRecheckInterval).C:
   271  				backoff = false
   272  			case <-f.quit:
   273  				return
   274  			}
   275  		}
   276  		// Retrieve the freezing threshold.
   277  		hash := ReadHeadBlockHash(nfdb)
   278  		if hash == (common.Hash{}) {
   279  			log.Debug("Current full block hash unavailable") // new chain, empty database
   280  			backoff = true
   281  			continue
   282  		}
   283  		number := ReadHeaderNumber(nfdb, hash)
   284  		switch {
   285  		case number == nil:
   286  			log.Error("Current full block number unavailable", "hash", hash)
   287  			backoff = true
   288  			continue
   289  
   290  		case *number < params.ImmutabilityThreshold:
   291  			log.Debug("Current full block not old enough", "number", *number, "hash", hash, "delay", params.ImmutabilityThreshold)
   292  			backoff = true
   293  			continue
   294  
   295  		case *number-params.ImmutabilityThreshold <= f.frozen:
   296  			log.Debug("Ancient blocks frozen already", "number", *number, "hash", hash, "frozen", f.frozen)
   297  			backoff = true
   298  			continue
   299  		}
   300  		head := ReadHeader(nfdb, hash, *number)
   301  		if head == nil {
   302  			log.Error("Current full block unavailable", "number", *number, "hash", hash)
   303  			backoff = true
   304  			continue
   305  		}
   306  		// Seems we have data ready to be frozen, process in usable batches
   307  		limit := *number - params.ImmutabilityThreshold
   308  		if limit-f.frozen > freezerBatchLimit {
   309  			limit = f.frozen + freezerBatchLimit
   310  		}
   311  		var (
   312  			start    = time.Now()
   313  			first    = f.frozen
   314  			ancients = make([]common.Hash, 0, limit)
   315  		)
   316  		for f.frozen < limit {
   317  			// Retrieves all the components of the canonical block
   318  			hash := ReadCanonicalHash(nfdb, f.frozen)
   319  			if hash == (common.Hash{}) {
   320  				log.Error("Canonical hash missing, can't freeze", "number", f.frozen)
   321  				break
   322  			}
   323  			header := ReadHeaderRLP(nfdb, hash, f.frozen)
   324  			if len(header) == 0 {
   325  				log.Error("Block header missing, can't freeze", "number", f.frozen, "hash", hash)
   326  				break
   327  			}
   328  			body := ReadBodyRLP(nfdb, hash, f.frozen)
   329  			if len(body) == 0 {
   330  				log.Error("Block body missing, can't freeze", "number", f.frozen, "hash", hash)
   331  				break
   332  			}
   333  			receipts := ReadReceiptsRLP(nfdb, hash, f.frozen)
   334  			if len(receipts) == 0 {
   335  				log.Error("Block receipts missing, can't freeze", "number", f.frozen, "hash", hash)
   336  				break
   337  			}
   338  			td := ReadTdRLP(nfdb, hash, f.frozen)
   339  			if len(td) == 0 {
   340  				log.Error("Total difficulty missing, can't freeze", "number", f.frozen, "hash", hash)
   341  				break
   342  			}
   343  			log.Trace("Deep froze ancient block", "number", f.frozen, "hash", hash)
   344  			// Inject all the components into the relevant data tables
   345  			if err := f.AppendAncient(f.frozen, hash[:], header, body, receipts, td); err != nil {
   346  				break
   347  			}
   348  			ancients = append(ancients, hash)
   349  		}
   350  		// Batch of blocks have been frozen, flush them before wiping from leveldb
   351  		if err := f.Sync(); err != nil {
   352  			log.Crit("Failed to flush frozen tables", "err", err)
   353  		}
   354  		// Wipe out all data from the active database
   355  		batch := db.NewBatch()
   356  		for i := 0; i < len(ancients); i++ {
   357  			// Always keep the genesis block in active database
   358  			if first+uint64(i) != 0 {
   359  				DeleteBlockWithoutNumber(batch, ancients[i], first+uint64(i))
   360  				DeleteCanonicalHash(batch, first+uint64(i))
   361  			}
   362  		}
   363  		if err := batch.Write(); err != nil {
   364  			log.Crit("Failed to delete frozen canonical blocks", "err", err)
   365  		}
   366  		batch.Reset()
   367  		// Wipe out side chain also.
   368  		for number := first; number < f.frozen; number++ {
   369  			// Always keep the genesis block in active database
   370  			if number != 0 {
   371  				for _, hash := range ReadAllHashes(db, number) {
   372  					DeleteBlock(batch, hash, number)
   373  				}
   374  			}
   375  		}
   376  		if err := batch.Write(); err != nil {
   377  			log.Crit("Failed to delete frozen side blocks", "err", err)
   378  		}
   379  		// Log something friendly for the user
   380  		context := []interface{}{
   381  			"blocks", f.frozen - first, "elapsed", common.PrettyDuration(time.Since(start)), "number", f.frozen - 1,
   382  		}
   383  		if n := len(ancients); n > 0 {
   384  			context = append(context, []interface{}{"hash", ancients[n-1]}...)
   385  		}
   386  		log.Info("Deep froze chain segment", context...)
   387  
   388  		// Avoid database thrashing with tiny writes
   389  		if f.frozen-first < freezerBatchLimit {
   390  			backoff = true
   391  		}
   392  	}
   393  }
   394  
   395  // repair truncates all data tables to the same length.
   396  func (f *freezer) repair() error {
   397  	min := uint64(math.MaxUint64)
   398  	for _, table := range f.tables {
   399  		items := atomic.LoadUint64(&table.items)
   400  		if min > items {
   401  			min = items
   402  		}
   403  	}
   404  	for _, table := range f.tables {
   405  		if err := table.truncate(min); err != nil {
   406  			return err
   407  		}
   408  	}
   409  	atomic.StoreUint64(&f.frozen, min)
   410  	return nil
   411  }