package validator import ( "context" "encoding/hex" "fmt" "time" emptypb "github.com/golang/protobuf/ptypes/empty" "github.com/pkg/errors" "github.com/prysmaticlabs/prysm/beacon-chain/core/feed" blockfeed "github.com/prysmaticlabs/prysm/beacon-chain/core/feed/block" "github.com/prysmaticlabs/prysm/beacon-chain/core/transition" "github.com/prysmaticlabs/prysm/config/params" "github.com/prysmaticlabs/prysm/encoding/bytesutil" ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/block" "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/wrapper" "github.com/prysmaticlabs/prysm/time/slots" "github.com/sirupsen/logrus" "go.opencensus.io/trace" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) // eth1DataNotification is a latch to stop flooding logs with the same warning. var eth1DataNotification bool const eth1dataTimeout = 2 * time.Second // GetBeaconBlock is called by a proposer during its assigned slot to request a block to sign // by passing in the slot and the signed randao reveal of the slot. Returns phase0 beacon blocks // before the Altair fork epoch and Altair blocks post-fork epoch. func (vs *Server) GetBeaconBlock(ctx context.Context, req *ethpb.BlockRequest) (*ethpb.GenericBeaconBlock, error) { ctx, span := trace.StartSpan(ctx, "ProposerServer.GetBeaconBlock") defer span.End() span.AddAttributes(trace.Int64Attribute("slot", int64(req.Slot))) if slots.ToEpoch(req.Slot) < params.BeaconConfig().AltairForkEpoch { blk, err := vs.getPhase0BeaconBlock(ctx, req) if err != nil { return nil, status.Errorf(codes.Internal, "Could not fetch phase0 beacon block: %v", err) } return ðpb.GenericBeaconBlock{Block: ðpb.GenericBeaconBlock_Phase0{Phase0: blk}}, nil } else if slots.ToEpoch(req.Slot) < params.BeaconConfig().BellatrixForkEpoch { blk, err := vs.getAltairBeaconBlock(ctx, req) if err != nil { return nil, status.Errorf(codes.Internal, "Could not fetch Altair beacon block: %v", err) } return ðpb.GenericBeaconBlock{Block: ðpb.GenericBeaconBlock_Altair{Altair: blk}}, nil } // An optimistic validator MUST NOT produce a block (i.e., sign across the DOMAIN_BEACON_PROPOSER domain). if err := vs.optimisticStatus(ctx); err != nil { return nil, err } blk, err := vs.getBellatrixBeaconBlock(ctx, req) if err != nil { return nil, status.Errorf(codes.Internal, "Could not fetch Bellatrix beacon block: %v", err) } return ðpb.GenericBeaconBlock{Block: ðpb.GenericBeaconBlock_Bellatrix{Bellatrix: blk}}, nil } // GetBlock is called by a proposer during its assigned slot to request a block to sign // by passing in the slot and the signed randao reveal of the slot. // // DEPRECATED: Use GetBeaconBlock instead to handle blocks pre and post-Altair hard fork. This endpoint // cannot handle blocks after the Altair fork epoch. If requesting a block after Altair, nothing will // be returned. func (vs *Server) GetBlock(ctx context.Context, req *ethpb.BlockRequest) (*ethpb.BeaconBlock, error) { ctx, span := trace.StartSpan(ctx, "ProposerServer.GetBlock") defer span.End() span.AddAttributes(trace.Int64Attribute("slot", int64(req.Slot))) blk, err := vs.GetBeaconBlock(ctx, req) if err != nil { return nil, err } return blk.GetPhase0(), nil } // ProposeBeaconBlock is called by a proposer during its assigned slot to create a block in an attempt // to get it processed by the beacon node as the canonical head. func (vs *Server) ProposeBeaconBlock(ctx context.Context, req *ethpb.GenericSignedBeaconBlock) (*ethpb.ProposeResponse, error) { ctx, span := trace.StartSpan(ctx, "ProposerServer.ProposeBeaconBlock") defer span.End() var blk block.SignedBeaconBlock var err error switch b := req.Block.(type) { case *ethpb.GenericSignedBeaconBlock_Phase0: blk = wrapper.WrappedPhase0SignedBeaconBlock(b.Phase0) case *ethpb.GenericSignedBeaconBlock_Altair: blk, err = wrapper.WrappedAltairSignedBeaconBlock(b.Altair) if err != nil { return nil, status.Error(codes.Internal, "could not wrap altair beacon block") } case *ethpb.GenericSignedBeaconBlock_Bellatrix: blk, err = wrapper.WrappedBellatrixSignedBeaconBlock(b.Bellatrix) if err != nil { return nil, status.Error(codes.Internal, "could not wrap Bellatrix beacon block") } default: return nil, status.Error(codes.Internal, "block version not supported") } return vs.proposeGenericBeaconBlock(ctx, blk) } // ProposeBlock is called by a proposer during its assigned slot to create a block in an attempt // to get it processed by the beacon node as the canonical head. // // DEPRECATED: Use ProposeBeaconBlock instead. func (vs *Server) ProposeBlock(ctx context.Context, rBlk *ethpb.SignedBeaconBlock) (*ethpb.ProposeResponse, error) { ctx, span := trace.StartSpan(ctx, "ProposerServer.ProposeBlock") defer span.End() blk := wrapper.WrappedPhase0SignedBeaconBlock(rBlk) return vs.proposeGenericBeaconBlock(ctx, blk) } // PrepareBeaconProposer -- func (vs *Server) PrepareBeaconProposer( _ context.Context, _ *ethpb.PrepareBeaconProposerRequest, ) (*emptypb.Empty, error) { return &emptypb.Empty{}, status.Error(codes.Unimplemented, "Unimplemented") } func (vs *Server) proposeGenericBeaconBlock(ctx context.Context, blk block.SignedBeaconBlock) (*ethpb.ProposeResponse, error) { ctx, span := trace.StartSpan(ctx, "ProposerServer.proposeGenericBeaconBlock") defer span.End() root, err := blk.Block().HashTreeRoot() if err != nil { return nil, fmt.Errorf("could not tree hash block: %v", err) } // Do not block proposal critical path with debug logging or block feed updates. defer func() { log.WithField("blockRoot", fmt.Sprintf("%#x", bytesutil.Trunc(root[:]))).Debugf( "Block proposal received via RPC") vs.BlockNotifier.BlockFeed().Send(&feed.Event{ Type: blockfeed.ReceivedBlock, Data: &blockfeed.ReceivedBlockData{SignedBlock: blk}, }) }() // Broadcast the new block to the network. if err := vs.P2P.Broadcast(ctx, blk.Proto()); err != nil { return nil, fmt.Errorf("could not broadcast block: %v", err) } log.WithFields(logrus.Fields{ "blockRoot": hex.EncodeToString(root[:]), }).Debug("Broadcasting block") if err := vs.BlockReceiver.ReceiveBlock(ctx, blk, root); err != nil { return nil, fmt.Errorf("could not process beacon block: %v", err) } return ðpb.ProposeResponse{ BlockRoot: root[:], }, nil } // computeStateRoot computes the state root after a block has been processed through a state transition and // returns it to the validator client. func (vs *Server) computeStateRoot(ctx context.Context, block block.SignedBeaconBlock) ([]byte, error) { beaconState, err := vs.StateGen.StateByRoot(ctx, bytesutil.ToBytes32(block.Block().ParentRoot())) if err != nil { return nil, errors.Wrap(err, "could not retrieve beacon state") } root, err := transition.CalculateStateRoot( ctx, beaconState, block, ) if err != nil { return nil, errors.Wrapf(err, "could not calculate state root at slot %d", beaconState.Slot()) } log.WithField("beaconStateRoot", fmt.Sprintf("%#x", root)).Debugf("Computed state root") return root[:], nil }