github.com/aigarnetwork/aigar@v0.0.0-20191115204914-d59a6eb70f8e/core/rawdb/freezer.go (about)

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