Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
{
"get" : {
"tags" : [ "Validator", "Validator Required Api" ],
"operationId" : "produceBlockV4",
"summary" : "Produce a new block, without signature.",
"description" : "Requests a beacon node to produce a valid block for post-Gloas (ePBS), which can then be signed by a validator.\nSupports two operational modes controlled by `include_payload`:\n\n- **Stateless mode** (`include_payload=true`, default): Returns the execution payload envelope and blobs\n inline, suitable for multi-BN operations.\n- **Stateful mode** (`include_payload=false`): The beacon node caches the payload for separate retrieval\n via `/eth/v1/validator/execution_payload_envelope/{slot}/{beacon_block_root}`.\n",
"parameters" : [ {
"name" : "slot",
"required" : true,
"in" : "path",
"schema" : {
"type" : "string",
"description" : "The slot for which the block should be proposed.",
"example" : "1",
"format" : "uint64"
}
}, {
"name" : "randao_reveal",
"required" : true,
"in" : "query",
"schema" : {
"type" : "string",
"description" : "`BLSSignature Hex` BLS12-381 signature for the current epoch.",
"example" : "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505",
"format" : "byte"
}
}, {
"name" : "graffiti",
"in" : "query",
"schema" : {
"type" : "string",
"description" : "`Bytes32 Hex` Graffiti.",
"example" : "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
"format" : "byte"
}
}, {
"name" : "builder_boost_factor",
"in" : "query",
"schema" : {
"type" : "string",
"description" : "Percentage multiplier to apply to the builder's payload value when choosing between a\nbuilder payload header and payload from the paired execution node. This parameter is only\nrelevant if the beacon node is connected to a builder, deems it safe to produce a builder\npayload, and receives valid responses from both the builder endpoint _and_ the paired\nexecution node. When these preconditions are met, the server MUST act as follows:\n\n* if `exec_node_payload_value >= builder_boost_factor * (builder_payload_value // 100)`,\n then return a full (unblinded) block containing the execution node payload.\n* otherwise, return a blinded block containing the builder payload header.\n\nServers must support the following values of the boost factor which encode common\npreferences:\n\n* `builder_boost_factor=0`: prefer the execution node payload unless an error makes it\n unviable.\n* `builder_boost_factor=100`: default profit maximization mode; choose whichever\n payload pays more.\n* `builder_boost_factor=2**64 - 1`: prefer the builder payload unless an error or\n beacon node health check makes it unviable.\n\nServers should use saturating arithmetic or another technique to ensure that large values of\nthe `builder_boost_factor` do not trigger overflows or errors. If this parameter is\nprovided and the beacon node is not configured with a builder then the beacon node MUST\nrespond with a full block, which the caller can choose to reject if it wishes. If this\nparameter is **not** provided then it should be treated as having the default value of 100.\nIf the value is provided but out of range for a 64-bit unsigned integer, then an error\nresponse with status code 400 MUST be returned.",
"example" : "1",
"format" : "uint64"
}
}, {
"name" : "include_payload",
"in" : "query",
"schema" : {
"type" : "boolean",
"description" : "If true, returns the execution payload envelope and blobs inline in the response (stateless mode, suitable for multi-BN setups). If false, the beacon node caches the payload for separate retrieval. Default: true."
}
}, {
"name" : "skip_randao_verification",
"allowEmptyValue" : true,
"in" : "query",
"schema" : {
"type" : "string",
"description" : "Skip verification of the `randao_reveal` value. Ignored in the Teku implementation.",
"minLength" : 0,
"maxLength" : 0
}
} ],
"responses" : {
"200" : {
"description" : "Request successful",
"headers" : {
"Eth-Consensus-Version" : {
"description" : "Required in response so client can deserialize returned json or ssz data more effectively.",
"required" : true,
"schema" : {
"type" : "string",
"enum" : [ "phase0", "altair", "bellatrix", "capella", "deneb", "electra", "fulu", "gloas", "heze" ],
"example" : "phase0"
}
},
"Eth-Execution-Payload-Included" : {
"description" : "Required in response so client can determine whether execution payload is included inline.",
"required" : true,
"schema" : {
"type" : "boolean"
}
},
"Eth-Consensus-Block-Value" : {
"description" : "Consensus rewards for this block in Wei paid to the proposer. The rewards value is the sum of values of the proposer rewards from attestations, sync committees and slashings included in the proposal. Required in response so client can determine relative value of consensus blocks.",
"required" : true,
"schema" : {
"type" : "string",
"example" : "1"
}
}
},
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/ProduceBlockV4Response"
}
},
"application/octet-stream" : {
"schema" : {
"type" : "string",
"format" : "binary"
}
}
}
},
"503" : {
"description" : "Service unavailable",
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/HttpErrorResponse"
}
}
}
},
"204" : {
"description" : "Data is unavailable because the chain has not yet reached genesis",
"content" : { }
},
"501" : {
"description" : "Not implemented",
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/HttpErrorResponse"
}
}
}
},
"400" : {
"description" : "The request could not be processed, check the response for more information.",
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/HttpErrorResponse"
}
}
}
},
"500" : {
"description" : "Internal server error",
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/HttpErrorResponse"
}
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"title" : "ProduceBlockV4Response",
"type" : "object",
"required" : [ "version", "execution_payload_included", "consensus_block_value" ],
"properties" : {
"version" : {
"type" : "string",
"enum" : [ "phase0", "altair", "bellatrix", "capella", "deneb", "electra", "fulu", "gloas", "heze" ]
},
"execution_payload_included" : {
"type" : "boolean"
},
"consensus_block_value" : {
"type" : "string",
"description" : "unsigned 256 bit integer",
"example" : "1",
"format" : "uint256"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import static tech.pegasys.teku.infrastructure.http.RestApiConstants.EPOCH_QUERY_DESCRIPTION;
import static tech.pegasys.teku.infrastructure.http.RestApiConstants.GRAFFITI;
import static tech.pegasys.teku.infrastructure.http.RestApiConstants.HEADER_CONSENSUS_VERSION;
import static tech.pegasys.teku.infrastructure.http.RestApiConstants.INCLUDE_PAYLOAD;
import static tech.pegasys.teku.infrastructure.http.RestApiConstants.INDEX;
import static tech.pegasys.teku.infrastructure.http.RestApiConstants.PARAM_BLOCK_ID;
import static tech.pegasys.teku.infrastructure.http.RestApiConstants.PARAM_BLOCK_ID_DESCRIPTION;
Expand Down Expand Up @@ -160,6 +161,15 @@ public class BeaconRestApiTypes {
RestApiConstants.BUILDER_BOOST_FACTOR,
UINT64_TYPE.withDescription(BUILDER_BOOST_FACTOR_DESCRIPTION));

public static final ParameterMetadata<Boolean> INCLUDE_PAYLOAD_PARAMETER =
new ParameterMetadata<>(
INCLUDE_PAYLOAD,
BOOLEAN_TYPE.withDescription(
"If true, returns the execution payload envelope and blobs inline in the response "
+ "(stateless mode, suitable for multi-BN setups). "
+ "If false, the beacon node caches the payload for separate retrieval. "
+ "Default: true."));

public static final ParameterMetadata<Bytes32> GRAFFITI_PARAMETER =
new ParameterMetadata<>(
GRAFFITI, CoreTypes.BYTES32_TYPE.withDescription("`Bytes32 Hex` Graffiti."));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@
import tech.pegasys.teku.beaconrestapi.handlers.v2.validator.GetProposerDutiesV2;
import tech.pegasys.teku.beaconrestapi.handlers.v2.validator.PostAggregateAndProofsV2;
import tech.pegasys.teku.beaconrestapi.handlers.v3.validator.GetNewBlockV3;
import tech.pegasys.teku.beaconrestapi.handlers.v4.validator.GetNewBlockV4;
import tech.pegasys.teku.infrastructure.async.AsyncRunner;
import tech.pegasys.teku.infrastructure.async.SafeFuture;
import tech.pegasys.teku.infrastructure.events.EventChannels;
Expand Down Expand Up @@ -299,6 +300,7 @@ private static RestApi create(
.endpoint(new GetProposerDuties(dataProvider))
.endpoint(new GetProposerDutiesV2(dataProvider))
.endpoint(new GetNewBlockV3(dataProvider, schemaCache))
.endpoint(new GetNewBlockV4(dataProvider, schemaCache))
.endpoint(new GetAttestationData(dataProvider, spec))
.endpoint(new GetAggregateAttestation(dataProvider, spec))
.endpoint(new GetAggregateAttestationV2(dataProvider, schemaCache))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* Copyright Consensys Software Inc., 2026
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/

package tech.pegasys.teku.beaconrestapi.handlers.v4.validator;

import static tech.pegasys.teku.beaconrestapi.BeaconRestApiTypes.BUILDER_BOOST_FACTOR_PARAMETER;
import static tech.pegasys.teku.beaconrestapi.BeaconRestApiTypes.GRAFFITI_PARAMETER;
import static tech.pegasys.teku.beaconrestapi.BeaconRestApiTypes.INCLUDE_PAYLOAD_PARAMETER;
import static tech.pegasys.teku.beaconrestapi.BeaconRestApiTypes.RANDAO_PARAMETER;
import static tech.pegasys.teku.beaconrestapi.BeaconRestApiTypes.SKIP_RANDAO_VERIFICATION_PARAMETER;
import static tech.pegasys.teku.beaconrestapi.BeaconRestApiTypes.SLOT_PARAMETER;
import static tech.pegasys.teku.ethereum.json.types.EthereumTypes.ETH_CONSENSUS_HEADER_TYPE;
import static tech.pegasys.teku.ethereum.json.types.EthereumTypes.ETH_HEADER_CONSENSUS_BLOCK_VALUE_TYPE;
import static tech.pegasys.teku.ethereum.json.types.EthereumTypes.ETH_HEADER_EXECUTION_PAYLOAD_INCLUDED_TYPE;
import static tech.pegasys.teku.ethereum.json.types.EthereumTypes.MILESTONE_TYPE;
import static tech.pegasys.teku.ethereum.json.types.EthereumTypes.blockContainerAndMetaDataSszResponseType;
import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_NOT_IMPLEMENTED;
import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_OK;
import static tech.pegasys.teku.infrastructure.http.RestApiConstants.CONSENSUS_BLOCK_VALUE;
import static tech.pegasys.teku.infrastructure.http.RestApiConstants.EXECUTION_PAYLOAD_INCLUDED;
import static tech.pegasys.teku.infrastructure.http.RestApiConstants.SLOT_PATH_DESCRIPTION;
import static tech.pegasys.teku.infrastructure.http.RestApiConstants.TAG_VALIDATOR;
import static tech.pegasys.teku.infrastructure.http.RestApiConstants.TAG_VALIDATOR_REQUIRED;
import static tech.pegasys.teku.infrastructure.json.types.CoreTypes.BOOLEAN_TYPE;
import static tech.pegasys.teku.infrastructure.json.types.CoreTypes.UINT256_TYPE;

import com.fasterxml.jackson.core.JsonProcessingException;
import java.util.ArrayList;
import java.util.List;
import tech.pegasys.teku.api.DataProvider;
import tech.pegasys.teku.api.ValidatorDataProvider;
import tech.pegasys.teku.infrastructure.async.SafeFuture;
import tech.pegasys.teku.infrastructure.json.types.SerializableTypeDefinition;
import tech.pegasys.teku.infrastructure.restapi.endpoints.AsyncApiResponse;
import tech.pegasys.teku.infrastructure.restapi.endpoints.EndpointMetadata;
import tech.pegasys.teku.infrastructure.restapi.endpoints.RestApiEndpoint;
import tech.pegasys.teku.infrastructure.restapi.endpoints.RestApiRequest;
import tech.pegasys.teku.spec.datastructures.metadata.BlockContainerAndMetaData;
import tech.pegasys.teku.spec.schemas.SchemaDefinitionCache;

public class GetNewBlockV4 extends RestApiEndpoint {

public static final String ROUTE = "/eth/v4/validator/blocks/{slot}";

protected final ValidatorDataProvider validatorDataProvider;

public GetNewBlockV4(
final DataProvider dataProvider, final SchemaDefinitionCache schemaDefinitionCache) {
this(dataProvider.getValidatorDataProvider(), schemaDefinitionCache);
}

public GetNewBlockV4(
final ValidatorDataProvider validatorDataProvider,
@SuppressWarnings("unused") final SchemaDefinitionCache schemaDefinitionCache) {
super(getEndpointMetaData());
this.validatorDataProvider = validatorDataProvider;
}

private static EndpointMetadata getEndpointMetaData() {
return EndpointMetadata.get(ROUTE)
.operationId("produceBlockV4")
.summary("Produce a new block, without signature.")
.description(
"""
Requests a beacon node to produce a valid block for post-Gloas (ePBS), which can then be signed by a validator.
Supports two operational modes controlled by `include_payload`:

- **Stateless mode** (`include_payload=true`, default): Returns the execution payload envelope and blobs
inline, suitable for multi-BN operations.
- **Stateful mode** (`include_payload=false`): The beacon node caches the payload for separate retrieval
via `/eth/v1/validator/execution_payload_envelope/{slot}/{beacon_block_root}`.
""")
.tags(TAG_VALIDATOR, TAG_VALIDATOR_REQUIRED)
.pathParam(SLOT_PARAMETER.withDescription(SLOT_PATH_DESCRIPTION))
.queryParamRequired(RANDAO_PARAMETER)
.queryParam(GRAFFITI_PARAMETER)
.queryParamAllowsEmpty(SKIP_RANDAO_VERIFICATION_PARAMETER)
.queryParam(BUILDER_BOOST_FACTOR_PARAMETER)
.queryParam(INCLUDE_PAYLOAD_PARAMETER)
.response(
SC_OK,
"Request successful",
getResponseType(),
blockContainerAndMetaDataSszResponseType(),
getHeaders())
.withChainDataResponses()
.withNotImplementedResponse()
.build();
}

@Override
public void handleRequest(final RestApiRequest request) throws JsonProcessingException {
request.respondAsync(
SafeFuture.completedFuture(
AsyncApiResponse.respondWithError(SC_NOT_IMPLEMENTED, "Not implemented")));
}

private static SerializableTypeDefinition<BlockContainerAndMetaData> getResponseType() {
return SerializableTypeDefinition.<BlockContainerAndMetaData>object()
.name("ProduceBlockV4Response")
.withField("version", MILESTONE_TYPE, BlockContainerAndMetaData::specMilestone)
.withField(EXECUTION_PAYLOAD_INCLUDED, BOOLEAN_TYPE, blockContainerAndMetaData -> false)
.withField(
CONSENSUS_BLOCK_VALUE, UINT256_TYPE, BlockContainerAndMetaData::consensusBlockValue)
.build();
}

private static List<SerializableTypeDefinition<?>> getHeaders() {
List<SerializableTypeDefinition<?>> headers = new ArrayList<>();
headers.add(ETH_CONSENSUS_HEADER_TYPE);
headers.add(ETH_HEADER_EXECUTION_PAYLOAD_INCLUDED_TYPE);
headers.add(ETH_HEADER_CONSENSUS_BLOCK_VALUE_TYPE);
return headers;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright Consensys Software Inc., 2026
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/

package tech.pegasys.teku.beaconrestapi.handlers.v4.validator;

import static org.assertj.core.api.Assertions.assertThat;
import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_NOT_IMPLEMENTED;
import static tech.pegasys.teku.infrastructure.http.RestApiConstants.RANDAO_REVEAL;
import static tech.pegasys.teku.infrastructure.http.RestApiConstants.SLOT;

import com.fasterxml.jackson.core.JsonProcessingException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import tech.pegasys.teku.beaconrestapi.AbstractMigratedBeaconHandlerTest;
import tech.pegasys.teku.bls.BLSSignature;
import tech.pegasys.teku.bls.BLSTestUtil;

public class GetNewBlockV4Test extends AbstractMigratedBeaconHandlerTest {

private final BLSSignature signature = BLSTestUtil.randomSignature(1234);

@BeforeEach
public void setup() {
setHandler(new GetNewBlockV4(validatorDataProvider, schemaDefinitionCache));
request.setPathParameter(SLOT, "1");
request.setQueryParameter(RANDAO_REVEAL, signature.toBytesCompressed().toHexString());
}

@Test
public void shouldReturnNotImplemented() throws JsonProcessingException {
handler.handleRequest(request);
assertThat(request.getResponseCode()).isEqualTo(SC_NOT_IMPLEMENTED);
}

@Test
public void shouldHaveCorrectRoute() {
assertThat(GetNewBlockV4.ROUTE).isEqualTo("/eth/v4/validator/blocks/{slot}");
}

@Test
public void shouldHaveCorrectPath() {
assertThat(handler.getMetadata().getPath()).isEqualTo(GetNewBlockV4.ROUTE);
}
}
Loading
Loading