github.com/core-coin/go-core/v2@v2.1.9/core/rawdb/freezer.go (about)

     1  // Copyright 2019 by the Authors
     2  // This file is part of the go-core library.
     3  //
     4  // The go-core 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-core 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-core 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/prometheus/tsdb/fileutil"
    30  
    31  	"github.com/core-coin/go-core/v2/xcbdb"
    32  
    33  	"github.com/core-coin/go-core/v2/common"
    34  	"github.com/core-coin/go-core/v2/log"
    35  	"github.com/core-coin/go-core/v2/metrics"
    36  	"github.com/core-coin/go-core/v2/params"
    37  )
    38  
    39  var (
    40  	// errUnknownTable is returned if the user attempts to read from a table that is
    41  	// not tracked by the freezer.
    42  	errUnknownTable = errors.New("unknown table")
    43  
    44  	// errOutOrderInsertion is returned if the user attempts to inject out-of-order
    45  	// binary blobs into the freezer.
    46  	errOutOrderInsertion = errors.New("the append operation is out-order")
    47  
    48  	// errSymlinkDatadir is returned if the ancient directory specified by user
    49  	// is a symbolic link.
    50  	errSymlinkDatadir = errors.New("symbolic link datadir is not supported")
    51  )
    52  
    53  const (
    54  	// freezerRecheckInterval is the frequency to check the key-value database for
    55  	// chain progression that might permit new blocks to be frozen into immutable
    56  	// storage.
    57  	freezerRecheckInterval = time.Minute
    58  
    59  	// freezerBatchLimit is the maximum number of blocks to freeze in one batch
    60  	// before doing an fsync and deleting it from the key-value store.
    61  	freezerBatchLimit = 30000
    62  )
    63  
    64  // freezer is an memory mapped append-only database to store immutable chain data
    65  // into flat files:
    66  //
    67  //   - The append only nature ensures that disk writes are minimized.
    68  //   - The memory mapping ensures we can max out system memory for caching without
    69  //     reserving it for go-core. This would also reduce the memory requirements
    70  //     of Gocore, and thus also GC overhead.
    71  type freezer struct {
    72  	// WARNING: The `frozen` field is accessed atomically. On 32 bit platforms, only
    73  	// 64-bit aligned fields can be atomic. The struct is guaranteed to be so aligned,
    74  	// so take advantage of that (https://golang.org/pkg/sync/atomic/#pkg-note-BUG).
    75  	frozen    uint64 // Number of blocks already frozen
    76  	threshold uint64 // Number of recent blocks not to freeze (params.FullImmutabilityThreshold apart from tests)
    77  
    78  	tables       map[string]*freezerTable // Data tables for storing everything
    79  	instanceLock fileutil.Releaser        // File-system lock to prevent double opens
    80  
    81  	trigger chan chan struct{} // Manual blocking freeze trigger, test determinism
    82  
    83  	quit      chan struct{}
    84  	closeOnce sync.Once
    85  }
    86  
    87  // newFreezer creates a chain freezer that moves ancient chain data into
    88  // append-only flat file containers.
    89  func newFreezer(datadir string, namespace string) (*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  		threshold:    params.FullImmutabilityThreshold,
   112  		tables:       make(map[string]*freezerTable),
   113  		instanceLock: lock,
   114  		trigger:      make(chan chan struct{}),
   115  		quit:         make(chan struct{}),
   116  	}
   117  	for name, disableSnappy := range freezerNoSnappy {
   118  		table, err := newTable(datadir, name, readMeter, writeMeter, sizeGauge, disableSnappy)
   119  		if err != nil {
   120  			for _, table := range freezer.tables {
   121  				table.Close()
   122  			}
   123  			lock.Release()
   124  			return nil, err
   125  		}
   126  		freezer.tables[name] = table
   127  	}
   128  	if err := freezer.repair(); err != nil {
   129  		for _, table := range freezer.tables {
   130  			table.Close()
   131  		}
   132  		lock.Release()
   133  		return nil, err
   134  	}
   135  	log.Info("Opened ancient database", "database", datadir)
   136  	return freezer, nil
   137  }
   138  
   139  // Close terminates the chain freezer, unmapping all the data files.
   140  func (f *freezer) Close() error {
   141  	var errs []error
   142  	f.closeOnce.Do(func() {
   143  		f.quit <- struct{}{}
   144  		for _, table := range f.tables {
   145  			if err := table.Close(); err != nil {
   146  				errs = append(errs, err)
   147  			}
   148  		}
   149  		if err := f.instanceLock.Release(); err != nil {
   150  			errs = append(errs, err)
   151  		}
   152  	})
   153  	if errs != nil {
   154  		return fmt.Errorf("%v", errs)
   155  	}
   156  	return nil
   157  }
   158  
   159  // HasAncient returns an indicator whether the specified ancient data exists
   160  // in the freezer.
   161  func (f *freezer) HasAncient(kind string, number uint64) (bool, error) {
   162  	if table := f.tables[kind]; table != nil {
   163  		return table.has(number), nil
   164  	}
   165  	return false, nil
   166  }
   167  
   168  // Ancient retrieves an ancient binary blob from the append-only immutable files.
   169  func (f *freezer) Ancient(kind string, number uint64) ([]byte, error) {
   170  	if table := f.tables[kind]; table != nil {
   171  		return table.Retrieve(number)
   172  	}
   173  	return nil, errUnknownTable
   174  }
   175  
   176  // Ancients returns the length of the frozen items.
   177  func (f *freezer) Ancients() (uint64, error) {
   178  	return atomic.LoadUint64(&f.frozen), nil
   179  }
   180  
   181  // AncientSize returns the ancient size of the specified category.
   182  func (f *freezer) AncientSize(kind string) (uint64, error) {
   183  	if table := f.tables[kind]; table != nil {
   184  		return table.size()
   185  	}
   186  	return 0, errUnknownTable
   187  }
   188  
   189  // AppendAncient injects all binary blobs belong to block at the end of the
   190  // append-only immutable table files.
   191  //
   192  // Notably, this function is lock free but kind of thread-safe. All out-of-order
   193  // injection will be rejected. But if two injections with same number happen at
   194  // the same time, we can get into the trouble.
   195  func (f *freezer) AppendAncient(number uint64, hash, header, body, receipts, td []byte) (err error) {
   196  	// Ensure the binary blobs we are appending is continuous with freezer.
   197  	if atomic.LoadUint64(&f.frozen) != number {
   198  		return errOutOrderInsertion
   199  	}
   200  	// Rollback all inserted data if any insertion below failed to ensure
   201  	// the tables won't out of sync.
   202  	defer func() {
   203  		if err != nil {
   204  			rerr := f.repair()
   205  			if rerr != nil {
   206  				log.Crit("Failed to repair freezer", "err", rerr)
   207  			}
   208  			log.Info("Append ancient failed", "number", number, "err", err)
   209  		}
   210  	}()
   211  	// Inject all the components into the relevant data tables
   212  	if err := f.tables[freezerHashTable].Append(f.frozen, hash[:]); err != nil {
   213  		log.Error("Failed to append ancient hash", "number", f.frozen, "hash", hash, "err", err)
   214  		return err
   215  	}
   216  	if err := f.tables[freezerHeaderTable].Append(f.frozen, header); err != nil {
   217  		log.Error("Failed to append ancient header", "number", f.frozen, "hash", hash, "err", err)
   218  		return err
   219  	}
   220  	if err := f.tables[freezerBodiesTable].Append(f.frozen, body); err != nil {
   221  		log.Error("Failed to append ancient body", "number", f.frozen, "hash", hash, "err", err)
   222  		return err
   223  	}
   224  	if err := f.tables[freezerReceiptTable].Append(f.frozen, receipts); err != nil {
   225  		log.Error("Failed to append ancient receipts", "number", f.frozen, "hash", hash, "err", err)
   226  		return err
   227  	}
   228  	if err := f.tables[freezerDifficultyTable].Append(f.frozen, td); err != nil {
   229  		log.Error("Failed to append ancient difficulty", "number", f.frozen, "hash", hash, "err", err)
   230  		return err
   231  	}
   232  	atomic.AddUint64(&f.frozen, 1) // Only modify atomically
   233  	return nil
   234  }
   235  
   236  // TruncateAncients discards any recent data above the provided threshold number.
   237  func (f *freezer) TruncateAncients(items uint64) error {
   238  	if atomic.LoadUint64(&f.frozen) <= items {
   239  		return nil
   240  	}
   241  	for _, table := range f.tables {
   242  		if err := table.truncate(items); err != nil {
   243  			return err
   244  		}
   245  	}
   246  	atomic.StoreUint64(&f.frozen, items)
   247  	return nil
   248  }
   249  
   250  // Sync flushes all data tables to disk.
   251  func (f *freezer) Sync() error {
   252  	var errs []error
   253  	for _, table := range f.tables {
   254  		if err := table.Sync(); err != nil {
   255  			errs = append(errs, err)
   256  		}
   257  	}
   258  	if errs != nil {
   259  		return fmt.Errorf("%v", errs)
   260  	}
   261  	return nil
   262  }
   263  
   264  // freeze is a background thread that periodically checks the blockchain for any
   265  // import progress and moves ancient data from the fast database into the freezer.
   266  //
   267  // This functionality is deliberately broken off from block importing to avoid
   268  // incurring additional data shuffling delays on block propagation.
   269  func (f *freezer) freeze(db xcbdb.KeyValueStore) {
   270  	nfdb := &nofreezedb{KeyValueStore: db}
   271  
   272  	var (
   273  		backoff   bool
   274  		triggered chan struct{} // Used in tests
   275  	)
   276  	for {
   277  		select {
   278  		case <-f.quit:
   279  			log.Info("Freezer shutting down")
   280  			return
   281  		default:
   282  		}
   283  		if backoff {
   284  			// If we were doing a manual trigger, notify it
   285  			if triggered != nil {
   286  				triggered <- struct{}{}
   287  				triggered = nil
   288  			}
   289  			select {
   290  			case <-time.NewTimer(freezerRecheckInterval).C:
   291  				backoff = false
   292  			case triggered = <-f.trigger:
   293  				backoff = false
   294  			case <-f.quit:
   295  				return
   296  			}
   297  		}
   298  		// Retrieve the freezing threshold.
   299  		hash := ReadHeadBlockHash(nfdb)
   300  		if hash == (common.Hash{}) {
   301  			log.Debug("Current full block hash unavailable") // new chain, empty database
   302  			backoff = true
   303  			continue
   304  		}
   305  		number := ReadHeaderNumber(nfdb, hash)
   306  		threshold := atomic.LoadUint64(&f.threshold)
   307  
   308  		switch {
   309  		case number == nil:
   310  			log.Error("Current full block number unavailable", "hash", hash)
   311  			backoff = true
   312  			continue
   313  
   314  		case *number < threshold:
   315  			log.Debug("Current full block not old enough", "number", *number, "hash", hash, "delay", threshold)
   316  			backoff = true
   317  			continue
   318  
   319  		case *number-threshold <= f.frozen:
   320  			log.Debug("Ancient blocks frozen already", "number", *number, "hash", hash, "frozen", f.frozen)
   321  			backoff = true
   322  			continue
   323  		}
   324  		head := ReadHeader(nfdb, hash, *number)
   325  		if head == nil {
   326  			log.Error("Current full block unavailable", "number", *number, "hash", hash)
   327  			backoff = true
   328  			continue
   329  		}
   330  		// Seems we have data ready to be frozen, process in usable batches
   331  		limit := *number - threshold
   332  		if limit-f.frozen > freezerBatchLimit {
   333  			limit = f.frozen + freezerBatchLimit
   334  		}
   335  		var (
   336  			start    = time.Now()
   337  			first    = f.frozen
   338  			ancients = make([]common.Hash, 0, limit-f.frozen)
   339  		)
   340  		for f.frozen <= limit {
   341  			// Retrieves all the components of the canonical block
   342  			hash := ReadCanonicalHash(nfdb, f.frozen)
   343  			if hash == (common.Hash{}) {
   344  				log.Error("Canonical hash missing, can't freeze", "number", f.frozen)
   345  				break
   346  			}
   347  			header := ReadHeaderRLP(nfdb, hash, f.frozen)
   348  			if len(header) == 0 {
   349  				log.Error("Block header missing, can't freeze", "number", f.frozen, "hash", hash)
   350  				break
   351  			}
   352  			body := ReadBodyRLP(nfdb, hash, f.frozen)
   353  			if len(body) == 0 {
   354  				log.Error("Block body missing, can't freeze", "number", f.frozen, "hash", hash)
   355  				break
   356  			}
   357  			receipts := ReadReceiptsRLP(nfdb, hash, f.frozen)
   358  			if len(receipts) == 0 {
   359  				log.Error("Block receipts missing, can't freeze", "number", f.frozen, "hash", hash)
   360  				break
   361  			}
   362  			td := ReadTdRLP(nfdb, hash, f.frozen)
   363  			if len(td) == 0 {
   364  				log.Error("Total difficulty missing, can't freeze", "number", f.frozen, "hash", hash)
   365  				break
   366  			}
   367  			log.Trace("Deep froze ancient block", "number", f.frozen, "hash", hash)
   368  			// Inject all the components into the relevant data tables
   369  			if err := f.AppendAncient(f.frozen, hash[:], header, body, receipts, td); err != nil {
   370  				break
   371  			}
   372  			ancients = append(ancients, hash)
   373  		}
   374  		// Batch of blocks have been frozen, flush them before wiping from leveldb
   375  		if err := f.Sync(); err != nil {
   376  			log.Crit("Failed to flush frozen tables", "err", err)
   377  		}
   378  		// Wipe out all data from the active database
   379  		batch := db.NewBatch()
   380  		for i := 0; i < len(ancients); i++ {
   381  			// Always keep the genesis block in active database
   382  			if first+uint64(i) != 0 {
   383  				DeleteBlockWithoutNumber(batch, ancients[i], first+uint64(i))
   384  				DeleteCanonicalHash(batch, first+uint64(i))
   385  			}
   386  		}
   387  		if err := batch.Write(); err != nil {
   388  			log.Crit("Failed to delete frozen canonical blocks", "err", err)
   389  		}
   390  		batch.Reset()
   391  
   392  		// Wipe out side chains also and track dangling side chians
   393  		var dangling []common.Hash
   394  		for number := first; number < f.frozen; number++ {
   395  			// Always keep the genesis block in active database
   396  			if number != 0 {
   397  				dangling = ReadAllHashes(db, number)
   398  				for _, hash := range dangling {
   399  					log.Trace("Deleting side chain", "number", number, "hash", hash)
   400  					DeleteBlock(batch, hash, number)
   401  				}
   402  			}
   403  		}
   404  		if err := batch.Write(); err != nil {
   405  			log.Crit("Failed to delete frozen side blocks", "err", err)
   406  		}
   407  		batch.Reset()
   408  
   409  		// Step into the future and delete and dangling side chains
   410  		if f.frozen > 0 {
   411  			tip := f.frozen
   412  			for len(dangling) > 0 {
   413  				drop := make(map[common.Hash]struct{})
   414  				for _, hash := range dangling {
   415  					log.Debug("Dangling parent from freezer", "number", tip-1, "hash", hash)
   416  					drop[hash] = struct{}{}
   417  				}
   418  				children := ReadAllHashes(db, tip)
   419  				for i := 0; i < len(children); i++ {
   420  					// Dig up the child and ensure it's dangling
   421  					child := ReadHeader(nfdb, children[i], tip)
   422  					if child == nil {
   423  						log.Error("Missing dangling header", "number", tip, "hash", children[i])
   424  						continue
   425  					}
   426  					if _, ok := drop[child.ParentHash]; !ok {
   427  						children = append(children[:i], children[i+1:]...)
   428  						i--
   429  						continue
   430  					}
   431  					// Delete all block data associated with the child
   432  					log.Debug("Deleting dangling block", "number", tip, "hash", children[i], "parent", child.ParentHash)
   433  					DeleteBlock(batch, children[i], tip)
   434  				}
   435  				dangling = children
   436  				tip++
   437  			}
   438  			if err := batch.Write(); err != nil {
   439  				log.Crit("Failed to delete dangling side blocks", "err", err)
   440  			}
   441  		}
   442  		// Log something friendly for the user
   443  		context := []interface{}{
   444  			"blocks", f.frozen - first, "elapsed", common.PrettyDuration(time.Since(start)), "number", f.frozen - 1,
   445  		}
   446  		if n := len(ancients); n > 0 {
   447  			context = append(context, []interface{}{"hash", ancients[n-1]}...)
   448  		}
   449  		log.Info("Deep froze chain segment", context...)
   450  
   451  		// Avoid database thrashing with tiny writes
   452  		if f.frozen-first < freezerBatchLimit {
   453  			backoff = true
   454  		}
   455  	}
   456  }
   457  
   458  // repair truncates all data tables to the same length.
   459  func (f *freezer) repair() error {
   460  	min := uint64(math.MaxUint64)
   461  	for _, table := range f.tables {
   462  		items := atomic.LoadUint64(&table.items)
   463  		if min > items {
   464  			min = items
   465  		}
   466  	}
   467  	for _, table := range f.tables {
   468  		if err := table.truncate(min); err != nil {
   469  			return err
   470  		}
   471  	}
   472  	atomic.StoreUint64(&f.frozen, min)
   473  	return nil
   474  }