-
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
defer payload attribute computation #14644
Conversation
8f86592
to
8222cb2
Compare
@@ -620,9 +618,6 @@ func (s *Service) lateBlockTasks(ctx context.Context) { | |||
if !s.inRegularSync() { | |||
return | |||
} | |||
s.cfg.StateNotifier.StateFeed().Send(&feed.Event{ |
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 still need this? Missed slot feed. #12153 based on this it needs to send when it's late.
I know you mentioned
The other important change is that this event is now triggered by calls to FCU, rather than piggybacking onto missed slot and new head events. This means that payload attribute events are no longer a special case, leading to simpler code, and their emission for builders is in sync with where we would notify the engine API.
but wouldn't it still need to be there?
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 the proposer is a tracked validator, lateBlockTasks
will call notifyForkchoiceUpdate
, which fires the PayloadAttributes
event. Otherwise lateBlockTasks
will early return before calling notifyForkchoiceUpdate
, in which case it won't fire. I need to check if builder/relay really wants these events for untracked validators. If it does, then we should fire the event right before the early return. Will try to ping metachris in the other issue for more insight.
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.
In the meantime I'll add it back in.
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.
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 believe it's still needed for untracked ( this was opened a while back, I updated to reflect the same as lighthouse #14204) but it's related to the untracked and submitting it, the builder should only use the registered info though.
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.
but it's related to the untracked and submitting it, the builder should only use the registered info though.
I can't quite make sense of your comment. Right now the code fires events for head updates and missed slots, whether or not the upcoming proposer is tracked. If it is tracked, the fee recipient for the tracked validator will be used. Is that the correct behavior?
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.
that's the correct behavior, just meant that regardless the builder should use the data from the validator registrations and not from the proposer attributes event
42ffe44
to
5c6a7ed
Compare
1edfa82
to
97dcbd4
Compare
@@ -31,6 +33,7 @@ import ( | |||
) | |||
|
|||
const DefaultEventFeedDepth = 1000 | |||
const payloadAttributeTimeout = 2 * time.Second |
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.
how was this timeout chosen?
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.
It's a guesstimate based on order of magnitude: 100ms possibly too short, 10s too long (would go into the next slot when triggered by lateBlockTasks). So 1s seems about right, and then I added some wiggle room.
headPayload, err := headBlock.Block().Body().Execution() | ||
if err != nil { | ||
return nil, errors.Wrap(err, "could not get execution payload") | ||
feeRecpt := params.BeaconConfig().DefaultFeeRecipient.Bytes() |
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 think this might be wrong if you set the suggested fee recipient flag for untracked validators, I think it needs to be the burn address
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 preserves the existing behavior: https://github.com/prysmaticlabs/prysm/blob/develop/beacon-chain/rpc/eth/events/events.go#L600-L604
If we want to change it I'd rather do that in a separate PR.
@@ -167,6 +171,35 @@ func (s *Service) notifyForkchoiceUpdate(ctx context.Context, arg *fcuConfig) (* | |||
return payloadID, nil | |||
} | |||
|
|||
func firePayloadAttributesEvent(ctx context.Context, f event.SubscriberSender, cfg *fcuConfig) { |
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.
Another option is to make it a method with the receiver (s *Service)
, allowing access to event.SubscriberSender
directly instead of passing it as an extra parameter. A function is probably a better choice than a method in this case
// return early if we are not proposing next slot | ||
if attribute.IsEmpty() { | ||
return | ||
} | ||
|
||
s.headLock.RLock() |
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 changes an invariant: previously, we didn’t need to call the head block if we weren’t proposing the next block, as calling the head block isn’t free. Now, we must call the head block even if we’re not proposing the next block when a late block is triggered. This might be fine, but if we want to be precise about it, we could move the head block call inside the goroutine
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've changed the event firing code and the handler to accommodate the head block being nil, looking up the block in the handler if it is nil, and making sure the root matches the head root in the event. If the head root in the event is different, we just drop the event. In this circumstance we know the mismatched event was fired by lateBlockTasks
, then essentially preempted by block processing updated head. The head update will stream its own proposer attribute event which is what the consumer really wants, so it should be fine to drop the late block event in this race.
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.
Lgtm
What type of PR is this?
Other
What does this PR do? Why is it needed?
Currently payload attribute event processing for the beacon api event feed occurs in the goroutine that reads event messages from the internal event feeds. If payload computation is slow, the goroutine reading from the event feed will be blocked. If it blocks long enough for the internal event feed to fill the queue, this would defeat the purpose of the async event stream writer design, which relies on this goroutine to process events in roughly linear time so it can consume the event queue in a timely fashion and detect the scenario where the outgoing response writing queue has backed up.
This PR mitigates that issue by spawning an extra goroutine to handle payload attribute event computation in a non-blocking fashion.
The other important change is that this event is now triggered by calls to
notifyForkchoiceUpdate
and inlateBlockTasks
, rather than piggybacking onto missed slot and new head events. This means that payload attribute events are no longer a special case, leading to simpler code, and their emission for builders is in sync with where we would notify the engine api. This also allows us to reuse the attributes that are computed for the engine api, which makes the event lightweight in the case where the proposer is tracked by the bn.Other notes for review
Acknowledgements