Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better proposal RPC #11721

Merged
merged 8 commits into from
Dec 8, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions beacon-chain/execution/testing/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ go_library(
"//consensus-types/blocks:go_default_library",
"//consensus-types/interfaces:go_default_library",
"//consensus-types/payload-attribute:go_default_library",
"//consensus-types/primitives:go_default_library",
"//encoding/bytesutil:go_default_library",
"//proto/engine/v1:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
Expand Down
9 changes: 7 additions & 2 deletions beacon-chain/execution/testing/mock_engine_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/prysmaticlabs/prysm/v3/consensus-types/blocks"
"github.com/prysmaticlabs/prysm/v3/consensus-types/interfaces"
payloadattribute "github.com/prysmaticlabs/prysm/v3/consensus-types/payload-attribute"
types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v3/encoding/bytesutil"
pb "github.com/prysmaticlabs/prysm/v3/proto/engine/v1"
)
Expand Down Expand Up @@ -53,8 +54,12 @@ func (e *EngineClient) ForkchoiceUpdated(
}

// GetPayload --
func (e *EngineClient) GetPayload(_ context.Context, _ [8]byte) (*pb.ExecutionPayload, error) {
return e.ExecutionPayload, e.ErrGetPayload
func (e *EngineClient) GetPayload(_ context.Context, _ [8]byte, slot types.Slot) (interfaces.ExecutionData, error) {
p, err := blocks.WrappedExecutionPayload(e.ExecutionPayload)
if err != nil {
return nil, err
}
return p, e.ErrGetPayload
}

func (e *EngineClient) GetPayloadV2(_ context.Context, _ [8]byte) (*pb.ExecutionPayloadCapella, error) {
Expand Down
11 changes: 2 additions & 9 deletions beacon-chain/rpc/eth/validator/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -488,16 +488,9 @@ func (vs *Server) ProduceBlindedBlock(ctx context.Context, req *ethpbv1.ProduceB
if optimistic {
return nil, status.Errorf(codes.Unavailable, "The node is currently optimistic and cannot serve validators")
}
altairBlk, err := vs.V1Alpha1Server.BuildAltairBeaconBlock(ctx, v1alpha1req)
b, err := vs.V1Alpha1Server.GetBeaconBlock(ctx, v1alpha1req)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not prepare beacon block: %v", err)
}
ok, b, err := vs.V1Alpha1Server.GetAndBuildBlindBlock(ctx, altairBlk)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not prepare blind beacon block: %v", err)
}
if !ok {
return nil, status.Error(codes.Unavailable, "Builder is not available due to miss-config or circuit breaker")
return nil, err
}
blk, err := migration.V1Alpha1BeaconBlockBlindedBellatrixToV2Blinded(b.GetBlindedBellatrix())
if err != nil {
Expand Down
1 change: 0 additions & 1 deletion beacon-chain/rpc/prysm/v1alpha1/validator/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ go_library(
"proposer_deposits.go",
"proposer_eth1data.go",
"proposer_execution_payload.go",
"proposer_phase0.go",
"proposer_sync_aggregate.go",
"server.go",
"status.go",
Expand Down
245 changes: 233 additions & 12 deletions beacon-chain/rpc/prysm/v1alpha1/validator/proposer.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,15 @@ import (
emptypb "github.com/golang/protobuf/ptypes/empty"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v3/beacon-chain/builder"
blocks2 "github.com/prysmaticlabs/prysm/v3/beacon-chain/core/blocks"
"github.com/prysmaticlabs/prysm/v3/beacon-chain/core/feed"
blockfeed "github.com/prysmaticlabs/prysm/v3/beacon-chain/core/feed/block"
"github.com/prysmaticlabs/prysm/v3/beacon-chain/core/helpers"
"github.com/prysmaticlabs/prysm/v3/beacon-chain/core/transition"
v "github.com/prysmaticlabs/prysm/v3/beacon-chain/core/validators"
"github.com/prysmaticlabs/prysm/v3/beacon-chain/db/kv"
"github.com/prysmaticlabs/prysm/v3/beacon-chain/state"
fieldparams "github.com/prysmaticlabs/prysm/v3/config/fieldparams"
"github.com/prysmaticlabs/prysm/v3/config/params"
"github.com/prysmaticlabs/prysm/v3/consensus-types/blocks"
"github.com/prysmaticlabs/prysm/v3/consensus-types/interfaces"
Expand All @@ -41,25 +46,241 @@ func (vs *Server) GetBeaconBlock(ctx context.Context, req *ethpb.BlockRequest) (
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)

// A syncing validator should not produce a block.
if vs.SyncChecker.Syncing() {
return nil, status.Error(codes.Unavailable, "Syncing to latest head, not ready to respond")
}

// 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, status.Errorf(codes.Unavailable, "Validator is not ready to propose: %v", err)
}

blk, sBlk, err := emptyBlockToSign(req.Slot)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not prepare block: %v", err)
}

parentRoot, err := vs.HeadFetcher.HeadRoot(ctx)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not get head root: %v", err)
}
head, err := vs.HeadFetcher.HeadState(ctx)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not get head state: %v", err)
}
head, err = transition.ProcessSlotsUsingNextSlotCache(ctx, head, parentRoot, req.Slot)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not process slots up to %d: %v", req.Slot, err)
}

