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 &ethpb.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 &ethpb.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 &ethpb.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 &ethpb.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 &ethpb.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 &ethpb.AttestationPoolResponse{
   414  		Attestations:  atts[start:end],
   415  		TotalSize:     int32(numAtts),
   416  		NextPageToken: nextPageToken,
   417  	}, nil
   418  }