Skip to content
5 changes: 5 additions & 0 deletions beacon_chain/conf.nim
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,11 @@ type
defaultValue: 10
name: "local-block-value-boost" .}: uint8

builderProposalDelayTolerance* {.
desc: "Timeout for builder proposal delay tolerance in milliseconds"
defaultValue: 1500
name: "builder-proposal-delay-tolerance" .}: uint16

historyMode* {.
desc: "Retention strategy for historical data (archive/prune)"
defaultValue: HistoryMode.Prune
Expand Down
1 change: 0 additions & 1 deletion beacon_chain/spec/mev/electra_mev.nim
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ const

# Spec is 1 second, but mev-boost indirection can induce delay when the relay
# itself has already consumed the entire second.
BUILDER_PROPOSAL_DELAY_TOLERANCE* = 1500.milliseconds

func shortLog*(v: BlindedBeaconBlock): auto =
(
Expand Down
30 changes: 14 additions & 16 deletions beacon_chain/validators/beacon_validators.nim
Original file line number Diff line number Diff line change
Expand Up @@ -618,17 +618,17 @@ proc makeBeaconBlockForHeadAndSlot*(
proc getBlindedExecutionPayload[
EPH: electra_mev.BlindedExecutionPayloadAndBlobsBundle |
fulu_mev.BlindedExecutionPayloadAndBlobsBundle](
node: BeaconNode, payloadBuilderClient: RestClientRef, slot: Slot,
executionBlockHash: Eth2Digest, pubkey: ValidatorPubKey):
Future[BlindedBlockResult[EPH]] {.async: (raises: [CancelledError, RestError]).} =
node: BeaconNode, payloadBuilderClient: RestClientRef, slot: Slot,
executionBlockHash: Eth2Digest, pubkey: ValidatorPubKey, builderTimeout: Duration):
Future[BlindedBlockResult[EPH]] {.async: (raises: [CancelledError, RestError]).} =
# Not ideal to use `when` where instead of splitting into separate functions,
# but Nim doesn't overload on generic EPH type parameter.
when EPH is electra_mev.BlindedExecutionPayloadAndBlobsBundle:
let
response = awaitWithTimeout(
payloadBuilderClient.getHeader(
slot, executionBlockHash, pubkey),
BUILDER_PROPOSAL_DELAY_TOLERANCE):
builderTimeout):
return err "Timeout obtaining Electra blinded header from builder"

res = decodeBytesJsonOrSsz(
Expand All @@ -646,7 +646,7 @@ proc getBlindedExecutionPayload[
response = awaitWithTimeout(
payloadBuilderClient.getHeader(
slot, executionBlockHash, pubkey),
BUILDER_PROPOSAL_DELAY_TOLERANCE):
builderTimeout):
return err "Timeout obtaining Fulu blinded header from builder"

res = decodeBytesJsonOrSsz(
Expand Down Expand Up @@ -772,11 +772,11 @@ func getUnsignedBlindedBeaconBlock[
proc getBlindedBlockParts[
EPH: electra_mev.BlindedExecutionPayloadAndBlobsBundle |
fulu_mev.BlindedExecutionPayloadAndBlobsBundle](
node: BeaconNode, payloadBuilderClient: RestClientRef, head: BlockRef,
pubkey: ValidatorPubKey, slot: Slot, randao: ValidatorSig,
validator_index: ValidatorIndex, graffiti: GraffitiBytes):
Future[Result[(EPH, UInt256, UInt256, ForkedBeaconBlock, ExecutionRequests), string]]
{.async: (raises: [CancelledError]).} =
node: BeaconNode, payloadBuilderClient: RestClientRef, head: BlockRef,
pubkey: ValidatorPubKey, slot: Slot, randao: ValidatorSig,
validator_index: ValidatorIndex, graffiti: GraffitiBytes, builderTimeout: Duration):
Future[Result[(EPH, UInt256, UInt256, ForkedBeaconBlock, ExecutionRequests), string]]
{.async: (raises: [CancelledError]).} =
let
executionBlockHash = node.dag.loadExecutionBlockHash(head).valueOr:
# With checkpoint sync, the checkpoint block may be unavailable,
Expand All @@ -790,8 +790,8 @@ proc getBlindedBlockParts[
try:
awaitWithTimeout(
getBlindedExecutionPayload[EPH](
node, payloadBuilderClient, slot, executionBlockHash, pubkey),
BUILDER_PROPOSAL_DELAY_TOLERANCE):
node, payloadBuilderClient, slot, executionBlockHash, pubkey, builderTimeout),
builderTimeout):
BlindedBlockResult[EPH].err("getBlindedExecutionPayload timed out")
except RestDecodingError as exc:
BlindedBlockResult[EPH].err(
Expand Down Expand Up @@ -886,9 +886,10 @@ proc getBuilderBid[
else:
static: doAssert false

let builderTimeout = node.config.builderProposalDelayTolerance.milliseconds
let blindedBlockParts = await getBlindedBlockParts[EPH](
node, payloadBuilderClient, head, validator_pubkey, slot, randao,
validator_index, graffitiBytes)
validator_index, graffitiBytes, builderTimeout)
if blindedBlockParts.isErr:
# Not signed yet, fine to try to fall back on EL
beacon_block_builder_missed_with_fallback.inc()
Expand Down Expand Up @@ -995,9 +996,6 @@ proc collectBids(
engineBlockFut = makeBeaconBlockForHeadAndSlot(
EPS, node, randao, validator_index, graffitiBytes, head, slot)

# getBuilderBid times out after BUILDER_PROPOSAL_DELAY_TOLERANCE, with 1 more
# second for remote validators. makeBeaconBlockForHeadAndSlot times out after
# 1 second.
await allFutures(payloadBuilderBidFut, engineBlockFut)
doAssert payloadBuilderBidFut.finished and engineBlockFut.finished

Expand Down
11 changes: 11 additions & 0 deletions docs/the_nimbus_book/src/external-block-builder.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,17 @@ Additionally, the URL of the service exposing the [builder API](https://ethereum
build/nimbus_validator_client \
--payload-builder=true
```
### Builder Proposal Delay Tolerance

You can configure the builder proposal delay tolerance (MEV block builder timeout) via the following flag:

```
--builder-proposal-delay-tolerance=<milliseconds>
```

- **Default value:** 1500 (milliseconds)
- **Description:** Timeout for builder proposal delay tolerance. Increasing this value may allow the builder extra time to gather more transactions or MEV value, potentially improving block value and network efficiency. Lower values may reduce block proposal latency.


## Useful resources

Expand Down