Skip to content

Add RPC endpoint testing_buildBlockV1#710

Draft
marcindsobczak wants to merge 16 commits intoethereum:mainfrom
marcindsobczak:main
Draft

Add RPC endpoint testing_buildBlockV1#710
marcindsobczak wants to merge 16 commits intoethereum:mainfrom
marcindsobczak:main

Conversation

@marcindsobczak
Copy link
Copy Markdown

Addressing #705


* This method MUST NOT be exposed on public-facing RPC APIs.

* It is strongly recommended that this method, and its `testing_` namespace, be disabled by default. Enabling it should require an explicit command-line flag or configuration setting by the node operator.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this fit better under debug_ namespace?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea of creating a new namespace is to prevent enabling this method by mistake, together with other debug ones.
It was specified here: ethpandaops/gas-lighting-tracker#5
This new endpoint is sensitive and should exist under a new, non-exposed namespace (i.e., not debug_ or admin_ which are sometimes exposed by RPC providers).

@spencer-tb has another idea - to make it _debug so it will be different than current debug, but will not introduce quite exotic testing namespace.
I'm open for every option.

Copy link
Copy Markdown
Contributor

@siladu siladu Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Besu actually already has a similar endpoint called engine_preparePayload_debug so would support the _debug suffix 👍

I think debug_ prefix is also fine because there are already debug_ methods that can do quite a lot of damage so it would be a misconfiguration to leave them publicly accessible IMO.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem with the debug namespace is that it contains the tracing methods, and basically everyone needs/wants the tracing methods, so debug is very often forced to be on, even for people who don't want the expose "dangerous methods"

I have no problem with a new namespace called testing being created, but I also have no problem with it going under debug since debug has been treated like a catchall and one more dangerous method isn't going to break the mess that is the debug namespace any more than it already is.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's probably the trace ones that are most dangerous, in terms of resource consumption at least 😆
But yeh I take your point, I imagine there's ppl who want trace but not this.


* The client MUST include all transactions from the `transactions` array on the block's transaction list, in the order they were provided.

* The client MUST NOT include any transactions from its local transaction pool. The resulting block MUST only contain the transactions specified in the `transactions` array.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* The client MUST NOT include any transactions from its local transaction pool. The resulting block MUST only contain the transactions specified in the `transactions` array.
* If the `transactions` array is non-empty, the client MUST NOT include any transactions from its local transaction pool. The resulting block MUST only contain the transactions specified in the `transactions` array.

It might be a useful (optional?) feature to allow for selectiung txpool transactions in the case where the transaction array is empty.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would keep it as simple as possible in V1 to make it as easy as possible to implement for all EL teams. The use case is simple here - to build a block from provided txs. Your proposition is interesting, but:

  1. It can break some test cases e.g. empty payload tests
  2. It will add complexity - in current form we can totally ignore mempool and it's transactions

Also here, if majority of people will opt to have such feature, I'm fine with adding it.

Copy link
Copy Markdown
Contributor

@siladu siladu Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think my wording keeps the option open without specifying what the client should do in the case of empty transaction array.

In Besu's case, we actually have a similar RPC method we use for internal testing, engine_preparePayload_debug see besu-eth/besu#4427
There are enough similarities that we would probably modify it to fit this spec, but the main use case for ours is to test txpool selection.

Copy link
Copy Markdown
Contributor

@MysticRyuujin MysticRyuujin Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if an empty array is passed then an empty transaction list should be used, otherwise it's non-deterministic, in the case of test chains and devnets, you may very well be trying to generate empty blocks. And to that effect, only the txs in the array should be included.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if we make an empty array be "build an empty block" and null be "use your mempool if you want to"? Or is that too much?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be fine with that, as long as the option is possible in some form that is spec-compliant, don't mind about the syntax.

Copy link
Copy Markdown
Member

@raxhvl raxhvl Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The transactions parameter MUST accept an array, not null.

Accepting mempool transactions contradicts the intent:

"It is intended to replace the multi-step workflow of sending transactions"

With null + mempool, we are back to multi-step: eth_sendRawTransaction + testing_buildBlockV1.[1]

This endpoint will be heavily used for benchmarking. We should keep the underlying logic straightforward and slim.

As always, we can extend if there is pressing need for the mempool path for testing later. Lets keep v1 simple for now.

[1]: mempool test case

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then don't send null for your use case


### Response

