-
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
Conversation
var blk interfaces.BeaconBlock | ||
var sBlk interfaces.SignedBeaconBlock | ||
var err error | ||
switch { |
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
|
||
// 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 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!
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 { | ||
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) | ||
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 comment
The 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 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 comment
The reason will be displayed to describe this comment to others. Learn more.
Let's continue here
// 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 comment
The 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.
// 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 comment
The 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 comment
The reason will be displayed to describe this comment to others. Learn more.
nice catch!
// 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 comment
The reason will be displayed to describe this comment to others. Learn more.
This should be its own tested helper
} | ||
if canUseBuilder && err != nil { |
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.
This will only use the builder if there was an error in the previous call!
} | |
if canUseBuilder && err != nil { | |
} else if canUseBuilder { |
// 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") | ||
} | ||
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 comment
The reason will be displayed to describe this comment to others. Learn more.
This should be its own tested helper
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 comment
The reason will be displayed to describe this comment to others. Learn more.
We better return the empty slice instead of erroring here.
return nil, errors.Wrap(err, "could not pack BLSToExecutionChanges") | ||
} | ||
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 comment
The reason will be displayed to describe this comment to others. Learn more.
same here
if err != nil { | ||
return nil, err | ||
} | ||
switch { |
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.
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 {
...
@@ -51,25 +56,37 @@ type BeaconBlock interface { | |||
ssz.HashRoot | |||
Version() int | |||
AsSignRequestObject() (validatorpb.SignRequestObject, error) | |||
SetBlinded(bool) |
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.
Move next to IsBlinded() bool
return ErrNotSupported("Execution", b.version) | ||
} | ||
if b.isBlinded { | ||
b.executionPayloadHeader = e // TODO: Copy? |
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.
You copy everything else, so I would say yes.
// Block returns the underlying beacon block object. | ||
func (b *SignedBeaconBlock) Block() interfaces.BeaconBlock { | ||
return b.block | ||
} | ||
|
||
// SetBlock sets the underlying beacon block object. | ||
func (b *SignedBeaconBlock) SetBlock(blk interfaces.BeaconBlock) error { |
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.
We want a copy here, right? You can create a Copy()
method on type BeaconBlock interface
and call it at the beginning here.
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 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?
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.
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)
Co-authored-by: Radosław Kapka <[email protected]>
Merging this in the next hour. Thanks for the review everyone |
This PR refactors
GetBeaconBlock
down for readability and clarity. AsGetBeaconBlock
is currently constructed, there are convoluted functions and redundant calls. It hurts readability, and it's no longer maintainable for future hard forks and block additions. This PR refactorsGetBeaconBlock
down to a single function. Please take a look at the attached mermaid diagram, which illustrates the intent of the code