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

     1  package slasherkv
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/binary"
     7  	"fmt"
     8  	"sort"
     9  
    10  	ssz "github.com/ferranbt/fastssz"
    11  	"github.com/golang/snappy"
    12  	"github.com/pkg/errors"
    13  	types "github.com/prysmaticlabs/eth2-types"
    14  	slashertypes "github.com/prysmaticlabs/prysm/beacon-chain/slasher/types"
    15  	slashpb "github.com/prysmaticlabs/prysm/proto/beacon/rpc/v1"
    16  	ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1"
    17  	"github.com/prysmaticlabs/prysm/shared/bytesutil"
    18  	"github.com/prysmaticlabs/prysm/shared/hashutil"
    19  	bolt "go.etcd.io/bbolt"
    20  	"go.opencensus.io/trace"
    21  )
    22  
    23  const (
    24  	// Signing root (32 bytes) + fastsum64(attesting_indices) (8 bytes).
    25  	attestationRecordKeySize = 40
    26  	signingRootSize          = 32 // Bytes.
    27  )
    28  
    29  // LastEpochWrittenForValidators given a list of validator indices returns the latest
    30  // epoch we have recorded the validators writing data for.
    31  func (s *Store) LastEpochWrittenForValidators(
    32  	ctx context.Context, validatorIndices []types.ValidatorIndex,
    33  ) ([]*slashertypes.AttestedEpochForValidator, error) {
    34  	ctx, span := trace.StartSpan(ctx, "BeaconDB.LastEpochWrittenForValidators")
    35  	defer span.End()
    36  	attestedEpochs := make([]*slashertypes.AttestedEpochForValidator, 0)
    37  	encodedIndices := make([][]byte, len(validatorIndices))
    38  	for i, valIdx := range validatorIndices {
    39  		encodedIndices[i] = encodeValidatorIndex(valIdx)
    40  	}
    41  	err := s.db.View(func(tx *bolt.Tx) error {
    42  		bkt := tx.Bucket(attestedEpochsByValidator)
    43  		for i, encodedIndex := range encodedIndices {
    44  			epochBytes := bkt.Get(encodedIndex)
    45  			if epochBytes != nil {
    46  				var epoch types.Epoch
    47  				if err := epoch.UnmarshalSSZ(epochBytes); err != nil {
    48  					return err
    49  				}
    50  				attestedEpochs = append(attestedEpochs, &slashertypes.AttestedEpochForValidator{
    51  					ValidatorIndex: validatorIndices[i],
    52  					Epoch:          epoch,
    53  				})
    54  			}
    55  		}
    56  		return nil
    57  	})
    58  	return attestedEpochs, err
    59  }
    60  
    61  // SaveLastEpochWrittenForValidators updates the latest epoch a slice
    62  // of validator indices has attested to.
    63  func (s *Store) SaveLastEpochWrittenForValidators(
    64  	ctx context.Context, validatorIndices []types.ValidatorIndex, epoch types.Epoch,
    65  ) error {
    66  	ctx, span := trace.StartSpan(ctx, "BeaconDB.SaveLastEpochWrittenForValidators")
    67  	defer span.End()
    68  	encodedIndices := make([][]byte, len(validatorIndices))
    69  	for i, valIdx := range validatorIndices {
    70  		encodedIndices[i] = encodeValidatorIndex(valIdx)
    71  	}
    72  	encodedEpoch, err := epoch.MarshalSSZ()
    73  	if err != nil {
    74  		return err
    75  	}
    76  	return s.db.Update(func(tx *bolt.Tx) error {
    77  		bkt := tx.Bucket(attestedEpochsByValidator)
    78  		for _, encodedIndex := range encodedIndices {
    79  			if err = bkt.Put(encodedIndex, encodedEpoch); err != nil {
    80  				return err
    81  			}
    82  		}
    83  		return nil
    84  	})
    85  }
    86  
    87  // CheckAttesterDoubleVotes retries any slashable double votes that exist
    88  // for a series of input attestations.
    89  func (s *Store) CheckAttesterDoubleVotes(
    90  	ctx context.Context, attestations []*slashertypes.IndexedAttestationWrapper,
    91  ) ([]*slashertypes.AttesterDoubleVote, error) {
    92  	ctx, span := trace.StartSpan(ctx, "BeaconDB.CheckAttesterDoubleVotes")
    93  	defer span.End()
    94  	doubleVotes := make([]*slashertypes.AttesterDoubleVote, 0)
    95  	err := s.db.View(func(tx *bolt.Tx) error {
    96  		signingRootsBkt := tx.Bucket(attestationDataRootsBucket)
    97  		attRecordsBkt := tx.Bucket(attestationRecordsBucket)
    98  		for _, att := range attestations {
    99  			encEpoch := encodeTargetEpoch(att.IndexedAttestation.Data.Target.Epoch)
   100  			for _, valIdx := range att.IndexedAttestation.AttestingIndices {
   101  				encIdx := encodeValidatorIndex(types.ValidatorIndex(valIdx))
   102  				validatorEpochKey := append(encEpoch, encIdx...)
   103  				attRecordsKey := signingRootsBkt.Get(validatorEpochKey)
   104  
   105  				// An attestation record key is comprised of a signing root (32 bytes)
   106  				// and a fast sum hash of the attesting indices (8 bytes).
   107  				if len(attRecordsKey) < attestationRecordKeySize {
   108  					continue
   109  				}
   110  				encExistingAttRecord := attRecordsBkt.Get(attRecordsKey)
   111  				if encExistingAttRecord == nil {
   112  					continue
   113  				}
   114  				existingSigningRoot := bytesutil.ToBytes32(attRecordsKey[:signingRootSize])
   115  				if existingSigningRoot != att.SigningRoot {
   116  					existingAttRecord, err := decodeAttestationRecord(encExistingAttRecord)
   117  					if err != nil {
   118  						return err
   119  					}
   120  					doubleVotes = append(doubleVotes, &slashertypes.AttesterDoubleVote{
   121  						ValidatorIndex:         types.ValidatorIndex(valIdx),
   122  						Target:                 att.IndexedAttestation.Data.Target.Epoch,
   123  						PrevAttestationWrapper: existingAttRecord,
   124  						AttestationWrapper:     att,
   125  					})
   126  				}
   127  			}
   128  		}
   129  		return nil
   130  	})
   131  	return doubleVotes, err
   132  }
   133  
   134  // AttestationRecordForValidator given a validator index and a target epoch,
   135  // retrieves an existing attestation record we have stored in the database.
   136  func (s *Store) AttestationRecordForValidator(
   137  	ctx context.Context, validatorIdx types.ValidatorIndex, targetEpoch types.Epoch,
   138  ) (*slashertypes.IndexedAttestationWrapper, error) {
   139  	ctx, span := trace.StartSpan(ctx, "BeaconDB.AttestationRecordForValidator")
   140  	defer span.End()
   141  	var record *slashertypes.IndexedAttestationWrapper
   142  	encIdx := encodeValidatorIndex(validatorIdx)
   143  	encEpoch := encodeTargetEpoch(targetEpoch)
   144  	key := append(encEpoch, encIdx...)
   145  	err := s.db.View(func(tx *bolt.Tx) error {
   146  		signingRootsBkt := tx.Bucket(attestationDataRootsBucket)
   147  		attRecordKey := signingRootsBkt.Get(key)
   148  		if attRecordKey == nil {
   149  			return nil
   150  		}
   151  		attRecordsBkt := tx.Bucket(attestationRecordsBucket)
   152  		indexedAttBytes := attRecordsBkt.Get(attRecordKey)
   153  		if indexedAttBytes == nil {
   154  			return nil
   155  		}
   156  		decoded, err := decodeAttestationRecord(indexedAttBytes)
   157  		if err != nil {
   158  			return err
   159  		}
   160  		record = decoded
   161  		return nil
   162  	})
   163  	return record, err
   164  }
   165  
   166  // SaveAttestationRecordsForValidators saves attestation records for the specified indices.
   167  func (s *Store) SaveAttestationRecordsForValidators(
   168  	ctx context.Context,
   169  	attestations []*slashertypes.IndexedAttestationWrapper,
   170  ) error {
   171  	ctx, span := trace.StartSpan(ctx, "BeaconDB.SaveAttestationRecordsForValidators")
   172  	defer span.End()
   173  	encodedTargetEpoch := make([][]byte, len(attestations))
   174  	encodedRecords := make([][]byte, len(attestations))
   175  	encodedIndices := make([][]byte, len(attestations))
   176  	for i, att := range attestations {
   177  		encEpoch := encodeTargetEpoch(att.IndexedAttestation.Data.Target.Epoch)
   178  		value, err := encodeAttestationRecord(att)
   179  		if err != nil {
   180  			return err
   181  		}
   182  		indicesBytes := make([]byte, len(att.IndexedAttestation.AttestingIndices)*8)
   183  		for _, idx := range att.IndexedAttestation.AttestingIndices {
   184  			encodedIdx := encodeValidatorIndex(types.ValidatorIndex(idx))
   185  			indicesBytes = append(indicesBytes, encodedIdx...)
   186  		}
   187  		encodedIndices[i] = indicesBytes
   188  		encodedTargetEpoch[i] = encEpoch
   189  		encodedRecords[i] = value
   190  	}
   191  	return s.db.Update(func(tx *bolt.Tx) error {
   192  		attRecordsBkt := tx.Bucket(attestationRecordsBucket)
   193  		signingRootsBkt := tx.Bucket(attestationDataRootsBucket)
   194  		for i, att := range attestations {
   195  			// An attestation record key is comprised of the signing root (32 bytes)
   196  			// and a fastsum64 of the attesting indices (8 bytes). This is used
   197  			// to have a more optimal schema
   198  			attIndicesHash := hashutil.FastSum64(encodedIndices[i])
   199  			attRecordKey := append(
   200  				att.SigningRoot[:], ssz.MarshalUint64(make([]byte, 0), attIndicesHash)...,
   201  			)
   202  			if err := attRecordsBkt.Put(attRecordKey, encodedRecords[i]); err != nil {
   203  				return err
   204  			}
   205  			for _, valIdx := range att.IndexedAttestation.AttestingIndices {
   206  				encIdx := encodeValidatorIndex(types.ValidatorIndex(valIdx))
   207  				key := append(encodedTargetEpoch[i], encIdx...)
   208  				if err := signingRootsBkt.Put(key, attRecordKey); err != nil {
   209  					return err
   210  				}
   211  			}
   212  		}
   213  		return nil
   214  	})
   215  }
   216  
   217  // LoadSlasherChunks given a chunk kind and a disk keys, retrieves chunks for a validator
   218  // min or max span used by slasher from our database.
   219  func (s *Store) LoadSlasherChunks(
   220  	ctx context.Context, kind slashertypes.ChunkKind, diskKeys [][]byte,
   221  ) ([][]uint16, []bool, error) {
   222  	ctx, span := trace.StartSpan(ctx, "BeaconDB.LoadSlasherChunk")
   223  	defer span.End()
   224  	chunks := make([][]uint16, 0)
   225  	var exists []bool
   226  	err := s.db.View(func(tx *bolt.Tx) error {
   227  		bkt := tx.Bucket(slasherChunksBucket)
   228  		for _, diskKey := range diskKeys {
   229  			key := append(ssz.MarshalUint8(make([]byte, 0), uint8(kind)), diskKey...)
   230  			chunkBytes := bkt.Get(key)
   231  			if chunkBytes == nil {
   232  				chunks = append(chunks, []uint16{})
   233  				exists = append(exists, false)
   234  				continue
   235  			}
   236  			chunk, err := decodeSlasherChunk(chunkBytes)
   237  			if err != nil {
   238  				return err
   239  			}
   240  			chunks = append(chunks, chunk)
   241  			exists = append(exists, true)
   242  		}
   243  		return nil
   244  	})
   245  	return chunks, exists, err
   246  }
   247  
   248  // SaveSlasherChunks given a chunk kind, list of disk keys, and list of chunks,
   249  // saves the chunks to our database for use by slasher in slashing detection.
   250  func (s *Store) SaveSlasherChunks(
   251  	ctx context.Context, kind slashertypes.ChunkKind, chunkKeys [][]byte, chunks [][]uint16,
   252  ) error {
   253  	ctx, span := trace.StartSpan(ctx, "BeaconDB.SaveSlasherChunks")
   254  	defer span.End()
   255  	encodedKeys := make([][]byte, len(chunkKeys))
   256  	encodedChunks := make([][]byte, len(chunkKeys))
   257  	for i := 0; i < len(chunkKeys); i++ {
   258  		encodedKeys[i] = append(ssz.MarshalUint8(make([]byte, 0), uint8(kind)), chunkKeys[i]...)
   259  		encodedChunks[i] = encodeSlasherChunk(chunks[i])
   260  	}
   261  	return s.db.Update(func(tx *bolt.Tx) error {
   262  		bkt := tx.Bucket(slasherChunksBucket)
   263  		for i := 0; i < len(chunkKeys); i++ {
   264  			if err := bkt.Put(encodedKeys[i], encodedChunks[i]); err != nil {
   265  				return err
   266  			}
   267  		}
   268  		return nil
   269  	})
   270  }
   271  
   272  // CheckDoubleBlockProposals takes in a list of proposals and for each,
   273  // checks if there already exists a proposal at the same slot+validatorIndex combination. If so,
   274  // We check if the existing signing root is not-empty and is different than the incoming
   275  // proposal signing root. If so, we return a double block proposal object.
   276  func (s *Store) CheckDoubleBlockProposals(
   277  	ctx context.Context, proposals []*slashertypes.SignedBlockHeaderWrapper,
   278  ) ([]*ethpb.ProposerSlashing, error) {
   279  	ctx, span := trace.StartSpan(ctx, "BeaconDB.CheckDoubleBlockProposals")
   280  	defer span.End()
   281  	proposerSlashings := make([]*ethpb.ProposerSlashing, 0, len(proposals))
   282  	err := s.db.View(func(tx *bolt.Tx) error {
   283  		bkt := tx.Bucket(proposalRecordsBucket)
   284  		for _, proposal := range proposals {
   285  			key, err := keyForValidatorProposal(proposal)
   286  			if err != nil {
   287  				return err
   288  			}
   289  			encExistingProposalWrapper := bkt.Get(key)
   290  			if len(encExistingProposalWrapper) < signingRootSize {
   291  				continue
   292  			}
   293  			existingSigningRoot := bytesutil.ToBytes32(encExistingProposalWrapper[:signingRootSize])
   294  			if existingSigningRoot != proposal.SigningRoot {
   295  				existingProposalWrapper, err := decodeProposalRecord(encExistingProposalWrapper)
   296  				if err != nil {
   297  					return err
   298  				}
   299  				proposerSlashings = append(proposerSlashings, &ethpb.ProposerSlashing{
   300  					Header_1: existingProposalWrapper.SignedBeaconBlockHeader,
   301  					Header_2: proposal.SignedBeaconBlockHeader,
   302  				})
   303  			}
   304  		}
   305  		return nil
   306  	})
   307  	return proposerSlashings, err
   308  }
   309  
   310  // SaveBlockProposals takes in a list of block proposals and saves them to our
   311  // proposal records bucket in the database.
   312  func (s *Store) SaveBlockProposals(
   313  	ctx context.Context, proposals []*slashertypes.SignedBlockHeaderWrapper,
   314  ) error {
   315  	ctx, span := trace.StartSpan(ctx, "BeaconDB.SaveBlockProposals")
   316  	defer span.End()
   317  	encodedKeys := make([][]byte, len(proposals))
   318  	encodedProposals := make([][]byte, len(proposals))
   319  	for i, proposal := range proposals {
   320  		key, err := keyForValidatorProposal(proposal)
   321  		if err != nil {
   322  			return err
   323  		}
   324  		enc, err := encodeProposalRecord(proposal)
   325  		if err != nil {
   326  			return err
   327  		}
   328  		encodedKeys[i] = key
   329  		encodedProposals[i] = enc
   330  	}
   331  	return s.db.Update(func(tx *bolt.Tx) error {
   332  		bkt := tx.Bucket(proposalRecordsBucket)
   333  		for i := range proposals {
   334  			if err := bkt.Put(encodedKeys[i], encodedProposals[i]); err != nil {
   335  				return err
   336  			}
   337  		}
   338  		return nil
   339  	})
   340  }
   341  
   342  // HighestAttestations retrieves the last attestation data from the database for all indices.
   343  func (s *Store) HighestAttestations(
   344  	ctx context.Context,
   345  	indices []types.ValidatorIndex,
   346  ) ([]*slashpb.HighestAttestation, error) {
   347  	if len(indices) == 0 {
   348  		return nil, nil
   349  	}
   350  	// Sort indices to keep DB interactions short.
   351  	sort.SliceStable(indices, func(i, j int) bool {
   352  		return uint64(indices[i]) < uint64(indices[j])
   353  	})
   354  
   355  	var err error
   356  	encodedIndices := make([][]byte, len(indices))
   357  	for i, valIdx := range indices {
   358  		encodedIndices[i] = encodeValidatorIndex(valIdx)
   359  	}
   360  
   361  	history := make([]*slashpb.HighestAttestation, 0, len(encodedIndices))
   362  	err = s.db.View(func(tx *bolt.Tx) error {
   363  		signingRootsBkt := tx.Bucket(attestationDataRootsBucket)
   364  		attRecordsBkt := tx.Bucket(attestationRecordsBucket)
   365  		for i := 0; i < len(encodedIndices); i++ {
   366  			c := signingRootsBkt.Cursor()
   367  			for k, v := c.Last(); k != nil; k, v = c.Prev() {
   368  				if suffixForAttestationRecordsKey(k, encodedIndices[i]) {
   369  					encodedAttRecord := attRecordsBkt.Get(v)
   370  					if encodedAttRecord == nil {
   371  						continue
   372  					}
   373  					attWrapper, err := decodeAttestationRecord(encodedAttRecord)
   374  					if err != nil {
   375  						return err
   376  					}
   377  					highestAtt := &slashpb.HighestAttestation{
   378  						ValidatorIndex:     uint64(indices[i]),
   379  						HighestSourceEpoch: attWrapper.IndexedAttestation.Data.Source.Epoch,
   380  						HighestTargetEpoch: attWrapper.IndexedAttestation.Data.Target.Epoch,
   381  					}
   382  					history = append(history, highestAtt)
   383  					break
   384  				}
   385  			}
   386  		}
   387  		return nil
   388  	})
   389  	return history, err
   390  }
   391  
   392  func suffixForAttestationRecordsKey(key, encodedValidatorIndex []byte) bool {
   393  	encIdx := key[8:]
   394  	return bytes.Equal(encIdx, encodedValidatorIndex)
   395  }
   396  
   397  // Disk key for a validator proposal, including a slot+validatorIndex as a byte slice.
   398  func keyForValidatorProposal(proposal *slashertypes.SignedBlockHeaderWrapper) ([]byte, error) {
   399  	encSlot, err := proposal.SignedBeaconBlockHeader.Header.Slot.MarshalSSZ()
   400  	if err != nil {
   401  		return nil, err
   402  	}
   403  	encValidatorIdx := encodeValidatorIndex(proposal.SignedBeaconBlockHeader.Header.ProposerIndex)
   404  	return append(encSlot, encValidatorIdx...), nil
   405  }
   406  
   407  func encodeSlasherChunk(chunk []uint16) []byte {
   408  	val := make([]byte, 0)
   409  	for i := 0; i < len(chunk); i++ {
   410  		val = append(val, ssz.MarshalUint16(make([]byte, 0), chunk[i])...)
   411  	}
   412  	return snappy.Encode(nil, val)
   413  }
   414  
   415  func decodeSlasherChunk(enc []byte) ([]uint16, error) {
   416  	chunkBytes, err := snappy.Decode(nil, enc)
   417  	if err != nil {
   418  		return nil, err
   419  	}
   420  	chunk := make([]uint16, 0)
   421  	for i := 0; i < len(chunkBytes); i += 2 {
   422  		distance := ssz.UnmarshallUint16(chunkBytes[i : i+2])
   423  		chunk = append(chunk, distance)
   424  	}
   425  	return chunk, nil
   426  }
   427  
   428  // Decode attestation record from bytes.
   429  func encodeAttestationRecord(att *slashertypes.IndexedAttestationWrapper) ([]byte, error) {
   430  	if att == nil || att.IndexedAttestation == nil {
   431  		return []byte{}, errors.New("nil proposal record")
   432  	}
   433  	encodedAtt, err := att.IndexedAttestation.MarshalSSZ()
   434  	if err != nil {
   435  		return nil, err
   436  	}
   437  	compressedAtt := snappy.Encode(nil, encodedAtt)
   438  	return append(att.SigningRoot[:], compressedAtt...), nil
   439  }
   440  
   441  // Decode attestation record from bytes.
   442  func decodeAttestationRecord(encoded []byte) (*slashertypes.IndexedAttestationWrapper, error) {
   443  	if len(encoded) < signingRootSize {
   444  		return nil, fmt.Errorf("wrong length for encoded attestation record, want 32, got %d", len(encoded))
   445  	}
   446  	signingRoot := encoded[:signingRootSize]
   447  	decodedAtt := &ethpb.IndexedAttestation{}
   448  	decodedAttBytes, err := snappy.Decode(nil, encoded[signingRootSize:])
   449  	if err != nil {
   450  		return nil, err
   451  	}
   452  	if err := decodedAtt.UnmarshalSSZ(decodedAttBytes); err != nil {
   453  		return nil, err
   454  	}
   455  	return &slashertypes.IndexedAttestationWrapper{
   456  		IndexedAttestation: decodedAtt,
   457  		SigningRoot:        bytesutil.ToBytes32(signingRoot),
   458  	}, nil
   459  }
   460  
   461  func encodeProposalRecord(blkHdr *slashertypes.SignedBlockHeaderWrapper) ([]byte, error) {
   462  	if blkHdr == nil || blkHdr.SignedBeaconBlockHeader == nil {
   463  		return []byte{}, errors.New("nil proposal record")
   464  	}
   465  	encodedHdr, err := blkHdr.SignedBeaconBlockHeader.MarshalSSZ()
   466  	if err != nil {
   467  		return nil, err
   468  	}
   469  	compressedHdr := snappy.Encode(nil, encodedHdr)
   470  	return append(blkHdr.SigningRoot[:], compressedHdr...), nil
   471  }
   472  
   473  func decodeProposalRecord(encoded []byte) (*slashertypes.SignedBlockHeaderWrapper, error) {
   474  	if len(encoded) < signingRootSize {
   475  		return nil, fmt.Errorf(
   476  			"wrong length for encoded proposal record, want %d, got %d", signingRootSize, len(encoded),
   477  		)
   478  	}
   479  	signingRoot := encoded[:signingRootSize]
   480  	decodedBlkHdr := &ethpb.SignedBeaconBlockHeader{}
   481  	decodedHdrBytes, err := snappy.Decode(nil, encoded[signingRootSize:])
   482  	if err != nil {
   483  		return nil, err
   484  	}
   485  	if err := decodedBlkHdr.UnmarshalSSZ(decodedHdrBytes); err != nil {
   486  		return nil, err
   487  	}
   488  	return &slashertypes.SignedBlockHeaderWrapper{
   489  		SignedBeaconBlockHeader: decodedBlkHdr,
   490  		SigningRoot:             bytesutil.ToBytes32(signingRoot),
   491  	}, nil
   492  }
   493  
   494  // Encodes an epoch into little-endian bytes.
   495  func encodeTargetEpoch(epoch types.Epoch) []byte {
   496  	buf := make([]byte, 8)
   497  	binary.LittleEndian.PutUint64(buf, uint64(epoch))
   498  	return buf
   499  }
   500  
   501  // Encodes a validator index using 5 bytes instead of 8 as a
   502  // client optimization to save space in the database. Because the max validator
   503  // registry size is 2**40, this is a safe optimization.
   504  func encodeValidatorIndex(index types.ValidatorIndex) []byte {
   505  	buf := make([]byte, 5)
   506  	v := uint64(index)
   507  	buf[0] = byte(v)
   508  	buf[1] = byte(v >> 8)
   509  	buf[2] = byte(v >> 16)
   510  	buf[3] = byte(v >> 24)
   511  	buf[4] = byte(v >> 32)
   512  	return buf
   513  }