-
Notifications
You must be signed in to change notification settings - Fork 1k
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
Better proposal RPC #11721
Changes from 5 commits
279cee4
d9646a9
468cc23
3f068c4
a0c9d4b
321014b
4514c3a
99abb10
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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" | ||
|
@@ -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 ðpb.GenericBeaconBlock{Block: ðpb.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)) | ||
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(ðpb.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 ð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 | ||
// 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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
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 ðpb.GenericBeaconBlock{Block: ðpb.GenericBeaconBlock_Capella{Capella: pb.(*ethpb.BeaconBlockCapella)}}, nil | ||
} else if slots.ToEpoch(req.Slot) >= params.BeaconConfig().BellatrixForkEpoch { | ||
return ðpb.GenericBeaconBlock{Block: ðpb.GenericBeaconBlock_Bellatrix{Bellatrix: pb.(*ethpb.BeaconBlockBellatrix)}}, nil | ||
} else if slots.ToEpoch(req.Slot) >= params.BeaconConfig().AltairForkEpoch { | ||
return ðpb.GenericBeaconBlock{Block: ðpb.GenericBeaconBlock_Altair{Altair: pb.(*ethpb.BeaconBlockAltair)}}, nil | ||
} | ||
return ðpb.GenericBeaconBlock{Block: ðpb.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(ðpb.BeaconBlock{Body: ðpb.BeaconBlockBody{}}) | ||
if err != nil { | ||
return nil, nil, status.Errorf(codes.Internal, "Could not initialize block for proposal: %v", err) | ||
} | ||
sBlk, err = blocks.NewSignedBeaconBlock(ðpb.SignedBeaconBlock{Block: ðpb.BeaconBlock{Body: ðpb.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(ðpb.BeaconBlockAltair{Body: ðpb.BeaconBlockBodyAltair{}}) | ||
if err != nil { | ||
return nil, nil, status.Errorf(codes.Internal, "Could not initialize block for proposal: %v", err) | ||
} | ||
sBlk, err = blocks.NewSignedBeaconBlock(ðpb.SignedBeaconBlockAltair{Block: ðpb.BeaconBlockAltair{Body: ðpb.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(ðpb.BeaconBlockBellatrix{Body: ðpb.BeaconBlockBodyBellatrix{}}) | ||
if err != nil { | ||
return nil, nil, status.Errorf(codes.Internal, "Could not initialize block for proposal: %v", err) | ||
} | ||
sBlk, err = blocks.NewSignedBeaconBlock(ðpb.SignedBeaconBlockBellatrix{Block: ðpb.BeaconBlockBellatrix{Body: ðpb.BeaconBlockBodyBellatrix{}}}) | ||
if err != nil { | ||
return nil, nil, status.Errorf(codes.Internal, "Could not initialize block for proposal: %v", err) | ||
} | ||
default: | ||
blk, err = blocks.NewBeaconBlock(ðpb.BeaconBlockCapella{Body: ðpb.BeaconBlockBodyCapella{}}) | ||
if err != nil { | ||
return nil, nil, status.Errorf(codes.Internal, "Could not initialize block for proposal: %v", err) | ||
} | ||
sBlk, err = blocks.NewSignedBeaconBlock(ðpb.SignedBeaconBlockCapella{Block: ðpb.BeaconBlockCapella{Body: ðpb.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 | ||
|
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice catch!