// Set slot, graffiti, randao reveal, and parent root.
blk.SetSlot(req.Slot)
blk.Body().SetGraffiti(req.Graffiti)
blk.Body().SetRandaoReveal(req.RandaoReveal)
blk.SetParentRoot(parentRoot)

// Set eth1 data.
eth1Data, err := vs.eth1DataMajorityVote(ctx, head)
if err != nil {
log.WithError(err).Error("Could not get eth1data")
} else {
blk.Body().SetEth1Data(eth1Data)

// Set deposit and attestation.
deposits, atts, err := vs.packDepositsAndAttestations(ctx, head, eth1Data) // TODO: split attestations and deposits
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not fetch phase0 beacon block: %v", err)
log.WithError(err).Error("Could not pack deposits and attestations")
} else {
blk.Body().SetDeposits(deposits)
blk.Body().SetAttestations(atts)
}
return &ethpb.GenericBeaconBlock{Block: &ethpb.GenericBeaconBlock_Phase0{Phase0: blk}}, nil
} else if slots.ToEpoch(req.Slot) < params.BeaconConfig().BellatrixForkEpoch {
blk, err := vs.getAltairBeaconBlock(ctx, req)
}

// Set proposer index
idx, err := helpers.BeaconProposerIndex(ctx, head)
if err != nil {
return nil, fmt.Errorf("could not calculate proposer index %v", err)
}
blk.SetProposerIndex(idx)

// Set slashings
validProposerSlashings, validAttSlashings := vs.getSlashings(ctx, head)
blk.Body().SetProposerSlashings(validProposerSlashings)
blk.Body().SetAttesterSlashings(validAttSlashings)

// Set exits
blk.Body().SetVoluntaryExits(vs.getExits(head, req))

