Skip to content
← Browse all courses
Reth topic

Reth payload builder

When the consensus layer asks for a block (engine_forkchoiceUpdated with payload attributes), Reth starts a payload job. The basic job calls your builder again and again, keeping the best result, until the payload is resolved, its deadline passes, or an attempt returns Freeze, so building is a loop of attempts, not a single call.

A custom chain changes what goes into a block (ordering, system transactions, a different fee rule) by implementing the PayloadBuilder trait from reth_basic_payload_builder and plugging it into the node's components.

API at a glance

fn try_build(&self, args: BuildArguments<Attrs, Payload>) -> Result<BuildOutcome<Payload>, PayloadBuilderError>
One attempt at a better payload. Called repeatedly by the job.
BuildOutcome::{Better, Aborted, Cancelled, Freeze}
Better: a new best payload. Aborted: the attempt paid less in fees than the current best. Cancelled: the job was cancelled. Freeze: the payload is final; stop building.
fn build_empty_payload(&self, config: PayloadConfig<…>) -> Result<Payload, PayloadBuilderError>
A block with no transactions, used when the payload is resolved and no built payload is ready; by default it races any attempt still in progress.
fn on_missing_payload(&self, args) -> MissingPayloadBehaviour<Payload>
What to do when the payload is resolved and none is ready. The default races an empty payload (RaceEmptyPayload).
BuildArguments { cached_reads, config, cancel, best_payload, … }
What each attempt gets: cached state reads from earlier attempts, the payload config (parent header and attributes), a cancel token and the best payload so far.
BasicPayloadJobGenerator / PayloadBuilderService
The generator starts a job per payload request around your builder; the service receives the engine's requests and hands them to the generator.

Checked against paradigmxyz/reth at eb4c15e (2026-04-07): crates/payload/basic/src/lib.rs, crates/payload/builder/src/service.rs

Lessons on this topic