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

Proposer use highest value payload vs header #11967

Merged
merged 17 commits into from
Feb 10, 2023

Conversation

terencechain
Copy link
Member

@terencechain terencechain commented Feb 3, 2023

What type of PR is this?

Feature

What does this PR do? Why is it needed?

With engine_getpayloadv2, blockValue is returned, and a rational validator can compare the local block and builder block (when choosing to use mev-boost) and use the higher value block. This ensures builder can't screw over the validator by constantly bidding over the second-highest bid whilst local mempool has higher value transactions are getting censored

To achieve this, we add Value() (*big.Int, error) to the execution interface and as we construct the interface during block proposal, the value from engine/builder-api gets passed in subsequently
Note: in any other instances, the value is just big.Int(0) which is fair IMO given this is only used during the proposal. The alternate method is to pass values as arguments through the proposal and that's messier

Which issues(s) does this PR fix?

Fixes #

Other notes for review

@prestonvanloon
Copy link
Member

Build fails

@terencechain terencechain self-assigned this Feb 8, 2023
@prestonvanloon
Copy link
Member

conflicts, build fails

@@ -211,12 +211,13 @@ func (s *Service) GetPayload(ctx context.Context, payloadId [8]byte, slot primit
defer cancel()

if slots.ToEpoch(slot) >= params.BeaconConfig().CapellaForkEpoch {
result := &pb.ExecutionPayloadCapella{}
result := &pb.ExecutionPayloadCapellaWithValue{}
Copy link
Member

Choose a reason for hiding this comment

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

if all capella payloads are now going to have block value in them, why do we need to define a separate type ? Cant we reuse the same capella type here

Copy link
Member Author

Choose a reason for hiding this comment

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

Capella payload doesn't have block value in them. It's the GetPayloadV2 response that has a block value in them. Hence we are creating a different type that encompasses both the payload itself and the block value

log.WithError(err).Warn("Proposer: failed to set execution payload")
} else {
return nil
localPayload, err := vs.getExecutionPayload(ctx, slot, idx, blk.ParentRoot(), headState)
Copy link
Member

Choose a reason for hiding this comment

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

what happens if this is still bellatrix. There is no block value, then this call will fail.

Copy link
Member Author

Choose a reason for hiding this comment

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

yeah, I still need to add coverage here. Thanks for pointing this out!

@terencechain terencechain added Ready For Review A pull request ready for code review Priority: High High priority item labels Feb 9, 2023
@@ -156,7 +159,7 @@ func WrappedBuilderBidCapella(p *ethpb.BuilderBidCapella) (Bid, error) {

// Header --
func (b builderBidCapella) Header() (interfaces.ExecutionData, error) {
return blocks.WrappedExecutionPayloadHeaderCapella(b.p.Header)
return blocks.WrappedExecutionPayloadHeaderCapella(b.p.Header, big.NewInt(0).SetBytes(bytesutil.ReverseByteOrder(b.p.Value)))
Copy link
Contributor

Choose a reason for hiding this comment

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

When I look at this line in a vacuum, there exists a hidden coupling because you're tied to expecting a big endian ordering from b.p.Value. What if this changes in the future? then you'll be reversing into an ordering you do not want. Instead, I would be explicit here about the byte ordering you want

Copy link
Member Author

@terencechain terencechain Feb 10, 2023

Choose a reason for hiding this comment

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

Adding a comment for clarity. If this change in the future then it'll be a dramatic hard fork change then we'll have to sweep the entire client codebase for all the instances. Things will fail abruptly and I argue that's ok. This is the biggest technique ethereum debt where we chose little endian for consensus layer

if err != nil {
return nil, errors.Wrap(err, "could not wrap capella payload")
}
return w, s.ErrSubmitBlindedBlock
}

// GetHeader for mocking.
func (s *MockBuilderService) GetHeader(context.Context, primitives.Slot, [32]byte, [48]byte) (builder.SignedBid, error) {
func (s *MockBuilderService) GetHeader(ctx context.Context, slot primitives.Slot, hr [32]byte, pb [48]byte) (builder.SignedBid, error) {
if slots.ToEpoch(slot) >= params.BeaconConfig().CapellaForkEpoch {
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't we have helpers for this stuff instead of manual conditionals?

Copy link
Member Author

Choose a reason for hiding this comment

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

We do not. We use these manual conditionals across the codebase. We certain can clean up but I prefer to do this in another PR

blk.SetBlinded(true)
if err := blk.SetExecution(h); err != nil {
log.WithError(err).Warn("Proposer: failed to set execution payload")
// Can only check values if block is Capella or later.
Copy link
Contributor

Choose a reason for hiding this comment

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

This logic is complex and deeply nested. Would recommend splitting up into a smaller helper and testing it independently

Copy link
Member Author

Choose a reason for hiding this comment

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

I used a switch case instead of if else. Other than that, it's really hard to split this function up. The concept of it is fairly straightforward. All the local block failures, return. All the remote block failures. cont to use local block

}
localValue, err := localPayload.Value()
if err != nil {
return errors.Wrap(err, "failed to get local payload value")
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we want to error and return in these examples? Why can't we fallback to a value of 0 and continue?

Copy link
Member Author

Choose a reason for hiding this comment

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

Because this is a local block payload. If it fails then I argue we should abort the ship.
The ones that are ok to log and continue are builder block payload failures

builderValue, err := builderPayload.Value()
if err != nil {
log.WithError(err).Warn("Proposer: failed to get builder payload value") // Default to local if can't get builder value.
} else if builderValue.Cmp(localValue) > 0 {
Copy link
Contributor

Choose a reason for hiding this comment

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

To me, big int comparison methods are always unintuitive and I wish I could remember the < and > conditions. Maybe a comment ? Perhaps we could benefit instead from a Max method that acts on big ints because thats what we want

Copy link
Member Author

Choose a reason for hiding this comment

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

I'll add a comment

rauljordan
rauljordan previously approved these changes Feb 10, 2023
@terencechain terencechain merged commit e136e10 into develop Feb 10, 2023
@delete-merged-branch delete-merged-branch bot deleted the proposer-compare-values branch February 10, 2023 19:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Priority: High High priority item Ready For Review A pull request ready for code review
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants