github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/rpc/prysm/v1alpha1/beacon/attestations.go (about) 1 package beacon 2 3 import ( 4 "context" 5 "sort" 6 "strconv" 7 "strings" 8 9 "github.com/prysmaticlabs/prysm/beacon-chain/core/feed" 10 "github.com/prysmaticlabs/prysm/beacon-chain/core/feed/operation" 11 "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" 12 "github.com/prysmaticlabs/prysm/beacon-chain/db/filters" 13 ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1" 14 "github.com/prysmaticlabs/prysm/proto/interfaces" 15 attaggregation "github.com/prysmaticlabs/prysm/shared/aggregation/attestations" 16 "github.com/prysmaticlabs/prysm/shared/attestationutil" 17 "github.com/prysmaticlabs/prysm/shared/bytesutil" 18 "github.com/prysmaticlabs/prysm/shared/cmd" 19 "github.com/prysmaticlabs/prysm/shared/pagination" 20 "github.com/prysmaticlabs/prysm/shared/params" 21 "github.com/prysmaticlabs/prysm/shared/slotutil" 22 "google.golang.org/grpc/codes" 23 "google.golang.org/grpc/status" 24 "google.golang.org/protobuf/types/known/emptypb" 25 ) 26 27 // sortableAttestations implements the Sort interface to sort attestations 28 // by slot as the canonical sorting attribute. 29 type sortableAttestations []*ethpb.Attestation 30 31 // Len is the number of elements in the collection. 32 func (s sortableAttestations) Len() int { return len(s) } 33 34 // Swap swaps the elements with indexes i and j. 35 func (s sortableAttestations) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 36 37 // Less reports whether the element with index i must sort before the element with index j. 38 func (s sortableAttestations) Less(i, j int) bool { 39 return s[i].Data.Slot < s[j].Data.Slot 40 } 41 42 func mapAttestationsByTargetRoot(atts []*ethpb.Attestation) map[[32]byte][]*ethpb.Attestation { 43 attsMap := make(map[[32]byte][]*ethpb.Attestation, len(atts)) 44 if len(atts) == 0 { 45 return attsMap 46 } 47 for _, att := range atts { 48 attsMap[bytesutil.ToBytes32(att.Data.Target.Root)] = append(attsMap[bytesutil.ToBytes32(att.Data.Target.Root)], att) 49 } 50 return attsMap 51 } 52 53 // ListAttestations retrieves attestations by block root, slot, or epoch. 54 // Attestations are sorted by data slot by default. 55 // 56 // The server may return an empty list when no attestations match the given 57 // filter criteria. This RPC should not return NOT_FOUND. Only one filter 58 // criteria should be used. 59 func (bs *Server) ListAttestations( 60 ctx context.Context, req *ethpb.ListAttestationsRequest, 61 ) (*ethpb.ListAttestationsResponse, error) { 62 if int(req.PageSize) > cmd.Get().MaxRPCPageSize { 63 return nil, status.Errorf(codes.InvalidArgument, "Requested page size %d can not be greater than max size %d", 64 req.PageSize, cmd.Get().MaxRPCPageSize) 65 } 66 var blocks []interfaces.SignedBeaconBlock 67 var err error 68 switch q := req.QueryFilter.(type) { 69 case *ethpb.ListAttestationsRequest_GenesisEpoch: 70 blocks, _, err = bs.BeaconDB.Blocks(ctx, filters.NewFilter().SetStartEpoch(0).SetEndEpoch(0)) 71 if err != nil { 72 return nil, status.Errorf(codes.Internal, "Could not fetch attestations: %v", err) 73 } 74 case *ethpb.ListAttestationsRequest_Epoch: 75 blocks, _, err = bs.BeaconDB.Blocks(ctx, filters.NewFilter().SetStartEpoch(q.Epoch).SetEndEpoch(q.Epoch)) 76 if err != nil { 77 return nil, status.Errorf(codes.Internal, "Could not fetch attestations: %v", err) 78 } 79 default: 80 return nil, status.Error(codes.InvalidArgument, "Must specify a filter criteria for fetching attestations") 81 } 82 atts := make([]*ethpb.Attestation, 0, params.BeaconConfig().MaxAttestations*uint64(len(blocks))) 83 for _, block := range blocks { 84 atts = append(atts, block.Block().Body().Attestations()...) 85 } 86 // We sort attestations according to the Sortable interface. 87 sort.Sort(sortableAttestations(atts)) 88 numAttestations := len(atts) 89 90 // If there are no attestations, we simply return a response specifying this. 91 // Otherwise, attempting to paginate 0 attestations below would result in an error. 92 if numAttestations == 0 { 93 return ðpb.ListAttestationsResponse{ 94 Attestations: make([]*ethpb.Attestation, 0), 95 TotalSize: int32(0), 96 NextPageToken: strconv.Itoa(0), 97 }, nil 98 } 99 100 start, end, nextPageToken, err := pagination.StartAndEndPage(req.PageToken, int(req.PageSize), numAttestations) 101 if err != nil { 102 return nil, status.Errorf(codes.Internal, "Could not paginate attestations: %v", err) 103 } 104 return ðpb.ListAttestationsResponse{ 105 Attestations: atts[start:end], 106 TotalSize: int32(numAttestations), 107 NextPageToken: nextPageToken, 108 }, nil 109 } 110 111 // ListIndexedAttestations retrieves indexed attestations by block root. 112 // IndexedAttestationsForEpoch are sorted by data slot by default. Start-end epoch 113 // filter is used to retrieve blocks with. 114 // 115 // The server may return an empty list when no attestations match the given 116 // filter criteria. This RPC should not return NOT_FOUND. 117 func (bs *Server) ListIndexedAttestations( 118 ctx context.Context, req *ethpb.ListIndexedAttestationsRequest, 119 ) (*ethpb.ListIndexedAttestationsResponse, error) { 120 var blocks []interfaces.SignedBeaconBlock 121 var err error 122 switch q := req.QueryFilter.(type) { 123 case *ethpb.ListIndexedAttestationsRequest_GenesisEpoch: 124 blocks, _, err = bs.BeaconDB.Blocks(ctx, filters.NewFilter().SetStartEpoch(0).SetEndEpoch(0)) 125 if err != nil { 126 return nil, status.Errorf(codes.Internal, "Could not fetch attestations: %v", err) 127 } 128 case *ethpb.ListIndexedAttestationsRequest_Epoch: 129 blocks, _, err = bs.BeaconDB.Blocks(ctx, filters.NewFilter().SetStartEpoch(q.Epoch).SetEndEpoch(q.Epoch)) 130 if err != nil { 131 return nil, status.Errorf(codes.Internal, "Could not fetch attestations: %v", err) 132 } 133 default: 134 return nil, status.Error(codes.InvalidArgument, "Must specify a filter criteria for fetching attestations") 135 } 136 137 attsArray := make([]*ethpb.Attestation, 0, params.BeaconConfig().MaxAttestations*uint64(len(blocks))) 138 for _, block := range blocks { 139 attsArray = append(attsArray, block.Block().Body().Attestations()...) 140 } 141 // We sort attestations according to the Sortable interface. 142 sort.Sort(sortableAttestations(attsArray)) 143 numAttestations := len(attsArray) 144 145 // If there are no attestations, we simply return a response specifying this. 146 // Otherwise, attempting to paginate 0 attestations below would result in an error. 147 if numAttestations == 0 { 148 return ðpb.ListIndexedAttestationsResponse{ 149 IndexedAttestations: make([]*ethpb.IndexedAttestation, 0), 150 TotalSize: int32(0), 151 NextPageToken: strconv.Itoa(0), 152 }, nil 153 } 154 // We use the retrieved committees for the block root to convert all attestations 155 // into indexed form effectively. 156 mappedAttestations := mapAttestationsByTargetRoot(attsArray) 157 indexedAtts := make([]*ethpb.IndexedAttestation, 0, numAttestations) 158 for targetRoot, atts := range mappedAttestations { 159 attState, err := bs.StateGen.StateByRoot(ctx, targetRoot) 160 if err != nil && strings.Contains(err.Error(), "unknown state summary") { 161 // We shouldn't stop the request if we encounter an attestation we don't have the state for. 162 log.Debugf("Could not get state for attestation target root %#x", targetRoot) 163 continue 164 } else if err != nil { 165 return nil, status.Errorf( 166 codes.Internal, 167 "Could not retrieve state for attestation target root %#x: %v", 168 targetRoot, 169 err, 170 ) 171 } 172 for i := 0; i < len(atts); i++ { 173 att := atts[i] 174 committee, err := helpers.BeaconCommitteeFromState(attState, att.Data.Slot, att.Data.CommitteeIndex) 175 if err != nil { 176 return nil, status.Errorf( 177 codes.Internal, 178 "Could not retrieve committee from state %v", 179 err, 180 ) 181 } 182 idxAtt, err := attestationutil.ConvertToIndexed(ctx, att, committee) 183 if err != nil { 184 return nil, err 185 } 186 indexedAtts = append(indexedAtts, idxAtt) 187 } 188 } 189 190 start, end, nextPageToken, err := pagination.StartAndEndPage(req.PageToken, int(req.PageSize), len(indexedAtts)) 191 if err != nil { 192 return nil, status.Errorf(codes.Internal, "Could not paginate attestations: %v", err) 193 } 194 return ðpb.ListIndexedAttestationsResponse{ 195 IndexedAttestations: indexedAtts[start:end], 196 TotalSize: int32(len(indexedAtts)), 197 NextPageToken: nextPageToken, 198 }, nil 199 } 200 201 // StreamAttestations to clients at the end of every slot. This method retrieves the 202 // aggregated attestations currently in the pool at the start of a slot and sends 203 // them over a gRPC stream. 204 func (bs *Server) StreamAttestations( 205 _ *emptypb.Empty, stream ethpb.BeaconChain_StreamAttestationsServer, 206 ) error { 207 attestationsChannel := make(chan *feed.Event, 1) 208 attSub := bs.AttestationNotifier.OperationFeed().Subscribe(attestationsChannel) 209 defer attSub.Unsubscribe() 210 for { 211 select { 212 case event := <-attestationsChannel: 213 if event.Type == operation.UnaggregatedAttReceived { 214 data, ok := event.Data.(*operation.UnAggregatedAttReceivedData) 215 if !ok { 216 // Got bad data over the stream. 217 continue 218 } 219 if data.Attestation == nil { 220 // One nil attestation shouldn't stop the stream. 221 continue 222 } 223 if err := stream.Send(data.Attestation); err != nil { 224 return status.Errorf(codes.Unavailable, "Could not send over stream: %v", err) 225 } 226 } 227 case <-bs.Ctx.Done(): 228 return status.Error(codes.Canceled, "Context canceled") 229 case <-stream.Context().Done(): 230 return status.Error(codes.Canceled, "Context canceled") 231 } 232 } 233 } 234 235 // StreamIndexedAttestations to clients at the end of every slot. This method retrieves the 236 // aggregated attestations currently in the pool, converts them into indexed form, and 237 // sends them over a gRPC stream. 238 func (bs *Server) StreamIndexedAttestations( 239 _ *emptypb.Empty, stream ethpb.BeaconChain_StreamIndexedAttestationsServer, 240 ) error { 241 attestationsChannel := make(chan *feed.Event, 1) 242 attSub := bs.AttestationNotifier.OperationFeed().Subscribe(attestationsChannel) 243 defer attSub.Unsubscribe() 244 go bs.collectReceivedAttestations(stream.Context()) 245 for { 246 select { 247 case event, ok := <-attestationsChannel: 248 if !ok { 249 log.Error("Indexed attestations stream channel closed") 250 continue 251 } 252 if event.Type == operation.UnaggregatedAttReceived { 253 data, ok := event.Data.(*operation.UnAggregatedAttReceivedData) 254 if !ok { 255 // Got bad data over the stream. 256 log.Warningf("Indexed attestations stream got data of wrong type on stream expected *UnAggregatedAttReceivedData, received %T", event.Data) 257 continue 258 } 259 if data.Attestation == nil { 260 // One nil attestation shouldn't stop the stream. 261 log.Debug("Indexed attestations stream got a nil attestation") 262 continue 263 } 264 bs.ReceivedAttestationsBuffer <- data.Attestation 265 } else if event.Type == operation.AggregatedAttReceived { 266 data, ok := event.Data.(*operation.AggregatedAttReceivedData) 267 if !ok { 268 // Got bad data over the stream. 269 log.Warningf("Indexed attestations stream got data of wrong type on stream expected *AggregatedAttReceivedData, received %T", event.Data) 270 continue 271 } 272 if data.Attestation == nil || data.Attestation.Aggregate == nil { 273 // One nil attestation shouldn't stop the stream. 274 log.Debug("Indexed attestations stream got nil attestation or nil attestation aggregate") 275 continue 276 } 277 bs.ReceivedAttestationsBuffer <- data.Attestation.Aggregate 278 } 279 case aggAtts, ok := <-bs.CollectedAttestationsBuffer: 280 if !ok { 281 log.Error("Indexed attestations stream collected attestations channel closed") 282 continue 283 } 284 if len(aggAtts) == 0 { 285 continue 286 } 287 // All attestations we receive have the same target epoch given they 288 // have the same data root, so we just use the target epoch from 289 // the first one to determine committees for converting into indexed 290 // form. 291 targetRoot := aggAtts[0].Data.Target.Root 292 targetEpoch := aggAtts[0].Data.Target.Epoch 293 committeesBySlot, _, err := bs.retrieveCommitteesForRoot(stream.Context(), targetRoot) 294 if err != nil { 295 return status.Errorf( 296 codes.Internal, 297 "Could not retrieve committees for target root %#x: %v", 298 targetRoot, 299 err, 300 ) 301 } 302 // We use the retrieved committees for the epoch to convert all attestations 303 // into indexed form effectively. 304 startSlot, err := helpers.StartSlot(targetEpoch) 305 if err != nil { 306 log.Error(err) 307 continue 308 } 309 endSlot := startSlot + params.BeaconConfig().SlotsPerEpoch 310 for _, att := range aggAtts { 311 // Out of range check, the attestation slot cannot be greater 312 // the last slot of the requested epoch or smaller than its start slot 313 // given committees are accessed as a map of slot -> commitees list, where there are 314 // SLOTS_PER_EPOCH keys in the map. 315 if att.Data.Slot < startSlot || att.Data.Slot > endSlot { 316 continue 317 } 318 committeesForSlot, ok := committeesBySlot[att.Data.Slot] 319 if !ok || committeesForSlot.Committees == nil { 320 continue 321 } 322 committee := committeesForSlot.Committees[att.Data.CommitteeIndex] 323 idxAtt, err := attestationutil.ConvertToIndexed(stream.Context(), att, committee.ValidatorIndices) 324 if err != nil { 325 continue 326 } 327 if err := stream.Send(idxAtt); err != nil { 328 return status.Errorf(codes.Unavailable, "Could not send over stream: %v", err) 329 } 330 } 331 case <-bs.Ctx.Done(): 332 return status.Error(codes.Canceled, "Context canceled") 333 case <-stream.Context().Done(): 334 return status.Error(codes.Canceled, "Context canceled") 335 } 336 } 337 } 338 339 // already being done by the attestation pool in the operations service. 340 func (bs *Server) collectReceivedAttestations(ctx context.Context) { 341 attsByRoot := make(map[[32]byte][]*ethpb.Attestation) 342 twoThirdsASlot := 2 * slotutil.DivideSlotBy(3) /* 2/3 slot duration */ 343 ticker := slotutil.NewSlotTickerWithOffset(bs.GenesisTimeFetcher.GenesisTime(), twoThirdsASlot, params.BeaconConfig().SecondsPerSlot) 344 for { 345 select { 346 case <-ticker.C(): 347 aggregatedAttsByTarget := make(map[[32]byte][]*ethpb.Attestation) 348 for root, atts := range attsByRoot { 349 // We aggregate the received attestations, we know they all have the same data root. 350 aggAtts, err := attaggregation.Aggregate(atts) 351 if err != nil { 352 log.WithError(err).Error("Could not aggregate attestations") 353 continue 354 } 355 if len(aggAtts) == 0 { 356 continue 357 } 358 targetRoot := bytesutil.ToBytes32(atts[0].Data.Target.Root) 359 aggregatedAttsByTarget[targetRoot] = append(aggregatedAttsByTarget[targetRoot], aggAtts...) 360 attsByRoot[root] = make([]*ethpb.Attestation, 0) 361 } 362 for _, atts := range aggregatedAttsByTarget { 363 bs.CollectedAttestationsBuffer <- atts 364 } 365 case att := <-bs.ReceivedAttestationsBuffer: 366 attDataRoot, err := att.Data.HashTreeRoot() 367 if err != nil { 368 log.Errorf("Could not hash tree root attestation data: %v", err) 369 continue 370 } 371 attsByRoot[attDataRoot] = append(attsByRoot[attDataRoot], att) 372 case <-ctx.Done(): 373 return 374 case <-bs.Ctx.Done(): 375 return 376 } 377 } 378 } 379 380 // AttestationPool retrieves pending attestations. 381 // 382 // The server returns a list of attestations that have been seen but not 383 // yet processed. Pool attestations eventually expire as the slot 384 // advances, so an attestation missing from this request does not imply 385 // that it was included in a block. The attestation may have expired. 386 // Refer to the ethereum consensus specification for more details on how 387 // attestations are processed and when they are no longer valid. 388 // https://github.com/ethereum/eth2.0-specs/blob/dev/specs/core/0_beacon-chain.md#attestations 389 func (bs *Server) AttestationPool( 390 _ context.Context, req *ethpb.AttestationPoolRequest, 391 ) (*ethpb.AttestationPoolResponse, error) { 392 if int(req.PageSize) > cmd.Get().MaxRPCPageSize { 393 return nil, status.Errorf( 394 codes.InvalidArgument, 395 "Requested page size %d can not be greater than max size %d", 396 req.PageSize, 397 cmd.Get().MaxRPCPageSize, 398 ) 399 } 400 atts := bs.AttestationsPool.AggregatedAttestations() 401 numAtts := len(atts) 402 if numAtts == 0 { 403 return ðpb.AttestationPoolResponse{ 404 Attestations: make([]*ethpb.Attestation, 0), 405 TotalSize: int32(0), 406 NextPageToken: strconv.Itoa(0), 407 }, nil 408 } 409 start, end, nextPageToken, err := pagination.StartAndEndPage(req.PageToken, int(req.PageSize), numAtts) 410 if err != nil { 411 return nil, status.Errorf(codes.Internal, "Could not paginate attestations: %v", err) 412 } 413 return ðpb.AttestationPoolResponse{ 414 Attestations: atts[start:end], 415 TotalSize: int32(numAtts), 416 NextPageToken: nextPageToken, 417 }, nil 418 }