github.com/ethereum/go-ethereum@v1.14.3/core/rawdb/chain_freezer.go (about)

     1  // Copyright 2022 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  	"sync"
    23  	"time"
    24  
    25  	"github.com/ethereum/go-ethereum/common"
    26  	"github.com/ethereum/go-ethereum/ethdb"
    27  	"github.com/ethereum/go-ethereum/log"
    28  	"github.com/ethereum/go-ethereum/params"
    29  )
    30  
    31  const (
    32  	// freezerRecheckInterval is the frequency to check the key-value database for
    33  	// chain progression that might permit new blocks to be frozen into immutable
    34  	// storage.
    35  	freezerRecheckInterval = time.Minute
    36  
    37  	// freezerBatchLimit is the maximum number of blocks to freeze in one batch
    38  	// before doing an fsync and deleting it from the key-value store.
    39  	freezerBatchLimit = 30000
    40  )
    41  
    42  // chainFreezer is a wrapper of chain ancient store with additional chain freezing
    43  // feature. The background thread will keep moving ancient chain segments from
    44  // key-value database to flat files for saving space on live database.
    45  type chainFreezer struct {
    46  	ethdb.AncientStore // Ancient store for storing cold chain segment
    47  
    48  	quit    chan struct{}
    49  	wg      sync.WaitGroup
    50  	trigger chan chan struct{} // Manual blocking freeze trigger, test determinism
    51  }
    52  
    53  // newChainFreezer initializes the freezer for ancient chain segment.
    54  //
    55  //   - if the empty directory is given, initializes the pure in-memory
    56  //     state freezer (e.g. dev mode).
    57  //   - if non-empty directory is given, initializes the regular file-based
    58  //     state freezer.
    59  func newChainFreezer(datadir string, namespace string, readonly bool) (*chainFreezer, error) {
    60  	var (
    61  		err     error
    62  		freezer ethdb.AncientStore
    63  	)
    64  	if datadir == "" {
    65  		freezer = NewMemoryFreezer(readonly, chainFreezerNoSnappy)
    66  	} else {
    67  		freezer, err = NewFreezer(datadir, namespace, readonly, freezerTableSize, chainFreezerNoSnappy)
    68  	}
    69  	if err != nil {
    70  		return nil, err
    71  	}
    72  	return &chainFreezer{
    73  		AncientStore: freezer,
    74  		quit:         make(chan struct{}),
    75  		trigger:      make(chan chan struct{}),
    76  	}, nil
    77  }
    78  
    79  // Close closes the chain freezer instance and terminates the background thread.
    80  func (f *chainFreezer) Close() error {
    81  	select {
    82  	case <-f.quit:
    83  	default:
    84  		close(f.quit)
    85  	}
    86  	f.wg.Wait()
    87  	return f.AncientStore.Close()
    88  }
    89  
    90  // readHeadNumber returns the number of chain head block. 0 is returned if the
    91  // block is unknown or not available yet.
    92  func (f *chainFreezer) readHeadNumber(db ethdb.KeyValueReader) uint64 {
    93  	hash := ReadHeadBlockHash(db)
    94  	if hash == (common.Hash{}) {
    95  		log.Error("Head block is not reachable")
    96  		return 0
    97  	}
    98  	number := ReadHeaderNumber(db, hash)
    99  	if number == nil {
   100  		log.Error("Number of head block is missing")
   101  		return 0
   102  	}
   103  	return *number
   104  }
   105  
   106  // readFinalizedNumber returns the number of finalized block. 0 is returned
   107  // if the block is unknown or not available yet.
   108  func (f *chainFreezer) readFinalizedNumber(db ethdb.KeyValueReader) uint64 {
   109  	hash := ReadFinalizedBlockHash(db)
   110  	if hash == (common.Hash{}) {
   111  		return 0
   112  	}
   113  	number := ReadHeaderNumber(db, hash)
   114  	if number == nil {
   115  		log.Error("Number of finalized block is missing")
   116  		return 0
   117  	}
   118  	return *number
   119  }
   120  
   121  // freezeThreshold returns the threshold for chain freezing. It's determined
   122  // by formula: max(finality, HEAD-params.FullImmutabilityThreshold).
   123  func (f *chainFreezer) freezeThreshold(db ethdb.KeyValueReader) (uint64, error) {
   124  	var (
   125  		head      = f.readHeadNumber(db)
   126  		final     = f.readFinalizedNumber(db)
   127  		headLimit uint64
   128  	)
   129  	if head > params.FullImmutabilityThreshold {
   130  		headLimit = head - params.FullImmutabilityThreshold
   131  	}
   132  	if final == 0 && headLimit == 0 {
   133  		return 0, errors.New("freezing threshold is not available")
   134  	}
   135  	if final > headLimit {
   136  		return final, nil
   137  	}
   138  	return headLimit, nil
   139  }
   140  
   141  // freeze is a background thread that periodically checks the blockchain for any
   142  // import progress and moves ancient data from the fast database into the freezer.
   143  //
   144  // This functionality is deliberately broken off from block importing to avoid
   145  // incurring additional data shuffling delays on block propagation.
   146  func (f *chainFreezer) freeze(db ethdb.KeyValueStore) {
   147  	var (
   148  		backoff   bool
   149  		triggered chan struct{} // Used in tests
   150  		nfdb      = &nofreezedb{KeyValueStore: db}
   151  	)
   152  	timer := time.NewTimer(freezerRecheckInterval)
   153  	defer timer.Stop()
   154  
   155  	for {
   156  		select {
   157  		case <-f.quit:
   158  			log.Info("Freezer shutting down")
   159  			return
   160  		default:
   161  		}
   162  		if backoff {
   163  			// If we were doing a manual trigger, notify it
   164  			if triggered != nil {
   165  				triggered <- struct{}{}
   166  				triggered = nil
   167  			}
   168  			select {
   169  			case <-timer.C:
   170  				backoff = false
   171  				timer.Reset(freezerRecheckInterval)
   172  			case triggered = <-f.trigger:
   173  				backoff = false
   174  			case <-f.quit:
   175  				return
   176  			}
   177  		}
   178  		threshold, err := f.freezeThreshold(nfdb)
   179  		if err != nil {
   180  			backoff = true
   181  			log.Debug("Current full block not old enough to freeze", "err", err)
   182  			continue
   183  		}
   184  		frozen, _ := f.Ancients() // no error will occur, safe to ignore
   185  
   186  		// Short circuit if the blocks below threshold are already frozen.
   187  		if frozen != 0 && frozen-1 >= threshold {
   188  			backoff = true
   189  			log.Debug("Ancient blocks frozen already", "threshold", threshold, "frozen", frozen)
   190  			continue
   191  		}
   192  		// Seems we have data ready to be frozen, process in usable batches
   193  		var (
   194  			start = time.Now()
   195  			first = frozen    // the first block to freeze
   196  			last  = threshold // the last block to freeze
   197  		)
   198  		if last-first+1 > freezerBatchLimit {
   199  			last = freezerBatchLimit + first - 1
   200  		}
   201  		ancients, err := f.freezeRange(nfdb, first, last)
   202  		if err != nil {
   203  			log.Error("Error in block freeze operation", "err", err)
   204  			backoff = true
   205  			continue
   206  		}
   207  		// Batch of blocks have been frozen, flush them before wiping from key-value store
   208  		if err := f.Sync(); err != nil {
   209  			log.Crit("Failed to flush frozen tables", "err", err)
   210  		}
   211  		// Wipe out all data from the active database
   212  		batch := db.NewBatch()
   213  		for i := 0; i < len(ancients); i++ {
   214  			// Always keep the genesis block in active database
   215  			if first+uint64(i) != 0 {
   216  				DeleteBlockWithoutNumber(batch, ancients[i], first+uint64(i))
   217  				DeleteCanonicalHash(batch, first+uint64(i))
   218  			}
   219  		}
   220  		if err := batch.Write(); err != nil {
   221  			log.Crit("Failed to delete frozen canonical blocks", "err", err)
   222  		}
   223  		batch.Reset()
   224  
   225  		// Wipe out side chains also and track dangling side chains
   226  		var dangling []common.Hash
   227  		frozen, _ = f.Ancients() // Needs reload after during freezeRange
   228  		for number := first; number < frozen; number++ {
   229  			// Always keep the genesis block in active database
   230  			if number != 0 {
   231  				dangling = ReadAllHashes(db, number)
   232  				for _, hash := range dangling {
   233  					log.Trace("Deleting side chain", "number", number, "hash", hash)
   234  					DeleteBlock(batch, hash, number)
   235  				}
   236  			}
   237  		}
   238  		if err := batch.Write(); err != nil {
   239  			log.Crit("Failed to delete frozen side blocks", "err", err)
   240  		}
   241  		batch.Reset()
   242  
   243  		// Step into the future and delete any dangling side chains
   244  		if frozen > 0 {
   245  			tip := frozen
   246  			for len(dangling) > 0 {
   247  				drop := make(map[common.Hash]struct{})
   248  				for _, hash := range dangling {
   249  					log.Debug("Dangling parent from Freezer", "number", tip-1, "hash", hash)
   250  					drop[hash] = struct{}{}
   251  				}
   252  				children := ReadAllHashes(db, tip)
   253  				for i := 0; i < len(children); i++ {
   254  					// Dig up the child and ensure it's dangling
   255  					child := ReadHeader(nfdb, children[i], tip)
   256  					if child == nil {
   257  						log.Error("Missing dangling header", "number", tip, "hash", children[i])
   258  						continue
   259  					}
   260  					if _, ok := drop[child.ParentHash]; !ok {
   261  						children = append(children[:i], children[i+1:]...)
   262  						i--
   263  						continue
   264  					}
   265  					// Delete all block data associated with the child
   266  					log.Debug("Deleting dangling block", "number", tip, "hash", children[i], "parent", child.ParentHash)
   267  					DeleteBlock(batch, children[i], tip)
   268  				}
   269  				dangling = children
   270  				tip++
   271  			}
   272  			if err := batch.Write(); err != nil {
   273  				log.Crit("Failed to delete dangling side blocks", "err", err)
   274  			}
   275  		}
   276  
   277  		// Log something friendly for the user
   278  		context := []interface{}{
   279  			"blocks", frozen - first, "elapsed", common.PrettyDuration(time.Since(start)), "number", frozen - 1,
   280  		}
   281  		if n := len(ancients); n > 0 {
   282  			context = append(context, []interface{}{"hash", ancients[n-1]}...)
   283  		}
   284  		log.Debug("Deep froze chain segment", context...)
   285  
   286  		// Avoid database thrashing with tiny writes
   287  		if frozen-first < freezerBatchLimit {
   288  			backoff = true
   289  		}
   290  	}
   291  }
   292  
   293  // freezeRange moves a batch of chain segments from the fast database to the freezer.
   294  // The parameters (number, limit) specify the relevant block range, both of which
   295  // are included.
   296  func (f *chainFreezer) freezeRange(nfdb *nofreezedb, number, limit uint64) (hashes []common.Hash, err error) {
   297  	hashes = make([]common.Hash, 0, limit-number+1)
   298  
   299  	_, err = f.ModifyAncients(func(op ethdb.AncientWriteOp) error {
   300  		for ; number <= limit; number++ {
   301  			// Retrieve all the components of the canonical block.
   302  			hash := ReadCanonicalHash(nfdb, number)
   303  			if hash == (common.Hash{}) {
   304  				return fmt.Errorf("canonical hash missing, can't freeze block %d", number)
   305  			}
   306  			header := ReadHeaderRLP(nfdb, hash, number)
   307  			if len(header) == 0 {
   308  				return fmt.Errorf("block header missing, can't freeze block %d", number)
   309  			}
   310  			body := ReadBodyRLP(nfdb, hash, number)
   311  			if len(body) == 0 {
   312  				return fmt.Errorf("block body missing, can't freeze block %d", number)
   313  			}
   314  			receipts := ReadReceiptsRLP(nfdb, hash, number)
   315  			if len(receipts) == 0 {
   316  				return fmt.Errorf("block receipts missing, can't freeze block %d", number)
   317  			}
   318  			td := ReadTdRLP(nfdb, hash, number)
   319  			if len(td) == 0 {
   320  				return fmt.Errorf("total difficulty missing, can't freeze block %d", number)
   321  			}
   322  
   323  			// Write to the batch.
   324  			if err := op.AppendRaw(ChainFreezerHashTable, number, hash[:]); err != nil {
   325  				return fmt.Errorf("can't write hash to Freezer: %v", err)
   326  			}
   327  			if err := op.AppendRaw(ChainFreezerHeaderTable, number, header); err != nil {
   328  				return fmt.Errorf("can't write header to Freezer: %v", err)
   329  			}
   330  			if err := op.AppendRaw(ChainFreezerBodiesTable, number, body); err != nil {
   331  				return fmt.Errorf("can't write body to Freezer: %v", err)
   332  			}
   333  			if err := op.AppendRaw(ChainFreezerReceiptTable, number, receipts); err != nil {
   334  				return fmt.Errorf("can't write receipts to Freezer: %v", err)
   335  			}
   336  			if err := op.AppendRaw(ChainFreezerDifficultyTable, number, td); err != nil {
   337  				return fmt.Errorf("can't write td to Freezer: %v", err)
   338  			}
   339  			hashes = append(hashes, hash)
   340  		}
   341  		return nil
   342  	})
   343  	return hashes, err
   344  }