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 }