-
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
Proposer use highest value payload vs header #11967
Conversation
Build fails |
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{} |
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 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
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.
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) |
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.
what happens if this is still bellatrix. There is no block value, then this call will fail.
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.
yeah, I still need to add coverage here. Thanks for pointing this out!
api/client/builder/bid.go
Outdated
@@ -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))) |
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.
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
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.
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 { |
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.
Don't we have helpers for this stuff instead of manual conditionals?
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 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. |
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 logic is complex and deeply nested. Would recommend splitting up into a smaller helper and testing it independently
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.
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") |
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.
Do we want to error and return in these examples? Why can't we fallback to a value of 0 and continue?
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.
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 { |
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.
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
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.
I'll add a comment
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 censoredTo 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 subsequentlyNote: 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 messierWhich issues(s) does this PR fix?
Fixes #
Other notes for review