* result: `OBJECT` - instance of [`engine_getPayloadV5`](../engine/osaka.md#response)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not really a good description, are we returning the method? or the same response as getPayloadV5

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I read this as defining the schema of the response object to the the same as engine_getPayloadV5 response object?


* This method MUST NOT be exposed on public-facing RPC APIs.

* It is strongly recommended that this method, and its `testing_` namespace, be disabled by default. Enabling it should require an explicit command-line flag or configuration setting by the node operator.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also hide it behind the jwt?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not opposed to hiding behind jwt but there are a few things to consider:

  1. Not all clients allow enabling/disabling methods specifically on the auth rpc port
  2. It makes tooling an automation lot more difficult

Since this is a new namespace, I think it's overkill to require it to be hidden behind auth


* The client MUST NOT include any transactions from its local transaction pool. The resulting block MUST only contain the transactions specified in the `transactions` array.

* If `extraData` is provided, the client MUST set the `extraData` field of the resulting payload to this value.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The extraData field is a bit weird, since we didn't use it before, it needs to be piped through to the miner

Copy link
Copy Markdown
Contributor

@MysticRyuujin MysticRyuujin Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extraData is part of the blockhash though, is it not? I think it's important that all the fields that would contribute to the blockHash of a real block are properly represented otherwise the returned blockhash wouldn't match the actual/expected blockhash (which is very useful for testing).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With extraData edits you can generate the same block, but with a different hash, which is a nice property. All other fields will edit something in the block which is observable from EVM.

@MysticRyuujin
Copy link
Copy Markdown
Contributor

Question, this spec says parentBlockHash - but it does not say if parentBlockHash needs to be the chain head, should this method support building blocks at arbitrary block heights?

I could see it being useful for testing purposes to support something like this in hive:

Current block is 45 - pull parentBlockHash of block 44, pull all transactions and block info out of block 45, send request - compare response to actual block 45

If the method is only suppose to be used at chain head, then that should be in the spec or clarified, if it's to support running against historical blocks then that should also be clarified.

@MariusVanDerWijden
Copy link
Copy Markdown
Member

Being able to build against historical blocks is not something that we support at the moment, which would necessitate writing a lot of custom code to support this method for a use cases that is not super necessary imo, so I would prefer to spec this method as only available on the current head. Users can emulate the other behavior by first reorging the node to a certain head and then running testing_buildBlock on this old head.

@jochem-brouwer
Copy link
Copy Markdown
Member

@MariusVanDerWijden how would we reorg to an ancestor of current head block via available methods? If you forkchoiceUpdated to a head block which is an ancestor of the current chain head, then clients might reject this as it is clearly not a "head block". The other way (but this depends on the client) is to force it, for instance in Geth using debug_setHead.

Building blocks on top of a specific block is something currently necessary to create stateful tests, otherwise all tests run sequentially and it is thus hard to isolate a specific test and keep running that one.

@MariusVanDerWijden
Copy link
Copy Markdown
Member

You're right, you can only use debug.SetHead which is not standardized across clients yet. @MysticRyuujin is working on making that standardized afaik


* If `extraData` is provided, the client MUST set the `extraData` field of the resulting payload to this value.

* This method MUST NOT modify the client's canonical chain or head block. It is a read-only method for payload generation.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if invalid txs get added?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a test case for that, it's a defined error in my PR

@MysticRyuujin
Copy link
Copy Markdown
Contributor

@marcindsobczak if #747 is merged, would you be ok closing this one? As the author, I just want to make sure you're good with #747 and that it meets all your objectives here.

1. `parentBlockHash`: `DATA`, 32 Bytes - block hash of the parent of the requested block.
2. `payloadAttributes`: `Object` - instance of [`PayloadAttributesV3`](../engine/cancun.md#payloadattributesv3)
3. `transactions`: `Array of DATA` - an array of raw, signed transactions (hex-encoded `0x...` strings) to forcibly include in the generated block.
4. `extraData`: `DATA|null`, 0 to 32 Bytes - data to be set as the `extraData` field of the built block.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another feature I'd like here is a gas limit. Besides the obvious reason to create bigger blocks for benchmarks, there might also be other reasons to control the gas limit, for instance instead of going up, bringing it down (this has influence on the fee market).
The suggestion here is to add an optional parameter "gas target" or something similar, and the client will attempt to move to that gas target (within the bounds of increasing/decreasing the gas limit of a block)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can obviously be done via client config, but with EELS as driver for the test/benchmark generator it is handy to be able to control this from EELS directly, without having to edit a config to set a specific gas target.

@@ -0,0 +1,115 @@
# `testing_buildBlockV1`

This document specifies the `testing_buildBlockV1` RPC method. This method is a debugging and testing tool that simplifies the block production process into a single call. It is intended to replace the multi-step workflow of sending transactions, calling `engine_forkchoiceUpdated` with `payloadAttributes`, and then calling `engine_getPayload`.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the current spec is tied to specific types like PayloadAttributesV3and engine_getPayloadV5does this implicitly mean that for each fork which would change these types (PayloadAttributesV4) we thus also need a new testing_buildBlockVx instance? (Which is fine).

Another main motivation of this method is bypassing the txpool. We could start a client and only inject specific txs into txpool, but if we then ask it to build a block via normal route (engine API) then it is not guaranteed all txs are in it, and the order of the txs is also not guaranteed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants