github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/core/helpers/committee.go (about)

     1  // Package helpers contains helper functions outlined in the Ethereum Beacon Chain spec, such as
     2  // computing committees, randao, rewards/penalties, and more.
     3  package helpers
     4  
     5  import (
     6  	"bytes"
     7  	"fmt"
     8  	"sort"
     9  
    10  	"github.com/pkg/errors"
    11  	types "github.com/prysmaticlabs/eth2-types"
    12  	"github.com/prysmaticlabs/go-bitfield"
    13  	"github.com/prysmaticlabs/prysm/beacon-chain/cache"
    14  	iface "github.com/prysmaticlabs/prysm/beacon-chain/state/interface"
    15  	ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1"
    16  	"github.com/prysmaticlabs/prysm/shared/bytesutil"
    17  	"github.com/prysmaticlabs/prysm/shared/hashutil"
    18  	"github.com/prysmaticlabs/prysm/shared/params"
    19  	"github.com/prysmaticlabs/prysm/shared/sliceutil"
    20  )
    21  
    22  var committeeCache = cache.NewCommitteesCache()
    23  var proposerIndicesCache = cache.NewProposerIndicesCache()
    24  
    25  // SlotCommitteeCount returns the number of crosslink committees of a slot. The
    26  // active validator count is provided as an argument rather than a imported implementation
    27  // from the spec definition. Having the active validator count as an argument allows for
    28  // cheaper computation, instead of retrieving head state, one can retrieve the validator
    29  // count.
    30  //
    31  //
    32  // Spec pseudocode definition:
    33  //   def get_committee_count_per_slot(state: BeaconState, epoch: Epoch) -> uint64:
    34  //    """
    35  //    Return the number of committees in each slot for the given ``epoch``.
    36  //    """
    37  //    return max(uint64(1), min(
    38  //        MAX_COMMITTEES_PER_SLOT,
    39  //        uint64(len(get_active_validator_indices(state, epoch))) // SLOTS_PER_EPOCH // TARGET_COMMITTEE_SIZE,
    40  //    ))
    41  func SlotCommitteeCount(activeValidatorCount uint64) uint64 {
    42  	var committeePerSlot = activeValidatorCount / uint64(params.BeaconConfig().SlotsPerEpoch) / params.BeaconConfig().TargetCommitteeSize
    43  
    44  	if committeePerSlot > params.BeaconConfig().MaxCommitteesPerSlot {
    45  		return params.BeaconConfig().MaxCommitteesPerSlot
    46  	}
    47  	if committeePerSlot == 0 {
    48  		return 1
    49  	}
    50  
    51  	return committeePerSlot
    52  }
    53  
    54  // BeaconCommitteeFromState returns the crosslink committee of a given slot and committee index. This
    55  // is a spec implementation where state is used as an argument. In case of state retrieval
    56  // becomes expensive, consider using BeaconCommittee below.
    57  //
    58  // Spec pseudocode definition:
    59  //   def get_beacon_committee(state: BeaconState, slot: Slot, index: CommitteeIndex) -> Sequence[ValidatorIndex]:
    60  //    """
    61  //    Return the beacon committee at ``slot`` for ``index``.
    62  //    """
    63  //    epoch = compute_epoch_at_slot(slot)
    64  //    committees_per_slot = get_committee_count_per_slot(state, epoch)
    65  //    return compute_committee(
    66  //        indices=get_active_validator_indices(state, epoch),
    67  //        seed=get_seed(state, epoch, DOMAIN_BEACON_ATTESTER),
    68  //        index=(slot % SLOTS_PER_EPOCH) * committees_per_slot + index,
    69  //        count=committees_per_slot * SLOTS_PER_EPOCH,
    70  //    )
    71  func BeaconCommitteeFromState(state iface.ReadOnlyBeaconState, slot types.Slot, committeeIndex types.CommitteeIndex) ([]types.ValidatorIndex, error) {
    72  	epoch := SlotToEpoch(slot)
    73  	seed, err := Seed(state, epoch, params.BeaconConfig().DomainBeaconAttester)
    74  	if err != nil {
    75  		return nil, errors.Wrap(err, "could not get seed")
    76  	}
    77  
    78  	indices, err := committeeCache.Committee(slot, seed, committeeIndex)
    79  	if err != nil {
    80  		return nil, errors.Wrap(err, "could not interface with committee cache")
    81  	}
    82  	if indices != nil {
    83  		return indices, nil
    84  	}
    85  
    86  	activeIndices, err := ActiveValidatorIndices(state, epoch)
    87  	if err != nil {
    88  		return nil, errors.Wrap(err, "could not get active indices")
    89  	}
    90  
    91  	return BeaconCommittee(activeIndices, seed, slot, committeeIndex)
    92  }
    93  
    94  // BeaconCommittee returns the crosslink committee of a given slot and committee index. The
    95  // validator indices and seed are provided as an argument rather than a imported implementation
    96  // from the spec definition. Having them as an argument allows for cheaper computation run time.
    97  func BeaconCommittee(
    98  	validatorIndices []types.ValidatorIndex,
    99  	seed [32]byte,
   100  	slot types.Slot,
   101  	committeeIndex types.CommitteeIndex,
   102  ) ([]types.ValidatorIndex, error) {
   103  	indices, err := committeeCache.Committee(slot, seed, committeeIndex)
   104  	if err != nil {
   105  		return nil, errors.Wrap(err, "could not interface with committee cache")
   106  	}
   107  	if indices != nil {
   108  		return indices, nil
   109  	}
   110  
   111  	committeesPerSlot := SlotCommitteeCount(uint64(len(validatorIndices)))
   112  
   113  	epochOffset := uint64(committeeIndex) + uint64(slot.ModSlot(params.BeaconConfig().SlotsPerEpoch).Mul(committeesPerSlot))
   114  	count := committeesPerSlot * uint64(params.BeaconConfig().SlotsPerEpoch)
   115  
   116  	return ComputeCommittee(validatorIndices, seed, epochOffset, count)
   117  }
   118  
   119  // ComputeCommittee returns the requested shuffled committee out of the total committees using
   120  // validator indices and seed.
   121  //
   122  // Spec pseudocode definition:
   123  //  def compute_committee(indices: Sequence[ValidatorIndex],
   124  //                      seed: Bytes32,
   125  //                      index: uint64,
   126  //                      count: uint64) -> Sequence[ValidatorIndex]:
   127  //    """
   128  //    Return the committee corresponding to ``indices``, ``seed``, ``index``, and committee ``count``.
   129  //    """
   130  //    start = (len(indices) * index) // count
   131  //    end = (len(indices) * uint64(index + 1)) // count
   132  //    return [indices[compute_shuffled_index(uint64(i), uint64(len(indices)), seed)] for i in range(start, end)]
   133  func ComputeCommittee(
   134  	indices []types.ValidatorIndex,
   135  	seed [32]byte,
   136  	index, count uint64,
   137  ) ([]types.ValidatorIndex, error) {
   138  	validatorCount := uint64(len(indices))
   139  	start := sliceutil.SplitOffset(validatorCount, count, index)
   140  	end := sliceutil.SplitOffset(validatorCount, count, index+1)
   141  
   142  	if start > validatorCount || end > validatorCount {
   143  		return nil, errors.New("index out of range")
   144  	}
   145  
   146  	// Save the shuffled indices in cache, this is only needed once per epoch or once per new committee index.
   147  	shuffledIndices := make([]types.ValidatorIndex, len(indices))
   148  	copy(shuffledIndices, indices)
   149  	// UnshuffleList is used here as it is an optimized implementation created
   150  	// for fast computation of committees.
   151  	// Reference implementation: https://github.com/protolambda/eth2-shuffle
   152  	shuffledList, err := UnshuffleList(shuffledIndices, seed)
   153  	if err != nil {
   154  		return nil, err
   155  	}
   156  
   157  	return shuffledList[start:end], nil
   158  }
   159  
   160  // CommitteeAssignmentContainer represents a committee, index, and attester slot for a given epoch.
   161  type CommitteeAssignmentContainer struct {
   162  	Committee      []types.ValidatorIndex
   163  	AttesterSlot   types.Slot
   164  	CommitteeIndex types.CommitteeIndex
   165  }
   166  
   167  // CommitteeAssignments is a map of validator indices pointing to the appropriate committee
   168  // assignment for the given epoch.
   169  //
   170  // 1. Determine the proposer validator index for each slot.
   171  // 2. Compute all committees.
   172  // 3. Determine the attesting slot for each committee.
   173  // 4. Construct a map of validator indices pointing to the respective committees.
   174  func CommitteeAssignments(
   175  	state iface.BeaconState,
   176  	epoch types.Epoch,
   177  ) (map[types.ValidatorIndex]*CommitteeAssignmentContainer, map[types.ValidatorIndex][]types.Slot, error) {
   178  	nextEpoch := NextEpoch(state)
   179  	if epoch > nextEpoch {
   180  		return nil, nil, fmt.Errorf(
   181  			"epoch %d can't be greater than next epoch %d",
   182  			epoch,
   183  			nextEpoch,
   184  		)
   185  	}
   186  
   187  	// We determine the slots in which proposers are supposed to act.
   188  	// Some validators may need to propose multiple times per epoch, so
   189  	// we use a map of proposer idx -> []slot to keep track of this possibility.
   190  	startSlot, err := StartSlot(epoch)
   191  	if err != nil {
   192  		return nil, nil, err
   193  	}
   194  	proposerIndexToSlots := make(map[types.ValidatorIndex][]types.Slot, params.BeaconConfig().SlotsPerEpoch)
   195  	// Proposal epochs do not have a look ahead, so we skip them over here.
   196  	validProposalEpoch := epoch < nextEpoch
   197  	for slot := startSlot; slot < startSlot+params.BeaconConfig().SlotsPerEpoch && validProposalEpoch; slot++ {
   198  		// Skip proposer assignment for genesis slot.
   199  		if slot == 0 {
   200  			continue
   201  		}
   202  		if err := state.SetSlot(slot); err != nil {
   203  			return nil, nil, err
   204  		}
   205  		i, err := BeaconProposerIndex(state)
   206  		if err != nil {
   207  			return nil, nil, errors.Wrapf(err, "could not check proposer at slot %d", state.Slot())
   208  		}
   209  		proposerIndexToSlots[i] = append(proposerIndexToSlots[i], slot)
   210  	}
   211  
   212  	activeValidatorIndices, err := ActiveValidatorIndices(state, epoch)
   213  	if err != nil {
   214  		return nil, nil, err
   215  	}
   216  	// Each slot in an epoch has a different set of committees. This value is derived from the
   217  	// active validator set, which does not change.
   218  	numCommitteesPerSlot := SlotCommitteeCount(uint64(len(activeValidatorIndices)))
   219  	validatorIndexToCommittee := make(map[types.ValidatorIndex]*CommitteeAssignmentContainer, params.BeaconConfig().SlotsPerEpoch.Mul(numCommitteesPerSlot))
   220  
   221  	// Compute all committees for all slots.
   222  	for i := types.Slot(0); i < params.BeaconConfig().SlotsPerEpoch; i++ {
   223  		// Compute committees.
   224  		for j := uint64(0); j < numCommitteesPerSlot; j++ {
   225  			slot := startSlot + i
   226  			committee, err := BeaconCommitteeFromState(state, slot, types.CommitteeIndex(j) /*committee index*/)
   227  			if err != nil {
   228  				return nil, nil, err
   229  			}
   230  
   231  			cac := &CommitteeAssignmentContainer{
   232  				Committee:      committee,
   233  				CommitteeIndex: types.CommitteeIndex(j),
   234  				AttesterSlot:   slot,
   235  			}
   236  			for _, vIndex := range committee {
   237  				validatorIndexToCommittee[vIndex] = cac
   238  			}
   239  		}
   240  	}
   241  
   242  	return validatorIndexToCommittee, proposerIndexToSlots, nil
   243  }
   244  
   245  // VerifyBitfieldLength verifies that a bitfield length matches the given committee size.
   246  func VerifyBitfieldLength(bf bitfield.Bitfield, committeeSize uint64) error {
   247  	if bf.Len() != committeeSize {
   248  		return fmt.Errorf(
   249  			"wanted participants bitfield length %d, got: %d",
   250  			committeeSize,
   251  			bf.Len())
   252  	}
   253  	return nil
   254  }
   255  
   256  // VerifyAttestationBitfieldLengths verifies that an attestations aggregation bitfields is
   257  // a valid length matching the size of the committee.
   258  func VerifyAttestationBitfieldLengths(state iface.ReadOnlyBeaconState, att *ethpb.Attestation) error {
   259  	committee, err := BeaconCommitteeFromState(state, att.Data.Slot, att.Data.CommitteeIndex)
   260  	if err != nil {
   261  		return errors.Wrap(err, "could not retrieve beacon committees")
   262  	}
   263  
   264  	if committee == nil {
   265  		return errors.New("no committee exist for this attestation")
   266  	}
   267  
   268  	if err := VerifyBitfieldLength(att.AggregationBits, uint64(len(committee))); err != nil {
   269  		return errors.Wrap(err, "failed to verify aggregation bitfield")
   270  	}
   271  	return nil
   272  }
   273  
   274  // ShuffledIndices uses input beacon state and returns the shuffled indices of the input epoch,
   275  // the shuffled indices then can be used to break up into committees.
   276  func ShuffledIndices(state iface.ReadOnlyBeaconState, epoch types.Epoch) ([]types.ValidatorIndex, error) {
   277  	seed, err := Seed(state, epoch, params.BeaconConfig().DomainBeaconAttester)
   278  	if err != nil {
   279  		return nil, errors.Wrapf(err, "could not get seed for epoch %d", epoch)
   280  	}
   281  
   282  	indices := make([]types.ValidatorIndex, 0, state.NumValidators())
   283  	if err := state.ReadFromEveryValidator(func(idx int, val iface.ReadOnlyValidator) error {
   284  		if IsActiveValidatorUsingTrie(val, epoch) {
   285  			indices = append(indices, types.ValidatorIndex(idx))
   286  		}
   287  		return nil
   288  	}); err != nil {
   289  		return nil, err
   290  	}
   291  
   292  	// UnshuffleList is used as an optimized implementation for raw speed.
   293  	return UnshuffleList(indices, seed)
   294  }
   295  
   296  // UpdateCommitteeCache gets called at the beginning of every epoch to cache the committee shuffled indices
   297  // list with committee index and epoch number. It caches the shuffled indices for current epoch and next epoch.
   298  func UpdateCommitteeCache(state iface.ReadOnlyBeaconState, epoch types.Epoch) error {
   299  	for _, e := range []types.Epoch{epoch, epoch + 1} {
   300  		seed, err := Seed(state, e, params.BeaconConfig().DomainBeaconAttester)
   301  		if err != nil {
   302  			return err
   303  		}
   304  
   305  		if committeeCache.HasEntry(string(seed[:])) {
   306  			return nil
   307  		}
   308  
   309  		shuffledIndices, err := ShuffledIndices(state, e)
   310  		if err != nil {
   311  			return err
   312  		}
   313  
   314  		count := SlotCommitteeCount(uint64(len(shuffledIndices)))
   315  
   316  		// Store the sorted indices as well as shuffled indices. In current spec,
   317  		// sorted indices is required to retrieve proposer index. This is also
   318  		// used for failing verify signature fallback.
   319  		sortedIndices := make([]types.ValidatorIndex, len(shuffledIndices))
   320  		copy(sortedIndices, shuffledIndices)
   321  		sort.Slice(sortedIndices, func(i, j int) bool {
   322  			return sortedIndices[i] < sortedIndices[j]
   323  		})
   324  
   325  		if err := committeeCache.AddCommitteeShuffledList(&cache.Committees{
   326  			ShuffledIndices: shuffledIndices,
   327  			CommitteeCount:  uint64(params.BeaconConfig().SlotsPerEpoch.Mul(count)),
   328  			Seed:            seed,
   329  			SortedIndices:   sortedIndices,
   330  		}); err != nil {
   331  			return err
   332  		}
   333  	}
   334  
   335  	return nil
   336  }
   337  
   338  // UpdateProposerIndicesInCache updates proposer indices entry of the committee cache.
   339  func UpdateProposerIndicesInCache(state iface.ReadOnlyBeaconState) error {
   340  	// The cache uses the state root at the (current epoch - 2)'s slot as key. (e.g. for epoch 2, the key is root at slot 31)
   341  	// Which is the reason why we skip genesis epoch.
   342  	if CurrentEpoch(state) <= params.BeaconConfig().GenesisEpoch+params.BeaconConfig().MinSeedLookahead {
   343  		return nil
   344  	}
   345  
   346  	// Use state root from (current_epoch - 1 - lookahead))
   347  	wantedEpoch := CurrentEpoch(state) - 1
   348  	if wantedEpoch >= params.BeaconConfig().MinSeedLookahead {
   349  		wantedEpoch -= params.BeaconConfig().MinSeedLookahead
   350  	}
   351  	s, err := EndSlot(wantedEpoch)
   352  	if err != nil {
   353  		return err
   354  	}
   355  	r, err := StateRootAtSlot(state, s)
   356  	if err != nil {
   357  		return err
   358  	}
   359  	// Skip cache update if we have an invalid key
   360  	if r == nil || bytes.Equal(r, params.BeaconConfig().ZeroHash[:]) {
   361  		return nil
   362  	}
   363  	// Skip cache update if the key already exists
   364  	exists, err := proposerIndicesCache.HasProposerIndices(bytesutil.ToBytes32(r))
   365  	if err != nil {
   366  		return err
   367  	}
   368  	if exists {
   369  		return nil
   370  	}
   371  
   372  	indices, err := ActiveValidatorIndices(state, CurrentEpoch(state))
   373  	if err != nil {
   374  		return err
   375  	}
   376  	proposerIndices, err := precomputeProposerIndices(state, indices)
   377  	if err != nil {
   378  		return err
   379  	}
   380  	return proposerIndicesCache.AddProposerIndices(&cache.ProposerIndices{
   381  		BlockRoot:       bytesutil.ToBytes32(r),
   382  		ProposerIndices: proposerIndices,
   383  	})
   384  }
   385  
   386  // ClearCache clears the committee cache
   387  func ClearCache() {
   388  	committeeCache = cache.NewCommitteesCache()
   389  	proposerIndicesCache = cache.NewProposerIndicesCache()
   390  }
   391  
   392  // This computes proposer indices of the current epoch and returns a list of proposer indices,
   393  // the index of the list represents the slot number.
   394  func precomputeProposerIndices(state iface.ReadOnlyBeaconState, activeIndices []types.ValidatorIndex) ([]types.ValidatorIndex, error) {
   395  	hashFunc := hashutil.CustomSHA256Hasher()
   396  	proposerIndices := make([]types.ValidatorIndex, params.BeaconConfig().SlotsPerEpoch)
   397  
   398  	e := CurrentEpoch(state)
   399  	seed, err := Seed(state, e, params.BeaconConfig().DomainBeaconProposer)
   400  	if err != nil {
   401  		return nil, errors.Wrap(err, "could not generate seed")
   402  	}
   403  	slot, err := StartSlot(e)
   404  	if err != nil {
   405  		return nil, err
   406  	}
   407  	for i := uint64(0); i < uint64(params.BeaconConfig().SlotsPerEpoch); i++ {
   408  		seedWithSlot := append(seed[:], bytesutil.Bytes8(uint64(slot)+i)...)
   409  		seedWithSlotHash := hashFunc(seedWithSlot)
   410  		index, err := ComputeProposerIndex(state, activeIndices, seedWithSlotHash)
   411  		if err != nil {
   412  			return nil, err
   413  		}
   414  		proposerIndices[i] = index
   415  	}
   416  
   417  	return proposerIndices, nil
   418  }