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

Better proposal RPC #11721

merged 8 commits into from
Dec 8, 2022

Conversation

terencechain
Copy link
Member

@terencechain terencechain commented Dec 4, 2022

This PR refactors GetBeaconBlock down for readability and clarity. As GetBeaconBlock 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 refactors GetBeaconBlock down to a single function. Please take a look at the attached mermaid diagram, which illustrates the intent of the code

graph TD

GetBeaconBlock --> C1{Returne err if syncing}
C1 --> C2{Returne err if optimsitic}
C2 --> A{Initialize empty `block` and `signed_block` based on request slot}
A -->B{Process head state to requested slot}
B -->C{Set `slot`, `graffiti`, `randao_reveal` and `parent_root` to block}
C -->D{Get `eth1_data` and set to block}
D -->E{Get `deposit` and `attestation` and set to block}
E -->F{Get `proposer_index` and set to block}
F -->G{Get `slashings` and set to block}
G -->H{Get `exit` and set to block}
H -->|If Altair| I{Get `sync_aggregate` and set to block}
I -->|If Bellatrix| J1{If validator registered to use mev-boost and no error}
J1 -->J2{If circuit breaker is not active and no error}
J2 -->J3{Get `execution_data` from builder} 
J1 -->|error|J{Get `execution_data` from local EE and set to block}
J1 -->|not_registered|J
J2 -->|error|J
J2 -->|active|J
J3 -->|error|J
J3 -->|If Capella| K{Get `bls_to_exec` and set to block}
J -->|If Capella|K
K -->L{Compute state root and set state root}
L -->M{Return}
Loading

@terencechain terencechain requested a review from a team as a code owner December 4, 2022 16:42
@terencechain terencechain requested review from saolyn, james-prysm and symbolpunk and removed request for a team December 4, 2022 16:42
var blk interfaces.BeaconBlock
var sBlk interfaces.SignedBeaconBlock
var err error
switch {
Copy link
Contributor

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))
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!

Comment on lines 194 to 220
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")
}
}
Copy link
Contributor

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)
Copy link
Contributor

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
Copy link
Contributor

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.

Comment on lines 140 to 162
// 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)
Copy link
Contributor

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))
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!

Comment on lines 164 to 180
// 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)

Copy link
Contributor

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

Comment on lines 198 to 199
}
if canUseBuilder && err != nil {
Copy link
Contributor

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!

Suggested change
}
if canUseBuilder && err != nil {
} else if canUseBuilder {

Comment on lines 223 to 232
// 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")
}
}
Copy link
Contributor

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")
Copy link
Contributor

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")
Copy link
Contributor

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 {
Copy link
Contributor

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 &ethpb.GenericBeaconBlock{Block: &ethpb.GenericBeaconBlock_Capella{Capella: pb.(*ethpb.BeaconBlockCapella)}}, nil
} 
if slots.ToEpoch(req.Slot) >= params.BeaconConfig().BellatrixForkEpoch {    
...

beacon-chain/rpc/prysm/v1alpha1/validator/proposer.go Outdated Show resolved Hide resolved
@@ -51,25 +56,37 @@ type BeaconBlock interface {
ssz.HashRoot
Version() int
AsSignRequestObject() (validatorpb.SignRequestObject, error)
SetBlinded(bool)
Copy link
Contributor

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?
Copy link
Contributor

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 {
Copy link
Contributor

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)
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)

@terencechain
Copy link
Member Author

Merging this in the next hour. Thanks for the review everyone

@terencechain terencechain merged commit f397cba into capella Dec 8, 2022
@delete-merged-branch delete-merged-branch bot deleted the better-validator-rpcs branch December 8, 2022 15:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants