-
Notifications
You must be signed in to change notification settings - Fork 120
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
Redis waitgroup for optimistic block sync #538
base: main
Are you sure you want to change the base?
Changes from all commits
7c815f3
00f30db
8396708
d668875
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -516,6 +516,7 @@ func (api *RelayAPI) IsReady() bool { | |||||
// - Stop returning bids | ||||||
// - Set ready /readyz to negative status | ||||||
// - Wait a bit to allow removal of service from load balancer and draining of requests | ||||||
// - If in the middle of processing optimistic blocks, wait for those to finish and release redis lock | ||||||
func (api *RelayAPI) StopServer() (err error) { | ||||||
// avoid running this twice. setting srvShutdown to true makes /readyz switch to negative status | ||||||
if wasStopping := api.srvShutdown.Swap(true); wasStopping { | ||||||
|
@@ -538,6 +539,13 @@ func (api *RelayAPI) StopServer() (err error) { | |||||
// wait for any active getPayload call to finish | ||||||
api.getPayloadCallsInFlight.Wait() | ||||||
|
||||||
// wait for optimistic blocks | ||||||
api.optimisticBlocksWG.Wait() | ||||||
err = api.redis.EndProcessingSlot(context.Background()) | ||||||
if err != nil { | ||||||
api.log.WithError(err).Error("failed to update redis optimistic processing slot") | ||||||
} | ||||||
|
||||||
// shutdown | ||||||
return api.srv.Shutdown(context.Background()) | ||||||
} | ||||||
|
@@ -754,15 +762,19 @@ func (api *RelayAPI) processNewSlot(headSlot uint64) { | |||||
// store the head slot | ||||||
api.headSlot.Store(headSlot) | ||||||
|
||||||
// only for builder-api | ||||||
// for both apis | ||||||
if api.opts.BlockBuilderAPI || api.opts.ProposerAPI { | ||||||
// update proposer duties in the background | ||||||
go api.updateProposerDuties(headSlot) | ||||||
} | ||||||
|
||||||
// for block builder api | ||||||
if api.opts.BlockBuilderAPI { | ||||||
// update the optimistic slot | ||||||
go api.prepareBuildersForSlot(headSlot) | ||||||
go api.prepareBuildersForSlot(headSlot, prevHeadSlot) | ||||||
} | ||||||
|
||||||
// for proposer api | ||||||
if api.opts.ProposerAPI { | ||||||
go api.datastore.RefreshKnownValidators(api.log, api.beaconClient, headSlot) | ||||||
} | ||||||
|
@@ -825,11 +837,32 @@ func (api *RelayAPI) updateProposerDuties(headSlot uint64) { | |||||
api.log.Infof("proposer duties updated: %s", strings.Join(_duties, ", ")) | ||||||
} | ||||||
|
||||||
func (api *RelayAPI) prepareBuildersForSlot(headSlot uint64) { | ||||||
// Wait until there are no optimistic blocks being processed. Then we can | ||||||
// safely update the slot. | ||||||
func (api *RelayAPI) prepareBuildersForSlot(headSlot, prevHeadSlot uint64) { | ||||||
// First wait for this process to finish processing optimistic blocks | ||||||
api.optimisticBlocksWG.Wait() | ||||||
|
||||||
// Now we release our lock and wait for all other builder processes to wrap up | ||||||
err := api.redis.EndProcessingSlot(context.Background()) | ||||||
if err != nil { | ||||||
api.log.WithError(err).Error("failed to unlock redis optimistic processing slot") | ||||||
} | ||||||
err = api.redis.WaitForSlotComplete(context.Background(), prevHeadSlot+1) | ||||||
if err != nil { | ||||||
api.log.WithError(err).Error("failed to get redis optimistic processing slot") | ||||||
} | ||||||
|
||||||
// Prevent race with StopServer, make sure we don't lock up redis if the server is shutting down | ||||||
if api.srvShutdown.Load() { | ||||||
return | ||||||
} | ||||||
|
||||||
// Update the optimistic slot and signal processing of the next slot | ||||||
api.optimisticSlot.Store(headSlot + 1) | ||||||
err = api.redis.BeginProcessingSlot(context.Background(), headSlot+1) | ||||||
if err != nil { | ||||||
api.log.WithError(err).Error("failed to lock redis optimistic processing slot") | ||||||
api.optimisticSlot.Store(0) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why is the optimistic slot 0 if redis fails to process the slot instead of the previous head? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The only place api.optimisticSlot is used is in deciding whether or not to process a submission optimistically: mev-boost-relay/services/api/service.go Line 2128 in d668875
We only reach this mev-boost-relay/services/api/service.go Line 477 in d668875
So the only valid block submissions we receive at this point are those with So while there are a few options for code flow, the current design sets to |
||||||
} | ||||||
|
||||||
builders, err := api.db.GetBlockBuilders() | ||||||
if err != nil { | ||||||
|
@@ -1383,8 +1416,11 @@ func (api *RelayAPI) handleGetPayload(w http.ResponseWriter, req *http.Request) | |||||
log.WithError(err).Error("failed to increment builder-stats after getPayload") | ||||||
} | ||||||
|
||||||
// Wait until optimistic blocks are complete. | ||||||
api.optimisticBlocksWG.Wait() | ||||||
// Wait until optimistic blocks are complete using the redis waitgroup | ||||||
err = api.redis.WaitForSlotComplete(context.Background(), uint64(slot)) | ||||||
if err != nil { | ||||||
api.log.WithError(err).Error("failed to get redis optimistic processing slot") | ||||||
} | ||||||
|
||||||
// Check if there is a demotion for the winning block. | ||||||
_, err = api.db.GetBuilderDemotion(bidTrace) | ||||||
|
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.
wondering if there should be a timeout if
EndProcessingSlot
fails to decrement the processing slot and the loop is stuck waiting for the slot to completeThere 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.
The current failsafe is that the key is slot-specific and has a fairly short expiry. The loop will break on a value of 0, key expiry, or any other redis error. But it probably wouldn't hurt to have an explicit timeout on the mev-boost-relay side. I'll whip something up.
Also can consider keyspace events (https://redis.io/docs/manual/keyspace-notifications/) as a cleaner way to accomplish this, but might require some redis server config.