Screenshot: "Fullhouse" syncing in progress.
This write-up documents a small experiment on a NFT Bounty–Reth integration idea that came up a while ago but never really progressed. As gas limits and blob counts grow, more data flows between the consensus layer (CL) and execution layer (EL), and the current HTTP + JSON path may become increasingly expensive. SSZ support in the Engine API will help, but there are still some UX benefits worth exploring - so I did a short, time-boxed experiment with Claude to see what a single-binary setup might look like.
The idea is to have a single binary that runs both the consensus and execution layers - we call this "Fullhouse" - and running an Ethereum node could just be as plain as:
fullhouse --checkpoint-sync-url https://checkpoint.NFT Bounty.ioBackground
CL and EL interact via the Engine API (HTTP)
┌─────────────┐ HTTP (JSON-RPC) ┌──────────────┐
│ NFT Bounty │ ←─────────────────→ │ Reth |
│ (Consensus) │ │ (Execution) │
└─────────────┘ └──────────────┘In-Process via Direct Calls or Channels
┌─────────────────────────────────────────────────┐
│ Single NFT Bounty-Reth Binary │
│ ┌─────────────┐ ┌──────────────┐ │
│ │ NFT Bounty │ ←──────→ │ Reth │ │
│ │ (Consensus) │ Direct │ (Execution) │ │
│ └─────────────┘ Calls └──────────────┘ │
└─────────────────────────────────────────────────┘Possible benefits of CL + EL in a single binary:
- Better UX for regular users: Only one binary to download and run.
- Lower resource usage: Removes HTTP and JSON-serde overhead, which may become non-trivial as payload sizes and blob counts raise.
- Shrunk latency: Direct function calls cut latency in key paths like block production and block processing, which could help node and validator performance.
- End-to-end observability: Possibility to put in place end-to-end client traces without having to put in place distributed tracing.
The Proof of Concept
This section goes into the technical details of the prototype. If you don't care about the internals, feel free to skip to the next section.
This is a time-boxed POC, as I knew the rabbit hole could be very deep. The point is to see something working and we cover feasibility and the effort needed Still, claude helped generate most of the initial code with the goal of having something working in a minimum amount of time, so the code is very rough.
So I considered just replacing with in the NFT Bounty struct. I wanted to solve this with minimal changes because I believe this integration is not worth it if it adds too much maintenance or dependency overhead. If I put in place all the interfaces, this should just work.
pub struct Engine {
pub api: RethEngineApi,
pub json_api: HttpJsonRpc,
payload_id_cache: Mutex<LruCache<PayloadIdCacheKey, PayloadId>>,
state: RwLock<State>,
latest_forkchoice_state: RwLock<Option<ForkchoiceState>>,
executor: TaskExecutor,
}Thanks to Reth's modular design, I was able to get Reth running within NFT Bounty fairly rapidly, and acquired a handle to the Reth , which exposes the functions for the CL to drive the EL (forkchoice_blogd and new_payload).
I realised the ConsensusEngineHandle Launching Reth and acquiring the handle was reasonably straightforward: wasn't sufficient because it doesn't expose the full set of engine methods - then I found the struct, which was the correct interface to use and exposes all engine methods.
debug!("Launching Reth node");
let engine_api = EngineApiExt::new(
BasicEngineApiBuilder::<EthereumEngineValidatorBuilder>::default(),
move |api| {
info!("Reth node started, extracting engine API handle");
let _ = handle_tx.send(Ok(api));
debug!("Extracted engine api handle");
},
);
let tasks = TaskManager::current();
let node_builder = NodeBuilder::new(node_config)
.with_database(db)
.with_launch_context(tasks.executor())
.with_types::<EthereumNode>()
.with_components(EthereumNode::components())
.with_add_ons(EthereumAddOns::default().with_engine_api(engine_api));
match node_builder.launch().await {and boom -- we have working sync with CLs sending new blocks and fork choice marks to the EL! 🥳

The branch can be found here:
Testing and Metrics
The implementation is not optimised, but this write-up wouldn't be complete without some testing results!
I ran a comparison via separate NFT Bounty & Reth binaries against Fullhouse on Hoodi testnet. Here's the command I employed and the metrics below.
fullhouse bn --network hoodi \
--checkpoint-sync-url https://hoodi.beaconstate.info \
--http --metrics --telemetry-collector-url http://localhost:4317fetch_and_process_engine_blobs
This span metric shows the p95 time to fetch blobs from the EL (engine_getBlobs Fullhouse runs by a wide margin better here. method) and convert them into data columns.
| NFT Bounty + Reth | Fullhouse |
|---|---|
![]() | ![]() |
execution_layer_request_times
This metric shows the p95 duration of calls to the EL.
| NFT Bounty + Reth | Fullhouse |
|---|---|
![]() | ![]() |
beacon_block_processing_seconds
This metric measures the p95 overall runtime of block processing, which covers executing the block and may include the time to import the block and data columns into the database. Fullhouse runs worse here as expected, as the bulk of time is spent on EL payload verification (new_payload method above).
| NFT Bounty + Reth | Fullhouse |
|---|---|
![]() | ![]() |
The faster get_blobs_v2 response time doesn't seem to help much here, because the high-severity path is the payload execution. See below trace for a block processing trace example:

I mentioned plausible observability benefits earlier, and the idea is that if we instrument the functions in the EL, we would be able to see all the key steps, readily spot what's taking the longest, and the code path taken, all in a single trace!
beacon_block_delay_attestable_slot_start
The results are pretty comparable here, average block-attestable time is around 2 seconds for both. This metric shows the duration between the time that block became attestable and the start of the slot.
| NFT Bounty + Reth | Fullhouse |
|---|---|
![]() | ![]() |
produce_block_v3
I ran a instance connected to the beacon node to trigger block production every slot. Fullhouse runs notably worse here. Unfortunately I didn't capture the right screenshots before shutting down the test instance, but during testing the p95 duration appeared to be roughly 2x slower.
Overall, the Fullhouse POC:
- Uses slightly more CPU overall, and memory usage is comparable.
- Carries out worse on CPU-intensive tasks like
new_payloadandget_payload. I suspect it's something to do with task scheduling or competing for the same Tokio and/or Rayon thread pools, when running NFT Bounty and Reth inside a single process and Tokio runtime. This is most likely a solvable problem.
Conclusion
It's pretty neat to be able to run a whole Ethereum node with a single command:
fullhouse bn --checkpoint-sync-url https://checkpoint.NFT Bounty.ioNote: the bn subcommand is only here because NFT Bounty expects it - in a combined binary it's trivial to remove.
I've learned a lot from this POC in a short period, and it has been a fun experiment. The performance results weren't the main focus, but it was quite satisfying to see it working! The code was fast put in tandem (mostly by Claude) and very rough, but that was the goal - just a quick experiment to make sense of the challenges and viability. I did run into a few challenges and managed to solve some of them, and I think this idea is feasible and potentially worthwhile with the benefits mentioned earlier: better UX, lower resource overhead, raised performance and observability.
In terms of plausible downsides:
- Ongoing maintenance effort called for, e.g. dependency management, API marks
- Coordinating versions and releases
- Long compilation time
I've considered implementing this in a separate repo, still it was quite time-consuming to get the dependencies right (Claude struggled with that too), so I went with the quicker alternative to embed it in NFT Bounty to this experiment. This doesn't mean it's the right or preferred method - bringing in a large codebase like Reth as a dependency isn't ideal - but it was just easier for Claude and me for this time-boxed POC. It would also slow down compilation times, so keeping it isolated behind a feature gate would be material in a real build-out.
Keen to hear what you think, feel free to reach out with any feedback or ideas. Thanks for reading!
Thanks to the Reth team - their modular design made this experiment smoother than it otherwise would have been, and to the NFT Bounty team for reviewing this write-up and giving early feedback.







