diff --git a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockOperationSelectorFactory.java b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockOperationSelectorFactory.java index ac812c12fba..441387d29d4 100644 --- a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockOperationSelectorFactory.java +++ b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockOperationSelectorFactory.java @@ -53,6 +53,7 @@ import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBodyBuilder; import tech.pegasys.teku.spec.datastructures.builder.BuilderBid; import tech.pegasys.teku.spec.datastructures.builder.BuilderPayload; +import tech.pegasys.teku.spec.datastructures.builder.versions.gloas.BuilderConfig; import tech.pegasys.teku.spec.datastructures.execution.BlobAndCellProofs; import tech.pegasys.teku.spec.datastructures.execution.BlobsBundle; import tech.pegasys.teku.spec.datastructures.execution.BuilderBidOrFallbackData; @@ -389,7 +390,7 @@ private SafeFuture setExecutionData( executionPayloadContext.orElseThrow(), blockSlotState, shouldTryBuilderFlow, - blockProductionContext.requestedBuilderBoostFactor(), + blockProductionContext.builderConfig().map(BuilderConfig::getBuilderBoostFactor), blockProductionContext.blockProductionPerformance()); return SafeFuture.allOf( @@ -556,7 +557,7 @@ private SafeFuture setExecutionPayloadBid( blockProductionContext.parentExecutionBlockHash(), blockSlotState, executionPayloadResult.getPayloadResponseFutureFromLocalFlowRequired(), - blockProductionContext.requestedBuilderBoostFactor(), + blockProductionContext.builderConfig().map(BuilderConfig::getBuilderBoostFactor), blockProductionContext.blockProductionPerformance()) .thenAccept(bodyBuilder::signedExecutionPayloadBid); return SafeFuture.allOf( diff --git a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockProductionContext.java b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockProductionContext.java index 2eccf8e89de..2791d03eafe 100644 --- a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockProductionContext.java +++ b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockProductionContext.java @@ -21,6 +21,7 @@ import tech.pegasys.teku.ethereum.performance.trackers.BlockProductionPerformance; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.datastructures.builder.versions.gloas.BuilderConfig; import tech.pegasys.teku.spec.datastructures.forkchoice.ForkChoiceNode; import tech.pegasys.teku.spec.datastructures.forkchoice.ForkChoicePayloadStatus; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; @@ -33,7 +34,7 @@ public record BlockProductionContext( Bytes32 parentExecutionBlockHash, BLSSignature randaoReveal, Optional graffiti, - Optional requestedBuilderBoostFactor, + Optional builderConfig, BlockProductionPerformance blockProductionPerformance) { public static BlockProductionContext create( @@ -43,7 +44,7 @@ public static BlockProductionContext create( final ChainHead parentChainHead, final BLSSignature randaoReveal, final Optional graffiti, - final Optional requestedBuilderBoostFactor, + final Optional builderConfig, final BlockProductionPerformance blockProductionPerformance) { checkArgument( blockSlotState.getSlot().equals(proposalSlot), @@ -63,7 +64,7 @@ public static BlockProductionContext create( parentChainHead.getExecutionBlockHash(), randaoReveal, graffiti, - requestedBuilderBoostFactor, + builderConfig, blockProductionPerformance); } diff --git a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandler.java b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandler.java index 4b78c0c52ad..88b512d36e2 100644 --- a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandler.java +++ b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandler.java @@ -82,6 +82,7 @@ import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockAndState; import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockContainer; import tech.pegasys.teku.spec.datastructures.builder.SignedValidatorRegistration; +import tech.pegasys.teku.spec.datastructures.builder.versions.gloas.BuilderConfig; import tech.pegasys.teku.spec.datastructures.epbs.versions.gloas.ExecutionPayloadBid; import tech.pegasys.teku.spec.datastructures.epbs.versions.gloas.ExecutionPayloadEnvelope; import tech.pegasys.teku.spec.datastructures.epbs.versions.gloas.PayloadAttestationData; @@ -446,13 +447,11 @@ public SafeFuture> createUnsignedBlock( final UInt64 slot, final BLSSignature randaoReveal, final Optional graffiti, - final Optional requestedBuilderBoostFactor) { + final boolean includePayload, + final Optional builderConfig) { return blockProductionBySlotCache .computeIfAbsent( - slot, - __ -> - createUnsignedBlockInternal( - slot, randaoReveal, graffiti, requestedBuilderBoostFactor)) + slot, __ -> createUnsignedBlockInternal(slot, randaoReveal, graffiti, builderConfig)) .whenException( __ -> { // allow further block production attempts for this slot @@ -493,7 +492,7 @@ private SafeFuture> createUnsignedBlockInter final UInt64 slot, final BLSSignature randaoReveal, final Optional graffiti, - final Optional requestedBuilderBoostFactor) { + final Optional builderConfig) { LOG.info("Creating unsigned block for slot {}", slot); performanceTracker.reportBlockProductionAttempt(spec.computeEpochAtSlot(slot)); if (isSyncActive()) { @@ -506,7 +505,7 @@ private SafeFuture> createUnsignedBlockInter blockProductionPreparationContext.blockProductionPerformance.validatorBlockRequested(); return blockProductionPreparationContext - .toBlockProductionContext(spec, slot, randaoReveal, graffiti, requestedBuilderBoostFactor) + .toBlockProductionContext(spec, slot, randaoReveal, graffiti, builderConfig) .thenCompose(this::createBlock) .thenPeek( maybeBlock -> @@ -1235,7 +1234,7 @@ SafeFuture toBlockProductionContext( final UInt64 proposalSlot, final BLSSignature randaoReveal, final Optional graffiti, - final Optional requestedBuilderBoostFactor) { + final Optional builderConfig) { return stateFuture.thenCombine( chainHeadFuture, (state, chainHead) -> @@ -1246,7 +1245,7 @@ SafeFuture toBlockProductionContext( chainHead, randaoReveal, graffiti, - requestedBuilderBoostFactor, + builderConfig, blockProductionPerformance)); } } diff --git a/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/BlockProductionContextTest.java b/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/BlockProductionContextTest.java index a3aa6f63759..3a23ececef6 100644 --- a/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/BlockProductionContextTest.java +++ b/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/BlockProductionContextTest.java @@ -31,12 +31,14 @@ import tech.pegasys.teku.spec.TestSpecInvocationContextProvider; import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockAndState; import tech.pegasys.teku.spec.datastructures.blocks.StateAndBlockSummary; +import tech.pegasys.teku.spec.datastructures.builder.versions.gloas.BuilderConfig; import tech.pegasys.teku.spec.datastructures.forkchoice.ForkChoiceNode; import tech.pegasys.teku.spec.datastructures.forkchoice.ForkChoicePayloadStatus; import tech.pegasys.teku.spec.datastructures.forkchoice.ProtoNodeData; import tech.pegasys.teku.spec.datastructures.forkchoice.ProtoNodeValidationStatus; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; import tech.pegasys.teku.spec.generator.ChainBuilder; +import tech.pegasys.teku.spec.schemas.ApiSchemas; import tech.pegasys.teku.spec.util.DataStructureUtil; import tech.pegasys.teku.storage.client.ChainHead; @@ -60,11 +62,11 @@ void create_shouldDeriveParentRootFromStateAndPreservePayloadStatus() throws Exc final UInt64 proposalSlot = parentBlock.getSlot().plus(1); final BeaconState blockSlotState = spec.processSlots(parentBlock.getState(), proposalSlot); final ChainHead parentChainHead = chainHead(parentBlock, PAYLOAD_STATUS_FULL); - final Optional requestedBuilderBoostFactor = Optional.of(UInt64.valueOf(42)); + final Optional builderConfig = + Optional.of(ApiSchemas.BUILDER_CONFIG_SCHEMA.create(UInt64.valueOf(42))); final BlockProductionContext context = - createBlockProductionContext( - proposalSlot, blockSlotState, parentChainHead, requestedBuilderBoostFactor); + createBlockProductionContext(proposalSlot, blockSlotState, parentChainHead, builderConfig); assertThat(context.proposalSlot()).isEqualTo(proposalSlot); assertThat(context.blockSlotState()).isSameAs(blockSlotState); @@ -76,7 +78,7 @@ void create_shouldDeriveParentRootFromStateAndPreservePayloadStatus() throws Exc assertThat(context.parentPayloadStatus()).isEqualTo(PAYLOAD_STATUS_FULL); assertThat(context.parentExecutionBlockHash()) .isEqualTo(parentChainHead.getExecutionBlockHash()); - assertThat(context.requestedBuilderBoostFactor()).isEqualTo(requestedBuilderBoostFactor); + assertThat(context.builderConfig()).isEqualTo(builderConfig); assertThat(context.blockProductionPerformance()).isSameAs(BlockProductionPerformance.NOOP); } @@ -130,7 +132,7 @@ private BlockProductionContext createBlockProductionContext( final UInt64 proposalSlot, final BeaconState blockSlotState, final ChainHead parentChainHead, - final Optional requestedBuilderBoostFactor) { + final Optional builderConfig) { final BLSSignature randaoReveal = dataStructureUtil.randomSignature(); final Optional graffiti = Optional.of(dataStructureUtil.randomBytes32()); return BlockProductionContext.create( @@ -140,7 +142,7 @@ private BlockProductionContext createBlockProductionContext( parentChainHead, randaoReveal, graffiti, - requestedBuilderBoostFactor, + builderConfig, BlockProductionPerformance.NOOP); } diff --git a/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/BlockProductionTestUtil.java b/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/BlockProductionTestUtil.java index 4f85cf71d73..da76ea75fb9 100644 --- a/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/BlockProductionTestUtil.java +++ b/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/BlockProductionTestUtil.java @@ -22,6 +22,7 @@ import tech.pegasys.teku.spec.datastructures.forkchoice.ForkChoiceNode; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.gloas.BeaconStateGloas; +import tech.pegasys.teku.spec.schemas.ApiSchemas; final class BlockProductionTestUtil { @@ -59,7 +60,7 @@ static BlockProductionContext blockProductionContext( parentExecutionBlockHash(blockSlotState), randaoReveal, graffiti, - requestedBuilderBoostFactor, + requestedBuilderBoostFactor.map(ApiSchemas.BUILDER_CONFIG_SCHEMA::create), blockProductionPerformance); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/builder/versions/gloas/BuilderConfig.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/builder/versions/gloas/BuilderConfig.java new file mode 100644 index 00000000000..865c599f3a8 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/builder/versions/gloas/BuilderConfig.java @@ -0,0 +1,58 @@ +/* + * 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.spec.datastructures.builder.versions.gloas; + +import java.util.List; +import tech.pegasys.teku.infrastructure.ssz.SszList; +import tech.pegasys.teku.infrastructure.ssz.containers.Container3; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; + +public class BuilderConfig + extends Container3> { + + protected BuilderConfig( + final BuilderConfigSchema schema, + final UInt64 minBid, + final UInt64 builderBoostFactor, + final List builders) { + super( + schema, + SszUInt64.of(minBid), + SszUInt64.of(builderBoostFactor), + schema.getBuildersSchema().createFromElements(builders)); + } + + protected BuilderConfig(final BuilderConfigSchema schema, final TreeNode backingNode) { + super(schema, backingNode); + } + + public UInt64 getMinBid() { + return getField0().get(); + } + + public UInt64 getBuilderBoostFactor() { + return getField1().get(); + } + + public SszList getBuilders() { + return getField2(); + } + + @Override + public BuilderConfigSchema getSchema() { + return (BuilderConfigSchema) super.getSchema(); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/builder/versions/gloas/BuilderConfigSchema.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/builder/versions/gloas/BuilderConfigSchema.java new file mode 100644 index 00000000000..159135e3ed2 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/builder/versions/gloas/BuilderConfigSchema.java @@ -0,0 +1,56 @@ +/* + * 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.spec.datastructures.builder.versions.gloas; + +import java.util.List; +import tech.pegasys.teku.infrastructure.ssz.SszList; +import tech.pegasys.teku.infrastructure.ssz.containers.ContainerSchema3; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; +import tech.pegasys.teku.infrastructure.ssz.schema.SszListSchema; +import tech.pegasys.teku.infrastructure.ssz.schema.SszPrimitiveSchemas; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; + +public class BuilderConfigSchema + extends ContainerSchema3> { + + private static final long MAX_BUILDER_ENTRIES = 64; + + public BuilderConfigSchema(final BuilderEntrySchema builderEntrySchema) { + super( + "BuilderConfig", + namedSchema("min_bid", SszPrimitiveSchemas.UINT64_SCHEMA), + namedSchema("builder_boost_factor", SszPrimitiveSchemas.UINT64_SCHEMA), + namedSchema("builders", SszListSchema.create(builderEntrySchema, MAX_BUILDER_ENTRIES))); + } + + public BuilderConfig create( + final UInt64 minBid, final UInt64 builderBoostFactor, final List builders) { + return new BuilderConfig(this, minBid, builderBoostFactor, builders); + } + + public BuilderConfig create(final UInt64 builderBoostFactor) { + return new BuilderConfig(this, UInt64.ZERO, builderBoostFactor, List.of()); + } + + @Override + public BuilderConfig createFromBackingNode(final TreeNode node) { + return new BuilderConfig(this, node); + } + + @SuppressWarnings("unchecked") + public SszListSchema getBuildersSchema() { + return (SszListSchema) getChildSchema(getFieldIndex("builders")); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/builder/versions/gloas/BuilderEntry.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/builder/versions/gloas/BuilderEntry.java new file mode 100644 index 00000000000..84759194eef --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/builder/versions/gloas/BuilderEntry.java @@ -0,0 +1,90 @@ +/* + * 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.spec.datastructures.builder.versions.gloas; + +import java.nio.charset.StandardCharsets; +import java.util.List; +import org.apache.tuweni.bytes.Bytes; +import tech.pegasys.teku.bls.BLSPublicKey; +import tech.pegasys.teku.infrastructure.ssz.SszList; +import tech.pegasys.teku.infrastructure.ssz.collections.SszByteList; +import tech.pegasys.teku.infrastructure.ssz.containers.Container6; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.datastructures.type.SszPublicKey; + +public class BuilderEntry + extends Container6< + BuilderEntry, + SszByteList, + SignedRequestAuth, + SszList, + SszUInt64, + SszUInt64, + SszUInt64> { + + protected BuilderEntry( + final BuilderEntrySchema schema, + final String url, + final SignedRequestAuth auth, + final List builderPubkeys, + final UInt64 maxExecutionPayment, + final UInt64 minBid, + final UInt64 builderBoostFactor) { + super( + schema, + schema.getUrlSchema().fromBytes(Bytes.wrap(url.getBytes(StandardCharsets.UTF_8))), + auth, + schema + .getBuilderPubkeysSchema() + .createFromElements(builderPubkeys.stream().map(SszPublicKey::new).toList()), + SszUInt64.of(maxExecutionPayment), + SszUInt64.of(minBid), + SszUInt64.of(builderBoostFactor)); + } + + protected BuilderEntry(final BuilderEntrySchema schema, final TreeNode backingNode) { + super(schema, backingNode); + } + + public String getUrl() { + return new String(getField0().getBytes().toArrayUnsafe(), StandardCharsets.UTF_8); + } + + public SignedRequestAuth getAuth() { + return getField1(); + } + + public SszList getBuilderPubkeys() { + return getField2(); + } + + public UInt64 getMaxExecutionPayment() { + return getField3().get(); + } + + public UInt64 getMinBid() { + return getField4().get(); + } + + public UInt64 getBuilderBoostFactor() { + return getField5().get(); + } + + @Override + public BuilderEntrySchema getSchema() { + return (BuilderEntrySchema) super.getSchema(); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/builder/versions/gloas/BuilderEntrySchema.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/builder/versions/gloas/BuilderEntrySchema.java new file mode 100644 index 00000000000..e9c2e7e28ab --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/builder/versions/gloas/BuilderEntrySchema.java @@ -0,0 +1,81 @@ +/* + * 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.spec.datastructures.builder.versions.gloas; + +import java.util.List; +import tech.pegasys.teku.bls.BLSPublicKey; +import tech.pegasys.teku.infrastructure.ssz.SszList; +import tech.pegasys.teku.infrastructure.ssz.collections.SszByteList; +import tech.pegasys.teku.infrastructure.ssz.containers.ContainerSchema6; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; +import tech.pegasys.teku.infrastructure.ssz.schema.SszListSchema; +import tech.pegasys.teku.infrastructure.ssz.schema.SszPrimitiveSchemas; +import tech.pegasys.teku.infrastructure.ssz.schema.collections.SszByteListSchema; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.datastructures.type.SszPublicKey; +import tech.pegasys.teku.spec.datastructures.type.SszPublicKeySchema; +import tech.pegasys.teku.spec.schemas.ApiSchemas; + +public class BuilderEntrySchema + extends ContainerSchema6< + BuilderEntry, + SszByteList, + SignedRequestAuth, + SszList, + SszUInt64, + SszUInt64, + SszUInt64> { + + private static final long MAX_BUILDER_URL_SIZE = 2048; + private static final long MAX_BUILDER_PUBKEYS = 64; + + public BuilderEntrySchema() { + super( + "BuilderEntry", + namedSchema("url", SszByteListSchema.create(MAX_BUILDER_URL_SIZE)), + namedSchema("auth", ApiSchemas.SIGNED_REQUEST_AUTH_SCHEMA), + namedSchema( + "builder_pubkeys", + SszListSchema.create(SszPublicKeySchema.INSTANCE, MAX_BUILDER_PUBKEYS)), + namedSchema("max_execution_payment", SszPrimitiveSchemas.UINT64_SCHEMA), + namedSchema("min_bid", SszPrimitiveSchemas.UINT64_SCHEMA), + namedSchema("builder_boost_factor", SszPrimitiveSchemas.UINT64_SCHEMA)); + } + + public BuilderEntry create( + final String url, + final SignedRequestAuth auth, + final List builderPubkeys, + final UInt64 maxExecutionPayment, + final UInt64 minBid, + final UInt64 builderBoostFactor) { + return new BuilderEntry( + this, url, auth, builderPubkeys, maxExecutionPayment, minBid, builderBoostFactor); + } + + @Override + public BuilderEntry createFromBackingNode(final TreeNode node) { + return new BuilderEntry(this, node); + } + + public SszByteListSchema getUrlSchema() { + return (SszByteListSchema) getChildSchema(getFieldIndex("url")); + } + + @SuppressWarnings("unchecked") + public SszListSchema getBuilderPubkeysSchema() { + return (SszListSchema) getChildSchema(getFieldIndex("builder_pubkeys")); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/ApiSchemas.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/ApiSchemas.java index a2e6efc2b65..0b15794b2ba 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/ApiSchemas.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/ApiSchemas.java @@ -17,6 +17,8 @@ import tech.pegasys.teku.spec.datastructures.builder.SignedValidatorRegistrationSchema; import tech.pegasys.teku.spec.datastructures.builder.SignedValidatorRegistrationsSchema; import tech.pegasys.teku.spec.datastructures.builder.ValidatorRegistrationSchema; +import tech.pegasys.teku.spec.datastructures.builder.versions.gloas.BuilderConfigSchema; +import tech.pegasys.teku.spec.datastructures.builder.versions.gloas.BuilderEntrySchema; import tech.pegasys.teku.spec.datastructures.builder.versions.gloas.BuilderPreferencesRequestSchema; import tech.pegasys.teku.spec.datastructures.builder.versions.gloas.BuilderPreferencesSchema; import tech.pegasys.teku.spec.datastructures.builder.versions.gloas.RequestAuthSchema; @@ -48,4 +50,10 @@ public class ApiSchemas { public static final BuilderPreferencesRequestSchema BUILDER_PREFERENCES_REQUEST_SCHEMA = new BuilderPreferencesRequestSchema(BUILDER_PREFERENCES_SCHEMA, SIGNED_REQUEST_AUTH_SCHEMA); + + // https://github.com/ethereum/beacon-APIs/pull/630/ + public static final BuilderEntrySchema BUILDER_ENTRY_SCHEMA = new BuilderEntrySchema(); + + public static final BuilderConfigSchema BUILDER_CONFIG_SCHEMA = + new BuilderConfigSchema(BUILDER_ENTRY_SCHEMA); } diff --git a/validator/api/src/main/java/tech/pegasys/teku/validator/api/ValidatorApiChannel.java b/validator/api/src/main/java/tech/pegasys/teku/validator/api/ValidatorApiChannel.java index b138d8051e0..fe02878b8d1 100644 --- a/validator/api/src/main/java/tech/pegasys/teku/validator/api/ValidatorApiChannel.java +++ b/validator/api/src/main/java/tech/pegasys/teku/validator/api/ValidatorApiChannel.java @@ -38,6 +38,7 @@ import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockContainer; import tech.pegasys.teku.spec.datastructures.builder.SignedValidatorRegistration; +import tech.pegasys.teku.spec.datastructures.builder.versions.gloas.BuilderConfig; import tech.pegasys.teku.spec.datastructures.epbs.versions.gloas.ExecutionPayloadBid; import tech.pegasys.teku.spec.datastructures.epbs.versions.gloas.ExecutionPayloadEnvelope; import tech.pegasys.teku.spec.datastructures.epbs.versions.gloas.PayloadAttestationData; @@ -58,6 +59,7 @@ import tech.pegasys.teku.spec.datastructures.validator.BeaconPreparableProposer; import tech.pegasys.teku.spec.datastructures.validator.BroadcastValidationLevel; import tech.pegasys.teku.spec.datastructures.validator.SubnetSubscription; +import tech.pegasys.teku.spec.schemas.ApiSchemas; public interface ValidatorApiChannel extends BuilderApiChannel, ChannelInterface { ValidatorApiChannel NOOP = @@ -113,7 +115,8 @@ public SafeFuture> createUnsignedBlock( final UInt64 slot, final BLSSignature randaoReveal, final Optional graffiti, - final Optional requestedBuilderBoostFactor) { + final boolean includePayload, + final Optional builderConfig) { return SafeFuture.completedFuture(Optional.empty()); } @@ -295,18 +298,32 @@ SafeFuture> getAttestationDuties( SafeFuture> getSyncCommitteeDuties( UInt64 epoch, IntCollection validatorIndices); - SafeFuture> getProposerDuties( - UInt64 epoch, final boolean isFuluCompatible); + SafeFuture> getProposerDuties(UInt64 epoch, boolean isFuluCompatible); SafeFuture> getPtcDuties(UInt64 epoch, IntCollection validatorIndices); SafeFuture> getPeerCount(); + // used to maintain backwards compatibility with block v3 + default SafeFuture> createUnsignedBlock( + final UInt64 slot, + final BLSSignature randaoReveal, + final Optional graffiti, + final Optional requestedBuilderBoostFactor) { + return createUnsignedBlock( + slot, + randaoReveal, + graffiti, + false, + requestedBuilderBoostFactor.map(ApiSchemas.BUILDER_CONFIG_SCHEMA::create)); + } + SafeFuture> createUnsignedBlock( UInt64 slot, BLSSignature randaoReveal, Optional graffiti, - Optional requestedBuilderBoostFactor); + boolean includePayload, + Optional builderConfig); SafeFuture> createAttestationData(UInt64 slot, int committeeIndex); diff --git a/validator/beaconnode/src/main/java/tech/pegasys/teku/validator/beaconnode/metrics/MetricRecordingValidatorApiChannel.java b/validator/beaconnode/src/main/java/tech/pegasys/teku/validator/beaconnode/metrics/MetricRecordingValidatorApiChannel.java index 2a20d31fc0b..bad282d4b40 100644 --- a/validator/beaconnode/src/main/java/tech/pegasys/teku/validator/beaconnode/metrics/MetricRecordingValidatorApiChannel.java +++ b/validator/beaconnode/src/main/java/tech/pegasys/teku/validator/beaconnode/metrics/MetricRecordingValidatorApiChannel.java @@ -47,6 +47,7 @@ import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockContainer; import tech.pegasys.teku.spec.datastructures.builder.SignedValidatorRegistration; +import tech.pegasys.teku.spec.datastructures.builder.versions.gloas.BuilderConfig; import tech.pegasys.teku.spec.datastructures.epbs.versions.gloas.ExecutionPayloadBid; import tech.pegasys.teku.spec.datastructures.epbs.versions.gloas.ExecutionPayloadEnvelope; import tech.pegasys.teku.spec.datastructures.epbs.versions.gloas.PayloadAttestationData; @@ -159,9 +160,10 @@ public SafeFuture> createUnsignedBlock( final UInt64 slot, final BLSSignature randaoReveal, final Optional graffiti, - final Optional requestedBuilderBoostFactor) { + final boolean includePayload, + final Optional builderConfig) { return countOptionalDataRequest( - delegate.createUnsignedBlock(slot, randaoReveal, graffiti, requestedBuilderBoostFactor), + delegate.createUnsignedBlock(slot, randaoReveal, graffiti, includePayload, builderConfig), BeaconNodeRequestLabels.CREATE_UNSIGNED_BLOCK_METHOD); } diff --git a/validator/beaconnode/src/test/java/tech/pegasys/teku/validator/beaconnode/metrics/MetricRecordingValidatorApiChannelTest.java b/validator/beaconnode/src/test/java/tech/pegasys/teku/validator/beaconnode/metrics/MetricRecordingValidatorApiChannelTest.java index a703d5b10d4..da6469316b5 100644 --- a/validator/beaconnode/src/test/java/tech/pegasys/teku/validator/beaconnode/metrics/MetricRecordingValidatorApiChannelTest.java +++ b/validator/beaconnode/src/test/java/tech/pegasys/teku/validator/beaconnode/metrics/MetricRecordingValidatorApiChannelTest.java @@ -258,7 +258,8 @@ public static Stream getDataRequestArguments() { requestDataTest( "createUnsignedBlock", channel -> - channel.createUnsignedBlock(slot, signature, Optional.empty(), Optional.empty()), + channel.createUnsignedBlock( + slot, signature, Optional.empty(), false, Optional.empty()), BeaconNodeRequestLabels.CREATE_UNSIGNED_BLOCK_METHOD, dataStructureUtil.randomBlockContainerAndMetaData(slot)), requestDataTest( diff --git a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/FailoverValidatorApiHandler.java b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/FailoverValidatorApiHandler.java index 053030afeb1..8da836866ac 100644 --- a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/FailoverValidatorApiHandler.java +++ b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/FailoverValidatorApiHandler.java @@ -50,6 +50,7 @@ import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockContainer; import tech.pegasys.teku.spec.datastructures.blocks.SlotAndBlockRoot; import tech.pegasys.teku.spec.datastructures.builder.SignedValidatorRegistration; +import tech.pegasys.teku.spec.datastructures.builder.versions.gloas.BuilderConfig; import tech.pegasys.teku.spec.datastructures.epbs.BlockRootAndBuilderIndex; import tech.pegasys.teku.spec.datastructures.epbs.SlotAndBuilderIndex; import tech.pegasys.teku.spec.datastructures.epbs.versions.gloas.ExecutionPayloadBid; @@ -187,11 +188,12 @@ public SafeFuture> createUnsignedBlock( final UInt64 slot, final BLSSignature randaoReveal, final Optional graffiti, - final Optional requestedBuilderBoostFactor) { + final boolean includePayload, + final Optional builderConfig) { final ValidatorApiChannelRequest> request = apiChannel -> apiChannel - .createUnsignedBlock(slot, randaoReveal, graffiti, requestedBuilderBoostFactor) + .createUnsignedBlock(slot, randaoReveal, graffiti, includePayload, builderConfig) .thenPeek( blockContainerAndMetaData -> { if (!failoverDelegates.isEmpty() diff --git a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/RemoteValidatorApiHandler.java b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/RemoteValidatorApiHandler.java index 329b34556ef..95dcaad1b6f 100644 --- a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/RemoteValidatorApiHandler.java +++ b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/RemoteValidatorApiHandler.java @@ -54,6 +54,7 @@ import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockContainer; import tech.pegasys.teku.spec.datastructures.builder.SignedValidatorRegistration; +import tech.pegasys.teku.spec.datastructures.builder.versions.gloas.BuilderConfig; import tech.pegasys.teku.spec.datastructures.epbs.versions.gloas.ExecutionPayloadBid; import tech.pegasys.teku.spec.datastructures.epbs.versions.gloas.ExecutionPayloadEnvelope; import tech.pegasys.teku.spec.datastructures.epbs.versions.gloas.PayloadAttestationData; @@ -251,11 +252,15 @@ public SafeFuture> createUnsignedBlock( final UInt64 slot, final BLSSignature randaoReveal, final Optional graffiti, - final Optional requestedBuilderBoostFactor) { + final boolean includePayload, + final Optional builderConfig) { return sendRequest( () -> typeDefClient.createUnsignedBlock( - slot, randaoReveal, graffiti, requestedBuilderBoostFactor)); + slot, + randaoReveal, + graffiti, + builderConfig.map(BuilderConfig::getBuilderBoostFactor))); } @Override diff --git a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/sentry/SentryValidatorApiChannel.java b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/sentry/SentryValidatorApiChannel.java index 9e787ab5576..610a6483aa0 100644 --- a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/sentry/SentryValidatorApiChannel.java +++ b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/sentry/SentryValidatorApiChannel.java @@ -37,6 +37,7 @@ import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockContainer; import tech.pegasys.teku.spec.datastructures.builder.SignedValidatorRegistration; +import tech.pegasys.teku.spec.datastructures.builder.versions.gloas.BuilderConfig; import tech.pegasys.teku.spec.datastructures.epbs.versions.gloas.ExecutionPayloadBid; import tech.pegasys.teku.spec.datastructures.epbs.versions.gloas.ExecutionPayloadEnvelope; import tech.pegasys.teku.spec.datastructures.epbs.versions.gloas.PayloadAttestationData; @@ -129,10 +130,11 @@ public SafeFuture> createUnsignedBlock( final UInt64 slot, final BLSSignature randaoReveal, final Optional graffiti, - final Optional requestedBuilderBoostFactor) { + final boolean includePayload, + final Optional builderConfig) { return blockHandlerChannel .orElse(dutiesProviderChannel) - .createUnsignedBlock(slot, randaoReveal, graffiti, requestedBuilderBoostFactor); + .createUnsignedBlock(slot, randaoReveal, graffiti, includePayload, builderConfig); } @Override diff --git a/validator/remote/src/test/java/tech/pegasys/teku/validator/remote/FailoverValidatorApiHandlerTest.java b/validator/remote/src/test/java/tech/pegasys/teku/validator/remote/FailoverValidatorApiHandlerTest.java index 624aacbaec8..3144fe46d7b 100644 --- a/validator/remote/src/test/java/tech/pegasys/teku/validator/remote/FailoverValidatorApiHandlerTest.java +++ b/validator/remote/src/test/java/tech/pegasys/teku/validator/remote/FailoverValidatorApiHandlerTest.java @@ -589,7 +589,8 @@ public void publishesBlindedBlockOnlyToTheBeaconNodeWhichCreatedIt() { final ValidatorApiChannelRequest> creationRequest = apiChannel -> - apiChannel.createUnsignedBlock(slot, randaoReveal, Optional.empty(), Optional.empty()); + apiChannel.createUnsignedBlock( + slot, randaoReveal, Optional.empty(), false, Optional.empty()); setupFailures(creationRequest, primaryApiChannel); setupSuccesses(creationRequest, Optional.of(blindedBlock), failoverApiChannel1); @@ -738,7 +739,7 @@ private static Stream getRequestsUsingFailover() { "createUnsignedBlock", apiChannel -> apiChannel.createUnsignedBlock( - slot, randaoReveal, Optional.empty(), Optional.empty()), + slot, randaoReveal, Optional.empty(), false, Optional.empty()), BeaconNodeRequestLabels.CREATE_UNSIGNED_BLOCK_METHOD, Optional.of(mock(BlockContainerAndMetaData.class))), getArguments( diff --git a/validator/remote/src/test/java/tech/pegasys/teku/validator/remote/sentry/SentryValidatorApiChannelTest.java b/validator/remote/src/test/java/tech/pegasys/teku/validator/remote/sentry/SentryValidatorApiChannelTest.java index 040dc1b0bb0..dad767d5f19 100644 --- a/validator/remote/src/test/java/tech/pegasys/teku/validator/remote/sentry/SentryValidatorApiChannelTest.java +++ b/validator/remote/src/test/java/tech/pegasys/teku/validator/remote/sentry/SentryValidatorApiChannelTest.java @@ -36,6 +36,7 @@ import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.builder.SignedValidatorRegistration; import tech.pegasys.teku.spec.datastructures.validator.BroadcastValidationLevel; +import tech.pegasys.teku.spec.schemas.ApiSchemas; import tech.pegasys.teku.spec.util.DataStructureUtil; import tech.pegasys.teku.validator.api.ValidatorApiChannel; @@ -128,7 +129,11 @@ void createUnsignedBlockShouldUseBlockHandlerChannelWhenAvailable() { verify(blockHandlerChannel) .createUnsignedBlock( - eq(UInt64.ZERO), eq(BLSSignature.empty()), eq(Optional.empty()), eq(Optional.of(ONE))); + eq(UInt64.ZERO), + eq(BLSSignature.empty()), + eq(Optional.empty()), + eq(false), + eq(Optional.of(ApiSchemas.BUILDER_CONFIG_SCHEMA.create(ONE)))); verifyNoInteractions(dutiesProviderChannel); verifyNoInteractions(attestationPublisherChannel); } @@ -144,7 +149,11 @@ void createUnsignedBlockShouldFallbackToDutiesProviderChannel() { verify(dutiesProviderChannel) .createUnsignedBlock( - eq(UInt64.ZERO), eq(BLSSignature.empty()), eq(Optional.empty()), eq(Optional.of(ONE))); + eq(UInt64.ZERO), + eq(BLSSignature.empty()), + eq(Optional.empty()), + eq(false), + eq(Optional.of(ApiSchemas.BUILDER_CONFIG_SCHEMA.create(ONE)))); verifyNoInteractions(blockHandlerChannel); verifyNoInteractions(attestationPublisherChannel); }