-
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 3 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,9 +12,12 @@ 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/config/params" | ||||||||
"github.com/prysmaticlabs/prysm/v3/consensus-types/blocks" | ||||||||
|
@@ -41,25 +44,215 @@ 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, fmt.Errorf("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, err | ||||||||
} | ||||||||
|
||||||||
var blk interfaces.BeaconBlock | ||||||||
var sBlk interfaces.SignedBeaconBlock | ||||||||
var err error | ||||||||
switch { | ||||||||
case slots.ToEpoch(req.Slot) < params.BeaconConfig().AltairForkEpoch: | ||||||||
blk, err = blocks.NewBeaconBlock(ðpb.BeaconBlock{Body: ðpb.BeaconBlockBody{}}) | ||||||||
if err != nil { | ||||||||
return 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, status.Errorf(codes.Internal, "Could not fetch phase0 beacon block: %v", err) | ||||||||
return nil, status.Errorf(codes.Internal, "Could not initialize block for proposal: %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) | ||||||||
case slots.ToEpoch(req.Slot) < params.BeaconConfig().BellatrixForkEpoch: | ||||||||
blk, err = blocks.NewBeaconBlock(ðpb.BeaconBlockAltair{Body: ðpb.BeaconBlockBodyAltair{}}) | ||||||||
if err != nil { | ||||||||
return nil, status.Errorf(codes.Internal, "Could not fetch Altair beacon block: %v", err) | ||||||||
return 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, status.Errorf(codes.Internal, "Could not initialize block for proposal: %v", err) | ||||||||
} | ||||||||
case slots.ToEpoch(req.Slot) < params.BeaconConfig().CapellaForkEpoch: | ||||||||
blk, err = blocks.NewBeaconBlock(ðpb.BeaconBlockBellatrix{Body: ðpb.BeaconBlockBodyBellatrix{}}) | ||||||||
if err != nil { | ||||||||
return 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, 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, 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, status.Errorf(codes.Internal, "Could not initialize block for proposal: %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 { | ||||||||
parentRoot, err := vs.HeadFetcher.HeadRoot(ctx) | ||||||||
if err != nil { | ||||||||
return nil, fmt.Errorf("could not retrieve head root: %v", err) | ||||||||
} | ||||||||
head, err := vs.HeadFetcher.HeadState(ctx) | ||||||||
if err != nil { | ||||||||
return nil, fmt.Errorf("could not get head state %v", err) | ||||||||
} | ||||||||
head, err = transition.ProcessSlotsUsingNextSlotCache(ctx, head, parentRoot, req.Slot) | ||||||||
if err != nil { | ||||||||
return nil, fmt.Errorf("could not advance slots to calculate proposer index: %v", 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 { | ||||||||
return nil, fmt.Errorf("could not get ETH1 data: %v", err) | ||||||||
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. Let's continue here |
||||||||
} | ||||||||
blk.Body().SetEth1Data(eth1Data) | ||||||||
|
||||||||
// Set deposit and attestation. | ||||||||
deposits, atts, err := vs.packDepositsAndAttestations(ctx, head, eth1Data) | ||||||||
if err != nil { | ||||||||
return nil, err | ||||||||
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. Let's continue here, that is, let's not pack deposits and attestations if we fail above. |
||||||||
} | ||||||||
blk.Body().SetDeposits(deposits) | ||||||||
blk.Body().SetAttestations(atts) | ||||||||
|
||||||||
// 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 | ||||||||
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) | ||||||||
} | ||||||||
blk.Body().SetProposerSlashings(validProposerSlashings) | ||||||||
blk.Body().SetAttesterSlashings(validAttSlashings) | ||||||||
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. This should be its own tested helper |
||||||||
|
||||||||
// Set exits | ||||||||
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) | ||||||||
} | ||||||||
blk.Body().SetVoluntaryExits(validExits) | ||||||||
|
||||||||
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. This should be its own tested helper |
||||||||
// Set sync aggregate. New in Altair | ||||||||
if slots.ToEpoch(req.Slot) >= params.BeaconConfig().AltairForkEpoch { | ||||||||
syncAggregate, err := vs.getSyncAggregate(ctx, req.Slot-1, bytesutil.ToBytes32(parentRoot)) | ||||||||
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 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 commentThe reason will be displayed to describe this comment to others. Learn more. nice catch! |
||||||||
if err != nil { | ||||||||
return nil, errors.Wrap(err, "could not compute the sync aggregate") | ||||||||
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. We should continue here instead of failing |
||||||||
} | ||||||||
if err := blk.Body().SetSyncAggregate(syncAggregate); err != nil { | ||||||||
return nil, errors.Wrap(err, "could not set sync aggregate") | ||||||||
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. same here, we should log an error and have another helper |
||||||||
} | ||||||||
} | ||||||||
|
||||||||
// 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") | ||||||||
} | ||||||||
if canUseBuilder && err != nil { | ||||||||
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. This will only use the builder if there was an error in the previous call!
Suggested change
|
||||||||
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, errors.Wrap(err, "could not get execution payload") | ||||||||
} | ||||||||
if err := blk.Body().SetExecution(executionData); err != nil { | ||||||||
return nil, errors.Wrap(err, "could not set execution payload") | ||||||||
} | ||||||||
} | ||||||||
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. Nesting and complex conditional logic can be reduced by creating a function that returns an execution data. Then, the caller can set the block's execution value to it |
||||||||
} | ||||||||
|
||||||||
// 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 { | ||||||||
return nil, errors.Wrap(err, "could not pack BLSToExecutionChanges") | ||||||||
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. We better return the empty slice instead of erroring here. |
||||||||
} | ||||||||
if err := blk.Body().SetBLSToExecutionChanges(changes); err != nil { | ||||||||
return nil, errors.Wrap(err, "could not set BLSToExecutionChanges") | ||||||||
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. same here |
||||||||
} | ||||||||
} | ||||||||
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. This should be its own tested helper |
||||||||
|
||||||||
if err := sBlk.SetBlock(blk); err != nil { | ||||||||
return nil, err | ||||||||
} | ||||||||
return vs.getBellatrixBeaconBlock(ctx, req) | ||||||||
sr, err := vs.computeStateRoot(ctx, sBlk) | ||||||||
if err != nil { | ||||||||
return nil, fmt.Errorf("could not compute state root: %v", err) | ||||||||
} | ||||||||
blk.SetStateRoot(sr) | ||||||||
|
||||||||
pb, err := blk.Proto() | ||||||||
if err != nil { | ||||||||
return nil, err | ||||||||
} | ||||||||
switch { | ||||||||
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. Is there any reason to use a switch statement here instead of a sequence of if checks? Also I would start with Capella if slots.ToEpoch(req.Slot) >= params.BeaconConfig().CapellaForkEpoch {
return ðpb.GenericBeaconBlock{Block: ðpb.GenericBeaconBlock_Capella{Capella: pb.(*ethpb.BeaconBlockCapella)}}, nil
}
if slots.ToEpoch(req.Slot) >= params.BeaconConfig().BellatrixForkEpoch {
... |
||||||||
case slots.ToEpoch(req.Slot) < params.BeaconConfig().AltairForkEpoch: | ||||||||
return ðpb.GenericBeaconBlock{Block: ðpb.GenericBeaconBlock_Phase0{Phase0: pb.(*ethpb.BeaconBlock)}}, nil | ||||||||
case slots.ToEpoch(req.Slot) < params.BeaconConfig().BellatrixForkEpoch: | ||||||||
return ðpb.GenericBeaconBlock{Block: ðpb.GenericBeaconBlock_Altair{Altair: pb.(*ethpb.BeaconBlockAltair)}}, nil | ||||||||
case slots.ToEpoch(req.Slot) < params.BeaconConfig().CapellaForkEpoch: | ||||||||
return ðpb.GenericBeaconBlock{Block: ðpb.GenericBeaconBlock_Bellatrix{Bellatrix: pb.(*ethpb.BeaconBlockBellatrix)}}, nil | ||||||||
} | ||||||||
return ðpb.GenericBeaconBlock{Block: ðpb.GenericBeaconBlock_Capella{Capella: pb.(*ethpb.BeaconBlockCapella)}}, nil | ||||||||
} | ||||||||
|
||||||||
// 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.
Can this be its own helper func with tests? Should take in a slot and return the block interface. Maybe call it PrepareBeaconBlockToSign to make it clear it is a signed data structure but does not contain a sig