github.com/prysmaticlabs/prysm@v1.4.4/validator/db/kv/proposer_protection.go (about)

     1  package kv
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"github.com/pkg/errors"
     8  	types "github.com/prysmaticlabs/eth2-types"
     9  	"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
    10  	"github.com/prysmaticlabs/prysm/shared/bytesutil"
    11  	"github.com/prysmaticlabs/prysm/shared/params"
    12  	bolt "go.etcd.io/bbolt"
    13  	"go.opencensus.io/trace"
    14  )
    15  
    16  // ProposalHistoryForPubkey for a validator public key.
    17  type ProposalHistoryForPubkey struct {
    18  	Proposals []Proposal
    19  }
    20  
    21  // Proposal representation for a validator public key.
    22  type Proposal struct {
    23  	Slot        types.Slot `json:"slot"`
    24  	SigningRoot []byte     `json:"signing_root"`
    25  }
    26  
    27  // ProposedPublicKeys retrieves all public keys in our proposals history bucket.
    28  func (s *Store) ProposedPublicKeys(ctx context.Context) ([][48]byte, error) {
    29  	ctx, span := trace.StartSpan(ctx, "Validator.ProposedPublicKeys")
    30  	defer span.End()
    31  	var err error
    32  	proposedPublicKeys := make([][48]byte, 0)
    33  	err = s.view(func(tx *bolt.Tx) error {
    34  		bucket := tx.Bucket(historicProposalsBucket)
    35  		return bucket.ForEach(func(key []byte, _ []byte) error {
    36  			pubKeyBytes := [48]byte{}
    37  			copy(pubKeyBytes[:], key)
    38  			proposedPublicKeys = append(proposedPublicKeys, pubKeyBytes)
    39  			return nil
    40  		})
    41  	})
    42  	return proposedPublicKeys, err
    43  }
    44  
    45  // ProposalHistoryForSlot accepts a validator public key and returns the corresponding signing root as well
    46  // as a boolean that tells us if we have a proposal history stored at the slot. It is possible we have proposed
    47  // a slot but stored a nil signing root, so the boolean helps give full information.
    48  func (s *Store) ProposalHistoryForSlot(ctx context.Context, publicKey [48]byte, slot types.Slot) ([32]byte, bool, error) {
    49  	ctx, span := trace.StartSpan(ctx, "Validator.ProposalHistoryForSlot")
    50  	defer span.End()
    51  
    52  	var err error
    53  	var proposalExists bool
    54  	signingRoot := [32]byte{}
    55  	err = s.view(func(tx *bolt.Tx) error {
    56  		bucket := tx.Bucket(historicProposalsBucket)
    57  		valBucket := bucket.Bucket(publicKey[:])
    58  		if valBucket == nil {
    59  			return nil
    60  		}
    61  		signingRootBytes := valBucket.Get(bytesutil.SlotToBytesBigEndian(slot))
    62  		if signingRootBytes == nil {
    63  			return nil
    64  		}
    65  		proposalExists = true
    66  		copy(signingRoot[:], signingRootBytes)
    67  		return nil
    68  	})
    69  	return signingRoot, proposalExists, err
    70  }
    71  
    72  // ProposalHistoryForPubKey returns the entire proposal history for a given public key.
    73  func (s *Store) ProposalHistoryForPubKey(ctx context.Context, publicKey [48]byte) ([]*Proposal, error) {
    74  	ctx, span := trace.StartSpan(ctx, "Validator.ProposalHistoryForPubKey")
    75  	defer span.End()
    76  
    77  	proposals := make([]*Proposal, 0)
    78  	err := s.view(func(tx *bolt.Tx) error {
    79  		bucket := tx.Bucket(historicProposalsBucket)
    80  		valBucket := bucket.Bucket(publicKey[:])
    81  		if valBucket == nil {
    82  			return nil
    83  		}
    84  		return valBucket.ForEach(func(slotKey, signingRootBytes []byte) error {
    85  			slot := bytesutil.BytesToSlotBigEndian(slotKey)
    86  			sr := make([]byte, 32)
    87  			copy(sr, signingRootBytes)
    88  			proposals = append(proposals, &Proposal{
    89  				Slot:        slot,
    90  				SigningRoot: sr,
    91  			})
    92  			return nil
    93  		})
    94  	})
    95  	return proposals, err
    96  }
    97  
    98  // SaveProposalHistoryForSlot saves the proposal history for the requested validator public key.
    99  // We also check if the incoming proposal slot is lower than the lowest signed proposal slot
   100  // for the validator and override its value on disk.
   101  func (s *Store) SaveProposalHistoryForSlot(ctx context.Context, pubKey [48]byte, slot types.Slot, signingRoot []byte) error {
   102  	ctx, span := trace.StartSpan(ctx, "Validator.SaveProposalHistoryForEpoch")
   103  	defer span.End()
   104  
   105  	err := s.update(func(tx *bolt.Tx) error {
   106  		bucket := tx.Bucket(historicProposalsBucket)
   107  		valBucket, err := bucket.CreateBucketIfNotExists(pubKey[:])
   108  		if err != nil {
   109  			return fmt.Errorf("could not create bucket for public key %#x", pubKey)
   110  		}
   111  
   112  		// If the incoming slot is lower than the lowest signed proposal slot, override.
   113  		lowestSignedBkt := tx.Bucket(lowestSignedProposalsBucket)
   114  		lowestSignedProposalBytes := lowestSignedBkt.Get(pubKey[:])
   115  		var lowestSignedProposalSlot types.Slot
   116  		if len(lowestSignedProposalBytes) >= 8 {
   117  			lowestSignedProposalSlot = bytesutil.BytesToSlotBigEndian(lowestSignedProposalBytes)
   118  		}
   119  		if len(lowestSignedProposalBytes) == 0 || slot < lowestSignedProposalSlot {
   120  			if err := lowestSignedBkt.Put(pubKey[:], bytesutil.SlotToBytesBigEndian(slot)); err != nil {
   121  				return err
   122  			}
   123  		}
   124  
   125  		// If the incoming slot is higher than the highest signed proposal slot, override.
   126  		highestSignedBkt := tx.Bucket(highestSignedProposalsBucket)
   127  		highestSignedProposalBytes := highestSignedBkt.Get(pubKey[:])
   128  		var highestSignedProposalSlot types.Slot
   129  		if len(highestSignedProposalBytes) >= 8 {
   130  			highestSignedProposalSlot = bytesutil.BytesToSlotBigEndian(highestSignedProposalBytes)
   131  		}
   132  		if len(highestSignedProposalBytes) == 0 || slot > highestSignedProposalSlot {
   133  			if err := highestSignedBkt.Put(pubKey[:], bytesutil.SlotToBytesBigEndian(slot)); err != nil {
   134  				return err
   135  			}
   136  		}
   137  
   138  		if err := valBucket.Put(bytesutil.SlotToBytesBigEndian(slot), signingRoot); err != nil {
   139  			return err
   140  		}
   141  		return pruneProposalHistoryBySlot(valBucket, slot)
   142  	})
   143  	return err
   144  }
   145  
   146  // LowestSignedProposal returns the lowest signed proposal slot for a validator public key.
   147  // If no data exists, a boolean of value false is returned.
   148  func (s *Store) LowestSignedProposal(ctx context.Context, publicKey [48]byte) (types.Slot, bool, error) {
   149  	ctx, span := trace.StartSpan(ctx, "Validator.LowestSignedProposal")
   150  	defer span.End()
   151  
   152  	var err error
   153  	var lowestSignedProposalSlot types.Slot
   154  	var exists bool
   155  	err = s.view(func(tx *bolt.Tx) error {
   156  		bucket := tx.Bucket(lowestSignedProposalsBucket)
   157  		lowestSignedProposalBytes := bucket.Get(publicKey[:])
   158  		// 8 because bytesutil.BytesToUint64BigEndian will return 0 if input is less than 8 bytes.
   159  		if len(lowestSignedProposalBytes) < 8 {
   160  			return nil
   161  		}
   162  		exists = true
   163  		lowestSignedProposalSlot = bytesutil.BytesToSlotBigEndian(lowestSignedProposalBytes)
   164  		return nil
   165  	})
   166  	return lowestSignedProposalSlot, exists, err
   167  }
   168  
   169  // HighestSignedProposal returns the highest signed proposal slot for a validator public key.
   170  // If no data exists, a boolean of value false is returned.
   171  func (s *Store) HighestSignedProposal(ctx context.Context, publicKey [48]byte) (types.Slot, bool, error) {
   172  	ctx, span := trace.StartSpan(ctx, "Validator.HighestSignedProposal")
   173  	defer span.End()
   174  
   175  	var err error
   176  	var highestSignedProposalSlot types.Slot
   177  	var exists bool
   178  	err = s.view(func(tx *bolt.Tx) error {
   179  		bucket := tx.Bucket(highestSignedProposalsBucket)
   180  		highestSignedProposalBytes := bucket.Get(publicKey[:])
   181  		// 8 because bytesutil.BytesToUint64BigEndian will return 0 if input is less than 8 bytes.
   182  		if len(highestSignedProposalBytes) < 8 {
   183  			return nil
   184  		}
   185  		exists = true
   186  		highestSignedProposalSlot = bytesutil.BytesToSlotBigEndian(highestSignedProposalBytes)
   187  		return nil
   188  	})
   189  	return highestSignedProposalSlot, exists, err
   190  }
   191  
   192  func pruneProposalHistoryBySlot(valBucket *bolt.Bucket, newestSlot types.Slot) error {
   193  	c := valBucket.Cursor()
   194  	for k, _ := c.First(); k != nil; k, _ = c.First() {
   195  		slot := bytesutil.BytesToSlotBigEndian(k)
   196  		epoch := helpers.SlotToEpoch(slot)
   197  		newestEpoch := helpers.SlotToEpoch(newestSlot)
   198  		// Only delete epochs that are older than the weak subjectivity period.
   199  		if epoch+params.BeaconConfig().WeakSubjectivityPeriod <= newestEpoch {
   200  			if err := c.Delete(); err != nil {
   201  				return errors.Wrapf(err, "could not prune epoch %d in proposal history", epoch)
   202  			}
   203  		} else {
   204  			// If starting from the oldest, we dont find anything prunable, stop pruning.
   205  			break
   206  		}
   207  	}
   208  	return nil
   209  }