github.com/aidoskuneen/adk-node@v0.0.0-20220315131952-2e32567cb7f4/core/rawdb/freezer.go (about)

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