github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/db/slasherkv/pruning.go (about)

     1  package slasherkv
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/binary"
     7  
     8  	fssz "github.com/ferranbt/fastssz"
     9  	types "github.com/prysmaticlabs/eth2-types"
    10  	"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
    11  	bolt "go.etcd.io/bbolt"
    12  )
    13  
    14  // PruneProposals prunes all proposal data older than currentEpoch - historyLength in
    15  // specified epoch increments. BoltDB cannot handle long-running transactions, so we instead
    16  // use a cursor-based mechanism to prune at pruningEpochIncrements by opening individual bolt
    17  // transactions for each pruning iteration.
    18  func (s *Store) PruneProposals(
    19  	ctx context.Context, currentEpoch, pruningEpochIncrements, historyLength types.Epoch,
    20  ) error {
    21  	if currentEpoch < historyLength {
    22  		log.Debugf("Current epoch %d < history length %d, nothing to prune", currentEpoch, historyLength)
    23  		return nil
    24  	}
    25  	// We can prune everything less than the current epoch - history length.
    26  	endEpoch := currentEpoch - historyLength
    27  	endPruneSlot, err := helpers.StartSlot(endEpoch)
    28  	if err != nil {
    29  		return err
    30  	}
    31  	encodedEndPruneSlot := fssz.MarshalUint64([]byte{}, uint64(endPruneSlot))
    32  
    33  	// We retrieve the lowest stored slot in the proposals bucket.
    34  	var lowestSlot types.Slot
    35  	if err = s.db.View(func(tx *bolt.Tx) error {
    36  		proposalBkt := tx.Bucket(proposalRecordsBucket)
    37  		c := proposalBkt.Cursor()
    38  		k, _ := c.First()
    39  		lowestSlot = slotFromProposalKey(k)
    40  		return nil
    41  	}); err != nil {
    42  		return err
    43  	}
    44  
    45  	// If the lowest slot is greater than or equal to the end pruning slot,
    46  	// there is nothing to prune, so we return early.
    47  	if lowestSlot >= endPruneSlot {
    48  		log.Debugf("Lowest slot %d is >= pruning slot %d, nothing to prune", lowestSlot, endPruneSlot)
    49  		return nil
    50  	}
    51  
    52  	// We prune in increments of `pruningEpochIncrements` at a time to prevent
    53  	// a long-running bolt transaction which overwhelms CPU and memory.
    54  	// While we still have epochs to prune based on a cursor, we continue the pruning process.
    55  	epochAtCursor := helpers.SlotToEpoch(lowestSlot)
    56  	for epochAtCursor < endEpoch {
    57  		// Each pruning iteration involves a unique bolt transaction. Given pruning can be
    58  		// a very expensive process which puts pressure on the database, we perform
    59  		// the process in a batch-based method using a cursor to proceed to the next batch.
    60  		log.Debugf("Pruned %d/%d epochs worth of proposals", epochAtCursor, endEpoch-1)
    61  		if err = s.db.Update(func(tx *bolt.Tx) error {
    62  			proposalBkt := tx.Bucket(proposalRecordsBucket)
    63  			c := proposalBkt.Cursor()
    64  
    65  			var lastPrunedEpoch, epochsPruned types.Epoch
    66  			// We begin a pruning iteration at starting from the first item in the bucket.
    67  			for k, _ := c.First(); k != nil; k, _ = c.Next() {
    68  				// We check the slot from the current key in the database.
    69  				// If we have hit a slot that is greater than the end slot of the pruning process,
    70  				// we then completely exit the process as we are done.
    71  				if !uint64PrefixLessThan(k, encodedEndPruneSlot) {
    72  					epochAtCursor = endEpoch
    73  					return nil
    74  				}
    75  				epochAtCursor = helpers.SlotToEpoch(slotFromProposalKey(k))
    76  
    77  				// Proposals in the database look like this:
    78  				//  (slot ++ validatorIndex) => encode(proposal)
    79  				// so it is possible we have a few adjacent objects that have the same slot, such as
    80  				//  (slot = 3 ++ validatorIndex = 0) => ...
    81  				//  (slot = 3 ++ validatorIndex = 1) => ...
    82  				//  (slot = 3 ++ validatorIndex = 2) => ...
    83  				// so we only mark an epoch as pruned if the epoch of the current object
    84  				// under the cursor has changed.
    85  				if err := proposalBkt.Delete(k); err != nil {
    86  					return err
    87  				}
    88  				if epochAtCursor == 0 {
    89  					epochsPruned = 1
    90  				} else if epochAtCursor > lastPrunedEpoch {
    91  					epochsPruned++
    92  					lastPrunedEpoch = epochAtCursor
    93  				}
    94  				slasherProposalsPrunedTotal.Inc()
    95  
    96  				// If we have pruned N epochs in this pruning iteration,
    97  				// we exit from the bolt transaction.
    98  				if epochsPruned >= pruningEpochIncrements {
    99  					return nil
   100  				}
   101  			}
   102  			return nil
   103  		}); err != nil {
   104  			return err
   105  		}
   106  	}
   107  	return nil
   108  }
   109  
   110  // PruneAttestations prunes all proposal data older than historyLength.
   111  func (s *Store) PruneAttestations(
   112  	ctx context.Context, currentEpoch, pruningEpochIncrements, historyLength types.Epoch,
   113  ) error {
   114  	if currentEpoch < historyLength {
   115  		log.Debugf("Current epoch %d < history length %d, nothing to prune", currentEpoch, historyLength)
   116  		return nil
   117  	}
   118  	// We can prune everything less than the current epoch - history length.
   119  	endPruneEpoch := currentEpoch - historyLength
   120  	encodedEndPruneEpoch := fssz.MarshalUint64([]byte{}, uint64(endPruneEpoch))
   121  
   122  	// We retrieve the lowest stored epoch in the proposals bucket.
   123  	var lowestEpoch types.Epoch
   124  	if err := s.db.View(func(tx *bolt.Tx) error {
   125  		bkt := tx.Bucket(attestationDataRootsBucket)
   126  		c := bkt.Cursor()
   127  		k, _ := c.First()
   128  		lowestEpoch = types.Epoch(binary.LittleEndian.Uint64(k))
   129  		return nil
   130  	}); err != nil {
   131  		return err
   132  	}
   133  
   134  	// If the lowest slot is greater than or equal to the end pruning slot,
   135  	// there is nothing to prune, so we return early.
   136  	if lowestEpoch >= endPruneEpoch {
   137  		log.Debugf("Lowest epoch %d is >= pruning epoch %d, nothing to prune", lowestEpoch, endPruneEpoch)
   138  		return nil
   139  	}
   140  
   141  	// We prune in increments of `pruningEpochIncrements` at a time to prevent
   142  	// a long-running bolt transaction which overwhelms CPU and memory.
   143  	// While we still have epochs to prune based on a cursor, we continue the pruning process.
   144  	epochAtCursor := lowestEpoch
   145  	for epochAtCursor < endPruneEpoch {
   146  		// Each pruning iteration involves a unique bolt transaction. Given pruning can be
   147  		// a very expensive process which puts pressure on the database, we perform
   148  		// the process in a batch-based method using a cursor to proceed to the next batch.
   149  		log.Debugf("Pruned %d/%d epochs worth of attestations", epochAtCursor, endPruneEpoch-1)
   150  		if err := s.db.Update(func(tx *bolt.Tx) error {
   151  			rootsBkt := tx.Bucket(attestationDataRootsBucket)
   152  			attsBkt := tx.Bucket(attestationRecordsBucket)
   153  			c := rootsBkt.Cursor()
   154  
   155  			var lastPrunedEpoch, epochsPruned types.Epoch
   156  			// We begin a pruning iteration at starting from the first item in the bucket.
   157  			for k, v := c.First(); k != nil; k, v = c.Next() {
   158  				// We check the epoch from the current key in the database.
   159  				// If we have hit an epoch that is greater than the end epoch of the pruning process,
   160  				// we then completely exit the process as we are done.
   161  				if !uint64PrefixLessThan(k, encodedEndPruneEpoch) {
   162  					epochAtCursor = endPruneEpoch
   163  					return nil
   164  				}
   165  
   166  				epochAtCursor = types.Epoch(binary.LittleEndian.Uint64(k))
   167  
   168  				// Attestation in the database look like this:
   169  				//  (target_epoch ++ _) => encode(attestation)
   170  				// so it is possible we have a few adjacent objects that have the same slot, such as
   171  				//  (target_epoch = 3 ++ _) => encode(attestation)
   172  				// so we only mark an epoch as pruned if the epoch of the current object
   173  				// under the cursor has changed.
   174  				if err := rootsBkt.Delete(k); err != nil {
   175  					return err
   176  				}
   177  				if err := attsBkt.Delete(v); err != nil {
   178  					return err
   179  				}
   180  				if epochAtCursor == 0 {
   181  					epochsPruned = 1
   182  				} else if epochAtCursor > lastPrunedEpoch {
   183  					epochsPruned++
   184  					lastPrunedEpoch = epochAtCursor
   185  				}
   186  				slasherAttestationsPrunedTotal.Inc()
   187  
   188  				// If we have pruned N epochs in this pruning iteration,
   189  				// we exit from the bolt transaction.
   190  				if epochsPruned >= pruningEpochIncrements {
   191  					return nil
   192  				}
   193  			}
   194  			return nil
   195  		}); err != nil {
   196  			return err
   197  		}
   198  	}
   199  	return nil
   200  }
   201  
   202  func slotFromProposalKey(key []byte) types.Slot {
   203  	return types.Slot(binary.LittleEndian.Uint64(key[:8]))
   204  }
   205  
   206  func uint64PrefixLessThan(key, lessThan []byte) bool {
   207  	enc := key[:8]
   208  	return bytes.Compare(enc, lessThan) < 0
   209  }