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 }