// Set sync aggregate. New in Altair.
if req.Slot > 0 && slots.ToEpoch(req.Slot) >= params.BeaconConfig().AltairForkEpoch {
syncAggregate, err := vs.getSyncAggregate(ctx, req.Slot-1, bytesutil.ToBytes32(parentRoot))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we do a custom testnet where we start from altair or greater, there is nothing preventing req.Slot from being 0. Would be best to play defensive to not run into crazy issues in e2e later or in devnets

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice catch!

if err != nil {
return nil, status.Errorf(codes.Internal, "Could not fetch Altair beacon block: %v", err)
log.WithError(err).Error("Could not get sync aggregate")
} else {
if err := blk.Body().SetSyncAggregate(syncAggregate); err != nil {
log.WithError(err).Error("Could not set sync aggregate")
if err := blk.Body().SetSyncAggregate(&ethpb.SyncAggregate{
SyncCommitteeBits: make([]byte, params.BeaconConfig().SyncCommitteeSize),
SyncCommitteeSignature: make([]byte, fieldparams.BLSSignatureLength),
}); err != nil {
return nil, status.Errorf(codes.Internal, "Could not set sync aggregate: %v", err)
terencechain marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
return &ethpb.GenericBeaconBlock{Block: &ethpb.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
// Set execution data. New in Bellatrix
if slots.ToEpoch(req.Slot) >= params.BeaconConfig().BellatrixForkEpoch {
fallBackToLocal := true
canUseBuilder, err := vs.canUseBuilder(ctx, req.Slot, idx)
if err != nil {
log.WithError(err).Warn("Proposer: failed to check if builder can be used")
} else if canUseBuilder {
h, err := vs.getPayloadHeaderFromBuilder(ctx, req.Slot, idx)
if err != nil {
log.WithError(err).Warn("Proposer: failed to get payload header from builder")
} else {
blk.SetBlinded(true)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we set the block to blinded? Don't we always propose full blocks?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetBlock is first part of the proposal. We are giving an unsigned block to the validator to sign and that can be full blocks or blind blocks (using mev-boost)

if err := blk.Body().SetExecution(h); err != nil {
log.WithError(err).Warn("Proposer: failed to set execution payload")
} else {
fallBackToLocal = false
}
}
}
if fallBackToLocal {
executionData, err := vs.getExecutionPayload(ctx, req.Slot, idx, bytesutil.ToBytes32(parentRoot), head)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not get execution payload: %v", err)
}
if err := blk.Body().SetExecution(executionData); err != nil {
return nil, status.Errorf(codes.Internal, "Could not set execution payload: %v", err)
}
}
}

// Set bls to execution change. New in Capella
if slots.ToEpoch(req.Slot) >= params.BeaconConfig().CapellaForkEpoch {
changes, err := vs.BLSChangesPool.BLSToExecChangesForInclusion(head)
if err != nil {
log.WithError(err).Error("Could not get bls to execution changes")
} else {
if err := blk.Body().SetBLSToExecutionChanges(changes); err != nil {
log.WithError(err).Error("Could not set bls to execution changes")
}
}
}

if err := sBlk.SetBlock(blk); err != nil {
return nil, status.Errorf(codes.Internal, "Could not set block: %v", err)
}
sr, err := vs.computeStateRoot(ctx, sBlk)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not compute state root: %v", err)
}
blk.SetStateRoot(sr)

pb, err := blk.Proto()
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not convert block to proto: %v", err)
}
if slots.ToEpoch(req.Slot) >= params.BeaconConfig().CapellaForkEpoch {
return &ethpb.GenericBeaconBlock{Block: &ethpb.GenericBeaconBlock_Capella{Capella: pb.(*ethpb.BeaconBlockCapella)}}, nil
} else if slots.ToEpoch(req.Slot) >= params.BeaconConfig().BellatrixForkEpoch {
return &ethpb.GenericBeaconBlock{Block: &ethpb.GenericBeaconBlock_Bellatrix{Bellatrix: pb.(*ethpb.BeaconBlockBellatrix)}}, nil
} else if slots.ToEpoch(req.Slot) >= params.BeaconConfig().AltairForkEpoch {
return &ethpb.GenericBeaconBlock{Block: &ethpb.GenericBeaconBlock_Altair{Altair: pb.(*ethpb.BeaconBlockAltair)}}, nil
}
return &ethpb.GenericBeaconBlock{Block: &ethpb.GenericBeaconBlock_Phase0{Phase0: pb.(*ethpb.BeaconBlock)}}, nil
}

func (vs *Server) getExits(head state.BeaconState, req *ethpb.BlockRequest) []*ethpb.SignedVoluntaryExit {
exits := vs.ExitPool.PendingExits(head, req.Slot, false /*noLimit*/)
validExits := make([]*ethpb.SignedVoluntaryExit, 0, len(exits))
for _, exit := range exits {
val, err := head.ValidatorAtIndexReadOnly(exit.Exit.ValidatorIndex)
if err != nil {
log.WithError(err).Warn("Proposer: invalid exit")
continue
}
if err := blocks2.VerifyExitAndSignature(val, head.Slot(), head.Fork(), exit, head.GenesisValidatorsRoot()); err != nil {
log.WithError(err).Warn("Proposer: invalid exit")
continue
}
validExits = append(validExits, exit)
}
return validExits
}

func (vs *Server) getSlashings(ctx context.Context, head state.BeaconState) ([]*ethpb.ProposerSlashing, []*ethpb.AttesterSlashing) {
proposerSlashings := vs.SlashingsPool.PendingProposerSlashings(ctx, head, false /*noLimit*/)
validProposerSlashings := make([]*ethpb.ProposerSlashing, 0, len(proposerSlashings))
for _, slashing := range proposerSlashings {
_, err := blocks2.ProcessProposerSlashing(ctx, head, slashing, v.SlashValidator)
if err != nil {
log.WithError(err).Warn("Proposer: invalid proposer slashing")
continue
}
validProposerSlashings = append(validProposerSlashings, slashing)
}
attSlashings := vs.SlashingsPool.PendingAttesterSlashings(ctx, head, false /*noLimit*/)
validAttSlashings := make([]*ethpb.AttesterSlashing, 0, len(attSlashings))
for _, slashing := range attSlashings {
_, err := blocks2.ProcessAttesterSlashing(ctx, head, slashing, v.SlashValidator)
if err != nil {
log.WithError(err).Warn("Proposer: invalid attester slashing")
continue
}
validAttSlashings = append(validAttSlashings, slashing)
}
return validProposerSlashings, validAttSlashings
}

func emptyBlockToSign(slot types.Slot) (interfaces.BeaconBlock, interfaces.SignedBeaconBlock, error) {
var blk interfaces.BeaconBlock
var sBlk interfaces.SignedBeaconBlock
var err error
switch {
case slots.ToEpoch(slot) < params.BeaconConfig().AltairForkEpoch:
blk, err = blocks.NewBeaconBlock(&ethpb.BeaconBlock{Body: &ethpb.BeaconBlockBody{}})
if err != nil {
return nil, nil, status.Errorf(codes.Internal, "Could not initialize block for proposal: %v", err)
}
sBlk, err = blocks.NewSignedBeaconBlock(&ethpb.SignedBeaconBlock{Block: &ethpb.BeaconBlock{Body: &ethpb.BeaconBlockBody{}}})
if err != nil {
return nil, nil, status.Errorf(codes.Internal, "Could not initialize block for proposal: %v", err)
}
case slots.ToEpoch(slot) < params.BeaconConfig().BellatrixForkEpoch:
blk, err = blocks.NewBeaconBlock(&ethpb.BeaconBlockAltair{Body: &ethpb.BeaconBlockBodyAltair{}})
if err != nil {
return nil, nil, status.Errorf(codes.Internal, "Could not initialize block for proposal: %v", err)
}
sBlk, err = blocks.NewSignedBeaconBlock(&ethpb.SignedBeaconBlockAltair{Block: &ethpb.BeaconBlockAltair{Body: &ethpb.BeaconBlockBodyAltair{}}})
if err != nil {
return nil, nil, status.Errorf(codes.Internal, "Could not initialize block for proposal: %v", err)
}
case slots.ToEpoch(slot) < params.BeaconConfig().CapellaForkEpoch:
blk, err = blocks.NewBeaconBlock(&ethpb.BeaconBlockBellatrix{Body: &ethpb.BeaconBlockBodyBellatrix{}})
if err != nil {
return nil, nil, status.Errorf(codes.Internal, "Could not initialize block for proposal: %v", err)
}
sBlk, err = blocks.NewSignedBeaconBlock(&ethpb.SignedBeaconBlockBellatrix{Block: &ethpb.BeaconBlockBellatrix{Body: &ethpb.BeaconBlockBodyBellatrix{}}})
if err != nil {
return nil, nil, status.Errorf(codes.Internal, "Could not initialize block for proposal: %v", err)
}
default:
blk, err = blocks.NewBeaconBlock(&ethpb.BeaconBlockCapella{Body: &ethpb.BeaconBlockBodyCapella{}})
if err != nil {
return nil, nil, status.Errorf(codes.Internal, "Could not initialize block for proposal: %v", err)
}
sBlk, err = blocks.NewSignedBeaconBlock(&ethpb.SignedBeaconBlockCapella{Block: &ethpb.BeaconBlockCapella{Body: &ethpb.BeaconBlockBodyCapella{}}})
if err != nil {
return nil, nil, status.Errorf(codes.Internal, "Could not initialize block for proposal: %v", err)
}
}
return vs.getBellatrixBeaconBlock(ctx, req)
return blk, sBlk, err
}

// ProposeBeaconBlock is called by a proposer during its assigned slot to create a block in an attempt
Expand Down
Loading