From bc82cf0636caf88648d016a148d95169244e48a5 Mon Sep 17 00:00:00 2001 From: Abhivansh <31abhivanshj@gmail.com> Date: Sat, 29 Aug 2026 16:19:23 +0530 Subject: [PATCH 1/7] feat: add light client update with context --- .../LightClientUpdateWithContext.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/metadata/LightClientUpdateWithContext.java diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/metadata/LightClientUpdateWithContext.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/metadata/LightClientUpdateWithContext.java new file mode 100644 index 00000000000..b3a129e688e --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/metadata/LightClientUpdateWithContext.java @@ -0,0 +1,19 @@ +/* + * 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.metadata; + +import tech.pegasys.teku.infrastructure.bytes.Bytes4; +import tech.pegasys.teku.spec.datastructures.lightclient.LightClientUpdate; + +public record LightClientUpdateWithContext(Bytes4 context, LightClientUpdate update) {} From fa72249787139776cb0a0d2825383b85e6028fa4 Mon Sep 17 00:00:00 2001 From: Abhivansh <31abhivanshj@gmail.com> Date: Sat, 29 Aug 2026 16:20:24 +0530 Subject: [PATCH 2/7] chore: remove LightClientUpdateResponse --- .../LightClientUpdateResponse.java | 36 --------------- .../LightClientUpdateResponseSchema.java | 45 ------------------- .../spec/schemas/SchemaDefinitionsAltair.java | 7 --- .../teku/spec/util/DataStructureUtil.java | 12 ++--- 4 files changed, 3 insertions(+), 97 deletions(-) delete mode 100644 ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/lightclient/LightClientUpdateResponse.java delete mode 100644 ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/lightclient/LightClientUpdateResponseSchema.java diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/lightclient/LightClientUpdateResponse.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/lightclient/LightClientUpdateResponse.java deleted file mode 100644 index 14995686e76..00000000000 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/lightclient/LightClientUpdateResponse.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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.lightclient; - -import tech.pegasys.teku.infrastructure.ssz.containers.Container3; -import tech.pegasys.teku.infrastructure.ssz.primitive.SszBytes4; -import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; -import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; - -public class LightClientUpdateResponse - extends Container3 { - - public LightClientUpdateResponse( - final LightClientUpdateResponseSchema schema, - final SszUInt64 responseChunkLen, - final SszBytes4 context, - final LightClientUpdate payload) { - super(schema, responseChunkLen, context, payload); - } - - protected LightClientUpdateResponse( - final LightClientUpdateResponseSchema type, final TreeNode backingNode) { - super(type, backingNode); - } -} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/lightclient/LightClientUpdateResponseSchema.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/lightclient/LightClientUpdateResponseSchema.java deleted file mode 100644 index 7b725a5c996..00000000000 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/lightclient/LightClientUpdateResponseSchema.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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.lightclient; - -import static tech.pegasys.teku.spec.schemas.registry.SchemaTypes.LIGHT_CLIENT_UPDATE_SCHEMA; - -import tech.pegasys.teku.infrastructure.ssz.containers.ContainerSchema3; -import tech.pegasys.teku.infrastructure.ssz.primitive.SszBytes4; -import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; -import tech.pegasys.teku.infrastructure.ssz.schema.SszPrimitiveSchemas; -import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; -import tech.pegasys.teku.spec.schemas.registry.SchemaRegistry; - -public class LightClientUpdateResponseSchema - extends ContainerSchema3 { - - public LightClientUpdateResponseSchema(final SchemaRegistry registry) { - super( - "LightClientUpdateResponse", - namedSchema("response_chunk_len", SszPrimitiveSchemas.UINT64_SCHEMA), - namedSchema("context", SszPrimitiveSchemas.BYTES4_SCHEMA), - namedSchema("payload", registry.get(LIGHT_CLIENT_UPDATE_SCHEMA))); - } - - public LightClientUpdateResponse create( - final SszUInt64 responseChunkLen, final SszBytes4 context, final LightClientUpdate payload) { - return new LightClientUpdateResponse(this, responseChunkLen, context, payload); - } - - @Override - public LightClientUpdateResponse createFromBackingNode(final TreeNode node) { - return new LightClientUpdateResponse(this, node); - } -} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsAltair.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsAltair.java index a587854f4af..dd81eba2c9e 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsAltair.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsAltair.java @@ -43,7 +43,6 @@ import tech.pegasys.teku.spec.datastructures.lightclient.LightClientFinalityUpdateSchema; import tech.pegasys.teku.spec.datastructures.lightclient.LightClientHeaderSchema; import tech.pegasys.teku.spec.datastructures.lightclient.LightClientOptimisticUpdateSchema; -import tech.pegasys.teku.spec.datastructures.lightclient.LightClientUpdateResponseSchema; import tech.pegasys.teku.spec.datastructures.lightclient.LightClientUpdateSchema; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.metadata.MetadataMessageSchema; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.status.StatusMessageSchema; @@ -83,7 +82,6 @@ public class SchemaDefinitionsAltair extends AbstractSchemaDefinitions { private final LightClientHeaderSchema lightClientHeaderSchema; private final LightClientBootstrapSchema lightClientBootstrapSchema; private final LightClientUpdateSchema lightClientUpdateSchema; - private final LightClientUpdateResponseSchema lightClientUpdateResponseSchema; private final LightClientFinalityUpdateSchema lightClientFinalityUpdateSchema; private final LightClientOptimisticUpdateSchema lightClientOptimisticUpdateSchema; @@ -110,7 +108,6 @@ public SchemaDefinitionsAltair(final SchemaRegistry schemaRegistry) { this.lightClientHeaderSchema = schemaRegistry.get(LIGHT_CLIENT_HEADER_SCHEMA); this.lightClientBootstrapSchema = schemaRegistry.get(LIGHT_CLIENT_BOOTSTRAP_SCHEMA); this.lightClientUpdateSchema = schemaRegistry.get(LIGHT_CLIENT_UPDATE_SCHEMA); - this.lightClientUpdateResponseSchema = new LightClientUpdateResponseSchema(schemaRegistry); this.lightClientFinalityUpdateSchema = schemaRegistry.get(LIGHT_CLIENT_FINALITY_UPDATE_SCHEMA); this.lightClientOptimisticUpdateSchema = schemaRegistry.get(LIGHT_CLIENT_OPTIMISTIC_UPDATE_SCHEMA); @@ -258,10 +255,6 @@ public LightClientUpdateSchema getLightClientUpdateSchema() { return lightClientUpdateSchema; } - public LightClientUpdateResponseSchema getLightClientUpdateResponseSchema() { - return lightClientUpdateResponseSchema; - } - public LightClientFinalityUpdateSchema getLightClientFinalityUpdateSchema() { return lightClientFinalityUpdateSchema; } diff --git a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/DataStructureUtil.java b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/DataStructureUtil.java index 66bf31cbaa0..948fe2cc791 100644 --- a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/DataStructureUtil.java +++ b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/DataStructureUtil.java @@ -77,7 +77,6 @@ import tech.pegasys.teku.infrastructure.ssz.collections.SszUInt64Vector; import tech.pegasys.teku.infrastructure.ssz.primitive.SszByte; import tech.pegasys.teku.infrastructure.ssz.primitive.SszBytes32; -import tech.pegasys.teku.infrastructure.ssz.primitive.SszBytes4; import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; import tech.pegasys.teku.infrastructure.ssz.schema.SszListSchema; import tech.pegasys.teku.infrastructure.ssz.schema.SszVectorSchema; @@ -198,12 +197,11 @@ import tech.pegasys.teku.spec.datastructures.lightclient.LightClientOptimisticUpdate; import tech.pegasys.teku.spec.datastructures.lightclient.LightClientOptimisticUpdateSchema; import tech.pegasys.teku.spec.datastructures.lightclient.LightClientUpdate; -import tech.pegasys.teku.spec.datastructures.lightclient.LightClientUpdateResponse; -import tech.pegasys.teku.spec.datastructures.lightclient.LightClientUpdateResponseSchema; import tech.pegasys.teku.spec.datastructures.lightclient.LightClientUpdateSchema; import tech.pegasys.teku.spec.datastructures.lightclient.versions.capella.LightClientHeaderSchemaCapella; import tech.pegasys.teku.spec.datastructures.lightclient.versions.gloas.LightClientHeaderSchemaGloas; import tech.pegasys.teku.spec.datastructures.metadata.BlockContainerAndMetaData; +import tech.pegasys.teku.spec.datastructures.metadata.LightClientUpdateWithContext; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.BlobIdentifier; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.EnrForkId; import tech.pegasys.teku.spec.datastructures.operations.AggregateAndProof; @@ -2546,12 +2544,8 @@ private LightClientHeader lightClientHeaderAtSlot(final UInt64 slot) { .create(randomBeaconBlockHeader(slot, UInt64.ZERO)); } - public LightClientUpdateResponse randomLightClientUpdateResponse(final UInt64 slot) { - final LightClientUpdateResponseSchema schema = - getAltairSchemaDefinitions(slot).getLightClientUpdateResponseSchema(); - - return schema.create( - SszUInt64.of(randomUInt64()), SszBytes4.of(randomBytes4()), randomLightClientUpdate(slot)); + public LightClientUpdateWithContext randomLightClientUpdateWithContext(final UInt64 slot) { + return new LightClientUpdateWithContext(randomBytes4(), randomLightClientUpdate(slot)); } public Withdrawal randomWithdrawal() { From c82c408e75fdee96a2ea2b3d3ba6da05c7c5e806 Mon Sep 17 00:00:00 2001 From: Abhivansh <31abhivanshj@gmail.com> Date: Sat, 29 Aug 2026 16:21:04 +0530 Subject: [PATCH 3/7] feat: implement GetLightClientUpdatesByRange --- .../addon/LightClientRestApiBuilderAddon.java | 2 +- .../GetLightClientUpdatesByRange.java | 98 +++++++++++++------ .../pegasys/teku/api/ChainDataProvider.java | 17 +++- .../teku/spec/constants/NetworkConstants.java | 2 + 4 files changed, 88 insertions(+), 31 deletions(-) diff --git a/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/addon/LightClientRestApiBuilderAddon.java b/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/addon/LightClientRestApiBuilderAddon.java index 9c61854f962..7de028ef592 100644 --- a/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/addon/LightClientRestApiBuilderAddon.java +++ b/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/addon/LightClientRestApiBuilderAddon.java @@ -45,6 +45,6 @@ public boolean isEnabled() { public RestApiBuilder apply(final RestApiBuilder builder) { return builder .endpoint(new GetLightClientBootstrap(dataProvider, schemaCache)) - .endpoint(new GetLightClientUpdatesByRange(schemaCache)); + .endpoint(new GetLightClientUpdatesByRange(dataProvider, schemaCache)); } } diff --git a/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v1/beacon/lightclient/GetLightClientUpdatesByRange.java b/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v1/beacon/lightclient/GetLightClientUpdatesByRange.java index 9361bfb142d..b4332753a8e 100644 --- a/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v1/beacon/lightclient/GetLightClientUpdatesByRange.java +++ b/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v1/beacon/lightclient/GetLightClientUpdatesByRange.java @@ -15,80 +15,122 @@ import static tech.pegasys.teku.beaconrestapi.BeaconRestApiTypes.COUNT_PARAMETER; import static tech.pegasys.teku.beaconrestapi.BeaconRestApiTypes.START_PERIOD_PARAMETER; +import static tech.pegasys.teku.beaconrestapi.handlers.v1.beacon.MilestoneDependentTypesUtil.getMultipleSchemaDefinitionFromMilestone; import static tech.pegasys.teku.ethereum.json.types.EthereumTypes.MILESTONE_TYPE; import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_OK; import static tech.pegasys.teku.infrastructure.http.RestApiConstants.TAG_BEACON; -import static tech.pegasys.teku.infrastructure.http.RestApiConstants.TAG_EXPERIMENTAL; +import static tech.pegasys.teku.spec.constants.NetworkConstants.MAX_REQUEST_LIGHT_CLIENT_UPDATES; import com.fasterxml.jackson.core.JsonProcessingException; +import java.nio.ByteOrder; import java.util.Collections; import java.util.List; +import org.apache.tuweni.bytes.Bytes; +import tech.pegasys.teku.api.ChainDataProvider; +import tech.pegasys.teku.api.DataProvider; +import tech.pegasys.teku.beaconrestapi.handlers.v1.beacon.MilestoneDependentTypesUtil; +import tech.pegasys.teku.infrastructure.bytes.Bytes4; import tech.pegasys.teku.infrastructure.json.types.SerializableTypeDefinition; 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.infrastructure.restapi.openapi.response.JsonResponseContentTypeDefinition; import tech.pegasys.teku.infrastructure.restapi.openapi.response.OctetStreamResponseContentTypeDefinition; import tech.pegasys.teku.infrastructure.restapi.openapi.response.ResponseContentTypeDefinition; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.SpecMilestone; import tech.pegasys.teku.spec.datastructures.lightclient.LightClientUpdate; -import tech.pegasys.teku.spec.datastructures.lightclient.LightClientUpdateResponse; -import tech.pegasys.teku.spec.datastructures.metadata.ObjectAndMetaData; +import tech.pegasys.teku.spec.datastructures.metadata.LightClientUpdateWithContext; import tech.pegasys.teku.spec.schemas.SchemaDefinitionCache; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsAltair; public class GetLightClientUpdatesByRange extends RestApiEndpoint { public static final String ROUTE = "/eth/v1/beacon/light_client/updates"; - public GetLightClientUpdatesByRange(final SchemaDefinitionCache schemaDefinitionCache) { + private final ChainDataProvider chainDataProvider; + + public GetLightClientUpdatesByRange( + final DataProvider provider, final SchemaDefinitionCache schemaDefinitionCache) { + this(provider.getChainDataProvider(), schemaDefinitionCache); + } + + public GetLightClientUpdatesByRange( + final ChainDataProvider chainDataProvider, + final SchemaDefinitionCache schemaDefinitionCache) { super( EndpointMetadata.get(ROUTE) .operationId("getLightClientUpdatesByRange") .summary("Get `LightClientUpdate` instances in a requested sync committee period range") .description( - "Requests the [`LightClientUpdate`](https://github.com/ethereum/consensus-specs/blob/v1.2.0-rc.3/specs/altair/light-client/sync-protocol.md#lightclientupdate) instances in the sync committee period range `[start_period, start_period + count)`, leading up to the current head sync committee period as selected by fork choice. Depending on the `Accept` header they can be returned either as JSON or SSZ-serialized bytes.") - .tags(TAG_BEACON, TAG_EXPERIMENTAL) + "Requests the `LightClientUpdate` instances in the sync committee period range `[start_period, start_period + count)`, leading up to the current head sync committee period as selected by fork choice. Depending on the `Accept` header they can be returned either as JSON or SSZ-serialized bytes.") + .tags(TAG_BEACON) .queryParamRequired(START_PERIOD_PARAMETER) .queryParamRequired(COUNT_PARAMETER) .response( SC_OK, "Request successful", - List.of(getJsonResponseType(schemaDefinitionCache), getSszResponseType())) + getJsonResponseType(schemaDefinitionCache), + getSszResponseType()) .withNotAcceptableResponse() - .withNotImplementedResponse() + .withChainDataResponses() .build()); + this.chainDataProvider = chainDataProvider; } @Override public void handleRequest(final RestApiRequest request) throws JsonProcessingException { - request.respondError(501, "Not implemented"); + final UInt64 startPeriod = request.getQueryParameter(START_PERIOD_PARAMETER); + final int count = + request.getQueryParameter(COUNT_PARAMETER).min(MAX_REQUEST_LIGHT_CLIENT_UPDATES).intValue(); + + request.respondOk(chainDataProvider.getBestLightClientUpdates(startPeriod, count)); } - private static ResponseContentTypeDefinition>> - getJsonResponseType(final SchemaDefinitionCache schemaDefinitionCache) { + private static SerializableTypeDefinition> getJsonResponseType( + final SchemaDefinitionCache schemaDefinitionCache) { final SerializableTypeDefinition lightClientUpdateType = - SchemaDefinitionsAltair.required( - schemaDefinitionCache.getSchemaDefinition(SpecMilestone.ALTAIR)) - .getLightClientUpdateSchema() - .getJsonTypeDefinition(); + getMultipleSchemaDefinitionFromMilestone( + schemaDefinitionCache, + "LightClientUpdate", + List.of( + new MilestoneDependentTypesUtil.ConditionalSchemaGetter<>( + (update, milestone) -> + milestoneAtAttestedSlot(schemaDefinitionCache, update).equals(milestone), + SpecMilestone.ALTAIR, + schemaDefinitions -> + SchemaDefinitionsAltair.required(schemaDefinitions) + .getLightClientUpdateSchema()))); - final SerializableTypeDefinition> - lightClientUpdateObjectType = - SerializableTypeDefinition.>object() - .withField("version", MILESTONE_TYPE, ObjectAndMetaData::getMilestone) - .withField("data", lightClientUpdateType, ObjectAndMetaData::getData) - .build(); + return SerializableTypeDefinition.listOf( + SerializableTypeDefinition.object() + .withField( + "version", + MILESTONE_TYPE, + updateWithContext -> + milestoneAtAttestedSlot(schemaDefinitionCache, updateWithContext.update())) + .withField("data", lightClientUpdateType, LightClientUpdateWithContext::update) + .build()); + } - return new JsonResponseContentTypeDefinition<>( - SerializableTypeDefinition.listOf(lightClientUpdateObjectType)); + private static SpecMilestone milestoneAtAttestedSlot( + final SchemaDefinitionCache schemaDefinitionCache, final LightClientUpdate update) { + return schemaDefinitionCache.milestoneAtSlot(update.getAttestedHeader().getBeacon().getSlot()); } - private static ResponseContentTypeDefinition> + private static ResponseContentTypeDefinition> getSszResponseType() { - OctetStreamResponseContentTypeDefinition.OctetStreamSerializer> + OctetStreamResponseContentTypeDefinition.OctetStreamSerializer< + List> serializer = - (data, out) -> - data.stream().forEachOrdered(lcuResponse -> lcuResponse.sszSerialize(out)); + (data, out) -> { + for (final LightClientUpdateWithContext updateWithContext : data) { + final Bytes payload = updateWithContext.update().sszSerialize(); + out.write( + Bytes.ofUnsignedLong(Bytes4.SIZE + payload.size(), ByteOrder.LITTLE_ENDIAN) + .toArrayUnsafe()); + out.write(updateWithContext.context().getWrappedBytes().toArrayUnsafe()); + out.write(payload.toArrayUnsafe()); + } + }; return new OctetStreamResponseContentTypeDefinition<>(serializer, __ -> Collections.emptyMap()); } diff --git a/data/provider/src/main/java/tech/pegasys/teku/api/ChainDataProvider.java b/data/provider/src/main/java/tech/pegasys/teku/api/ChainDataProvider.java index f60e7c11c0b..7d13c237368 100644 --- a/data/provider/src/main/java/tech/pegasys/teku/api/ChainDataProvider.java +++ b/data/provider/src/main/java/tech/pegasys/teku/api/ChainDataProvider.java @@ -82,6 +82,7 @@ import tech.pegasys.teku.spec.datastructures.metadata.BlockAndMetaData; import tech.pegasys.teku.spec.datastructures.metadata.DataColumnSidecarsAndMetaData; import tech.pegasys.teku.spec.datastructures.metadata.ExecutionPayloadAndMetaData; +import tech.pegasys.teku.spec.datastructures.metadata.LightClientUpdateWithContext; import tech.pegasys.teku.spec.datastructures.metadata.ObjectAndMetaData; import tech.pegasys.teku.spec.datastructures.metadata.StateAndMetaData; import tech.pegasys.teku.spec.datastructures.operations.Attestation; @@ -168,9 +169,21 @@ public ChainDataProvider( this.lightClientUpdateStore = lightClientUpdateStore; } - public List getBestLightClientUpdates( + public List getBestLightClientUpdates( final UInt64 startPeriod, final int count) { - return lightClientUpdateStore.getBestUpdatesInRange(startPeriod, count); + final Bytes32 genesisValidatorsRoot = getGenesisStateData().getGenesisValidatorsRoot(); + return lightClientUpdateStore.getBestUpdatesInRange(startPeriod, count).stream() + .map( + update -> + new LightClientUpdateWithContext( + spec.computeForkDigest( + genesisValidatorsRoot, spec.computeEpochAtSlot(attestedSlot(update))), + update)) + .toList(); + } + + private static UInt64 attestedSlot(final LightClientUpdate update) { + return update.getAttestedHeader().getBeacon().getSlot(); } public Optional getLatestLightClientFinalityUpdate() { diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/constants/NetworkConstants.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/constants/NetworkConstants.java index d11d1433b7f..453129a76b0 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/constants/NetworkConstants.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/constants/NetworkConstants.java @@ -22,4 +22,6 @@ public class NetworkConstants { // https://github.com/ethereum/consensus-specs/pull/3767 public static final int MAX_CONCURRENT_REQUESTS = 2; + + public static final int MAX_REQUEST_LIGHT_CLIENT_UPDATES = 128; } From 78908cf86a8a045a05082348f4f638562ac49ec9 Mon Sep 17 00:00:00 2001 From: Abhivansh <31abhivanshj@gmail.com> Date: Sat, 29 Aug 2026 16:21:13 +0530 Subject: [PATCH 4/7] test: add tests --- .../GetLightClientUpdatesByRangeTest.java | 76 ++++++++++++++----- 1 file changed, 57 insertions(+), 19 deletions(-) diff --git a/data/beaconrestapi/src/test/java/tech/pegasys/teku/beaconrestapi/handlers/v1/beacon/lightclient/GetLightClientUpdatesByRangeTest.java b/data/beaconrestapi/src/test/java/tech/pegasys/teku/beaconrestapi/handlers/v1/beacon/lightclient/GetLightClientUpdatesByRangeTest.java index f6c8fd16e05..4e69c97256e 100644 --- a/data/beaconrestapi/src/test/java/tech/pegasys/teku/beaconrestapi/handlers/v1/beacon/lightclient/GetLightClientUpdatesByRangeTest.java +++ b/data/beaconrestapi/src/test/java/tech/pegasys/teku/beaconrestapi/handlers/v1/beacon/lightclient/GetLightClientUpdatesByRangeTest.java @@ -14,47 +14,76 @@ package tech.pegasys.teku.beaconrestapi.handlers.v1.beacon.lightclient; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.Mockito.when; import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_BAD_REQUEST; import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_INTERNAL_SERVER_ERROR; import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_NOT_ACCEPTABLE; -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.HttpStatusCodes.SC_SERVICE_UNAVAILABLE; import static tech.pegasys.teku.infrastructure.restapi.MetadataTestUtil.getResponseSszFromMetadata; import static tech.pegasys.teku.infrastructure.restapi.MetadataTestUtil.getResponseStringFromMetadata; import static tech.pegasys.teku.infrastructure.restapi.MetadataTestUtil.verifyMetadataErrorResponse; +import static tech.pegasys.teku.spec.constants.NetworkConstants.MAX_REQUEST_LIGHT_CLIENT_UPDATES; import com.fasterxml.jackson.core.JsonProcessingException; import com.google.common.io.Resources; import java.io.IOException; +import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; import java.util.List; +import org.apache.tuweni.bytes.Bytes; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import tech.pegasys.teku.beaconrestapi.AbstractMigratedBeaconHandlerTest; +import tech.pegasys.teku.infrastructure.bytes.Bytes4; import tech.pegasys.teku.infrastructure.unsigned.UInt64; -import tech.pegasys.teku.spec.SpecMilestone; import tech.pegasys.teku.spec.TestSpecFactory; import tech.pegasys.teku.spec.datastructures.lightclient.LightClientUpdate; -import tech.pegasys.teku.spec.datastructures.lightclient.LightClientUpdateResponse; -import tech.pegasys.teku.spec.datastructures.metadata.ObjectAndMetaData; -import tech.pegasys.teku.spec.util.DataStructureUtil; +import tech.pegasys.teku.spec.datastructures.metadata.LightClientUpdateWithContext; public class GetLightClientUpdatesByRangeTest extends AbstractMigratedBeaconHandlerTest { @BeforeEach void setup() { - setHandler(new GetLightClientUpdatesByRange(schemaDefinitionCache)); - dataStructureUtil = new DataStructureUtil(TestSpecFactory.createMinimalAltair()); + setSpec(TestSpecFactory.createMinimalAltair()); + setHandler(new GetLightClientUpdatesByRange(chainDataProvider, schemaDefinitionCache)); request.setQueryParameter("start_period", "1"); request.setQueryParameter("count", "1"); } + @Test + void shouldReturnBestUpdates() throws Exception { + final List updates = + List.of(dataStructureUtil.randomLightClientUpdateWithContext(UInt64.ONE)); + when(chainDataProvider.getBestLightClientUpdates(UInt64.ONE, 1)).thenReturn(updates); + + handler.handleRequest(request); + + assertThat(request.getResponseCode()).isEqualTo(SC_OK); + assertThat(request.getResponseBody()).isEqualTo(updates); + } + + @Test + void shouldCapCountAtMaxRequestLightClientUpdates() throws Exception { + setHandler(new GetLightClientUpdatesByRange(chainDataProvider, schemaDefinitionCache)); + request.setQueryParameter("start_period", "1"); + request.setQueryParameter("count", "1000"); + when(chainDataProvider.getBestLightClientUpdates(UInt64.ONE, MAX_REQUEST_LIGHT_CLIENT_UPDATES)) + .thenReturn(List.of()); + + handler.handleRequest(request); + + assertThat(request.getResponseCode()).isEqualTo(SC_OK); + assertThat(request.getResponseBody()).isEqualTo(List.of()); + } + @Test void metadata_shouldHandleJson200() throws IOException { - LightClientUpdate lightClientUpdate = dataStructureUtil.randomLightClientUpdate(UInt64.ONE); - ObjectAndMetaData responseData = - new ObjectAndMetaData<>(lightClientUpdate, SpecMilestone.ALTAIR, false, true, false); - List> response = List.of(responseData); + final LightClientUpdate lightClientUpdate = + dataStructureUtil.randomLightClientUpdate(UInt64.ONE); + final List response = + List.of( + new LightClientUpdateWithContext(dataStructureUtil.randomBytes4(), lightClientUpdate)); final String data = getResponseStringFromMetadata(handler, SC_OK, response); final String expected = @@ -67,14 +96,23 @@ void metadata_shouldHandleJson200() throws IOException { @Test void metadata_shouldHandleSsz200() throws IOException { - LightClientUpdateResponse responseData = - dataStructureUtil.randomLightClientUpdateResponse(UInt64.ONE); - List response = List.of(responseData); + final LightClientUpdateWithContext first = + dataStructureUtil.randomLightClientUpdateWithContext(UInt64.ONE); + final LightClientUpdateWithContext second = + dataStructureUtil.randomLightClientUpdateWithContext(UInt64.ONE); + + final byte[] actual = getResponseSszFromMetadata(handler, SC_OK, List.of(first, second)); - final byte[] actual = getResponseSszFromMetadata(handler, SC_OK, response); - final byte[] expected = responseData.sszSerialize().toArray(); + assertThat(Bytes.wrap(actual)) + .isEqualTo(Bytes.wrap(responseChunk(first), responseChunk(second))); + } - assertThat(actual).isEqualTo(expected); + private static Bytes responseChunk(final LightClientUpdateWithContext updateWithContext) { + final Bytes payload = updateWithContext.update().sszSerialize(); + return Bytes.wrap( + Bytes.ofUnsignedLong(Bytes4.SIZE + payload.size(), ByteOrder.LITTLE_ENDIAN), + updateWithContext.context().getWrappedBytes(), + payload); } @Test @@ -93,7 +131,7 @@ void metadata_shouldHandle500() throws JsonProcessingException { } @Test - public void metadata_shouldHandle501() throws JsonProcessingException { - verifyMetadataErrorResponse(handler, SC_NOT_IMPLEMENTED); + void metadata_shouldHandle503() throws JsonProcessingException { + verifyMetadataErrorResponse(handler, SC_SERVICE_UNAVAILABLE); } } From ac21194e25249c19598a1138d0b06bbea397b086 Mon Sep 17 00:00:00 2001 From: Abhivansh <31abhivanshj@gmail.com> Date: Sat, 29 Aug 2026 17:00:09 +0530 Subject: [PATCH 5/7] chore: drop 503 and 204 responses --- .../v1/beacon/lightclient/GetLightClientUpdatesByRange.java | 1 - .../lightclient/GetLightClientUpdatesByRangeTest.java | 6 ------ 2 files changed, 7 deletions(-) diff --git a/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v1/beacon/lightclient/GetLightClientUpdatesByRange.java b/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v1/beacon/lightclient/GetLightClientUpdatesByRange.java index b4332753a8e..a3fc304c951 100644 --- a/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v1/beacon/lightclient/GetLightClientUpdatesByRange.java +++ b/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v1/beacon/lightclient/GetLightClientUpdatesByRange.java @@ -71,7 +71,6 @@ public GetLightClientUpdatesByRange( getJsonResponseType(schemaDefinitionCache), getSszResponseType()) .withNotAcceptableResponse() - .withChainDataResponses() .build()); this.chainDataProvider = chainDataProvider; } diff --git a/data/beaconrestapi/src/test/java/tech/pegasys/teku/beaconrestapi/handlers/v1/beacon/lightclient/GetLightClientUpdatesByRangeTest.java b/data/beaconrestapi/src/test/java/tech/pegasys/teku/beaconrestapi/handlers/v1/beacon/lightclient/GetLightClientUpdatesByRangeTest.java index 4e69c97256e..016ff3a9d16 100644 --- a/data/beaconrestapi/src/test/java/tech/pegasys/teku/beaconrestapi/handlers/v1/beacon/lightclient/GetLightClientUpdatesByRangeTest.java +++ b/data/beaconrestapi/src/test/java/tech/pegasys/teku/beaconrestapi/handlers/v1/beacon/lightclient/GetLightClientUpdatesByRangeTest.java @@ -19,7 +19,6 @@ import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_INTERNAL_SERVER_ERROR; import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_NOT_ACCEPTABLE; import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_OK; -import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_SERVICE_UNAVAILABLE; import static tech.pegasys.teku.infrastructure.restapi.MetadataTestUtil.getResponseSszFromMetadata; import static tech.pegasys.teku.infrastructure.restapi.MetadataTestUtil.getResponseStringFromMetadata; import static tech.pegasys.teku.infrastructure.restapi.MetadataTestUtil.verifyMetadataErrorResponse; @@ -129,9 +128,4 @@ void metadata_shouldHandle406() throws JsonProcessingException { void metadata_shouldHandle500() throws JsonProcessingException { verifyMetadataErrorResponse(handler, SC_INTERNAL_SERVER_ERROR); } - - @Test - void metadata_shouldHandle503() throws JsonProcessingException { - verifyMetadataErrorResponse(handler, SC_SERVICE_UNAVAILABLE); - } } From 686793705c3379ed20430dfe086ff8a291578282 Mon Sep 17 00:00:00 2001 From: Abhivansh <31abhivanshj@gmail.com> Date: Sat, 29 Aug 2026 17:46:53 +0530 Subject: [PATCH 6/7] test: update openapi reference files --- .../_eth_v1_beacon_light_client_updates.json | 28 ++++++------ .../schema/LightClientUpdateCapella.json | 43 +++++++++++++++++++ .../beacon/schema/LightClientUpdateDeneb.json | 43 +++++++++++++++++++ .../schema/LightClientUpdateElectra.json | 43 +++++++++++++++++++ .../beacon/schema/LightClientUpdateGloas.json | 43 +++++++++++++++++++ 5 files changed, 187 insertions(+), 13 deletions(-) create mode 100644 data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/LightClientUpdateCapella.json create mode 100644 data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/LightClientUpdateDeneb.json create mode 100644 data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/LightClientUpdateElectra.json create mode 100644 data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/LightClientUpdateGloas.json diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v1_beacon_light_client_updates.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v1_beacon_light_client_updates.json index 59914da7125..8b3cc081182 100644 --- a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v1_beacon_light_client_updates.json +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v1_beacon_light_client_updates.json @@ -1,9 +1,9 @@ { "get" : { - "tags" : [ "Beacon", "Experimental" ], + "tags" : [ "Beacon" ], "operationId" : "getLightClientUpdatesByRange", "summary" : "Get `LightClientUpdate` instances in a requested sync committee period range", - "description" : "Requests the [`LightClientUpdate`](https://github.com/ethereum/consensus-specs/blob/v1.2.0-rc.3/specs/altair/light-client/sync-protocol.md#lightclientupdate) instances in the sync committee period range `[start_period, start_period + count)`, leading up to the current head sync committee period as selected by fork choice. Depending on the `Accept` header they can be returned either as JSON or SSZ-serialized bytes.", + "description" : "Requests the `LightClientUpdate` instances in the sync committee period range `[start_period, start_period + count)`, leading up to the current head sync committee period as selected by fork choice. Depending on the `Accept` header they can be returned either as JSON or SSZ-serialized bytes.", "parameters" : [ { "name" : "start_period", "required" : true, @@ -41,7 +41,19 @@ "enum" : [ "phase0", "altair", "bellatrix", "capella", "deneb", "electra", "fulu", "gloas", "heze" ] }, "data" : { - "$ref" : "#/components/schemas/LightClientUpdateAltair" + "title" : "LightClientUpdate", + "type" : "object", + "oneOf" : [ { + "$ref" : "#/components/schemas/LightClientUpdateAltair" + }, { + "$ref" : "#/components/schemas/LightClientUpdateCapella" + }, { + "$ref" : "#/components/schemas/LightClientUpdateDeneb" + }, { + "$ref" : "#/components/schemas/LightClientUpdateElectra" + }, { + "$ref" : "#/components/schemas/LightClientUpdateGloas" + } ] } } } @@ -65,16 +77,6 @@ } } }, - "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" : { diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/LightClientUpdateCapella.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/LightClientUpdateCapella.json new file mode 100644 index 00000000000..a59679bbf0a --- /dev/null +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/LightClientUpdateCapella.json @@ -0,0 +1,43 @@ +{ + "title" : "LightClientUpdateCapella", + "type" : "object", + "required" : [ "attested_header", "next_sync_committee", "next_sync_committee_branch", "finalized_header", "finality_branch", "sync_aggregate", "signature_slot" ], + "properties" : { + "attested_header" : { + "$ref" : "#/components/schemas/LightClientHeaderCapella" + }, + "next_sync_committee" : { + "$ref" : "#/components/schemas/SyncCommittee" + }, + "next_sync_committee_branch" : { + "type" : "array", + "items" : { + "type" : "string", + "description" : "Bytes32 hexadecimal", + "example" : "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", + "format" : "byte" + } + }, + "finalized_header" : { + "$ref" : "#/components/schemas/LightClientHeaderCapella" + }, + "finality_branch" : { + "type" : "array", + "items" : { + "type" : "string", + "description" : "Bytes32 hexadecimal", + "example" : "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", + "format" : "byte" + } + }, + "sync_aggregate" : { + "$ref" : "#/components/schemas/SyncAggregate" + }, + "signature_slot" : { + "type" : "string", + "description" : "unsigned 64 bit integer", + "example" : "1", + "format" : "uint64" + } + } +} \ No newline at end of file diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/LightClientUpdateDeneb.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/LightClientUpdateDeneb.json new file mode 100644 index 00000000000..d4735654d0b --- /dev/null +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/LightClientUpdateDeneb.json @@ -0,0 +1,43 @@ +{ + "title" : "LightClientUpdateDeneb", + "type" : "object", + "required" : [ "attested_header", "next_sync_committee", "next_sync_committee_branch", "finalized_header", "finality_branch", "sync_aggregate", "signature_slot" ], + "properties" : { + "attested_header" : { + "$ref" : "#/components/schemas/LightClientHeaderDeneb" + }, + "next_sync_committee" : { + "$ref" : "#/components/schemas/SyncCommittee" + }, + "next_sync_committee_branch" : { + "type" : "array", + "items" : { + "type" : "string", + "description" : "Bytes32 hexadecimal", + "example" : "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", + "format" : "byte" + } + }, + "finalized_header" : { + "$ref" : "#/components/schemas/LightClientHeaderDeneb" + }, + "finality_branch" : { + "type" : "array", + "items" : { + "type" : "string", + "description" : "Bytes32 hexadecimal", + "example" : "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", + "format" : "byte" + } + }, + "sync_aggregate" : { + "$ref" : "#/components/schemas/SyncAggregate" + }, + "signature_slot" : { + "type" : "string", + "description" : "unsigned 64 bit integer", + "example" : "1", + "format" : "uint64" + } + } +} \ No newline at end of file diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/LightClientUpdateElectra.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/LightClientUpdateElectra.json new file mode 100644 index 00000000000..d60fb673f98 --- /dev/null +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/LightClientUpdateElectra.json @@ -0,0 +1,43 @@ +{ + "title" : "LightClientUpdateElectra", + "type" : "object", + "required" : [ "attested_header", "next_sync_committee", "next_sync_committee_branch", "finalized_header", "finality_branch", "sync_aggregate", "signature_slot" ], + "properties" : { + "attested_header" : { + "$ref" : "#/components/schemas/LightClientHeaderDeneb" + }, + "next_sync_committee" : { + "$ref" : "#/components/schemas/SyncCommittee" + }, + "next_sync_committee_branch" : { + "type" : "array", + "items" : { + "type" : "string", + "description" : "Bytes32 hexadecimal", + "example" : "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", + "format" : "byte" + } + }, + "finalized_header" : { + "$ref" : "#/components/schemas/LightClientHeaderDeneb" + }, + "finality_branch" : { + "type" : "array", + "items" : { + "type" : "string", + "description" : "Bytes32 hexadecimal", + "example" : "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", + "format" : "byte" + } + }, + "sync_aggregate" : { + "$ref" : "#/components/schemas/SyncAggregate" + }, + "signature_slot" : { + "type" : "string", + "description" : "unsigned 64 bit integer", + "example" : "1", + "format" : "uint64" + } + } +} \ No newline at end of file diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/LightClientUpdateGloas.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/LightClientUpdateGloas.json new file mode 100644 index 00000000000..2e3bed8e621 --- /dev/null +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/LightClientUpdateGloas.json @@ -0,0 +1,43 @@ +{ + "title" : "LightClientUpdateGloas", + "type" : "object", + "required" : [ "attested_header", "next_sync_committee", "next_sync_committee_branch", "finalized_header", "finality_branch", "sync_aggregate", "signature_slot" ], + "properties" : { + "attested_header" : { + "$ref" : "#/components/schemas/LightClientHeaderGloas" + }, + "next_sync_committee" : { + "$ref" : "#/components/schemas/SyncCommittee" + }, + "next_sync_committee_branch" : { + "type" : "array", + "items" : { + "type" : "string", + "description" : "Bytes32 hexadecimal", + "example" : "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", + "format" : "byte" + } + }, + "finalized_header" : { + "$ref" : "#/components/schemas/LightClientHeaderGloas" + }, + "finality_branch" : { + "type" : "array", + "items" : { + "type" : "string", + "description" : "Bytes32 hexadecimal", + "example" : "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", + "format" : "byte" + } + }, + "sync_aggregate" : { + "$ref" : "#/components/schemas/SyncAggregate" + }, + "signature_slot" : { + "type" : "string", + "description" : "unsigned 64 bit integer", + "example" : "1", + "format" : "uint64" + } + } +} \ No newline at end of file From a97e3a3dfdc519919d2eb25b54e3fc37bfd9ed6f Mon Sep 17 00:00:00 2001 From: Abhivansh <31abhivanshj@gmail.com> Date: Tue, 1 Sep 2026 17:45:23 +0530 Subject: [PATCH 7/7] chore: add base case in getBestLightClientUpdates --- .../GetLightClientUpdatesByRangeTest.java | 19 +++++++++++++++++-- .../pegasys/teku/api/ChainDataProvider.java | 9 ++++++++- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/data/beaconrestapi/src/test/java/tech/pegasys/teku/beaconrestapi/handlers/v1/beacon/lightclient/GetLightClientUpdatesByRangeTest.java b/data/beaconrestapi/src/test/java/tech/pegasys/teku/beaconrestapi/handlers/v1/beacon/lightclient/GetLightClientUpdatesByRangeTest.java index 016ff3a9d16..58bd6886d49 100644 --- a/data/beaconrestapi/src/test/java/tech/pegasys/teku/beaconrestapi/handlers/v1/beacon/lightclient/GetLightClientUpdatesByRangeTest.java +++ b/data/beaconrestapi/src/test/java/tech/pegasys/teku/beaconrestapi/handlers/v1/beacon/lightclient/GetLightClientUpdatesByRangeTest.java @@ -33,14 +33,16 @@ import org.apache.tuweni.bytes.Bytes; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import tech.pegasys.teku.beaconrestapi.AbstractMigratedBeaconHandlerTest; +import tech.pegasys.teku.beaconrestapi.AbstractMigratedBeaconHandlerWithChainDataProviderTest; import tech.pegasys.teku.infrastructure.bytes.Bytes4; import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.SpecMilestone; import tech.pegasys.teku.spec.TestSpecFactory; import tech.pegasys.teku.spec.datastructures.lightclient.LightClientUpdate; import tech.pegasys.teku.spec.datastructures.metadata.LightClientUpdateWithContext; -public class GetLightClientUpdatesByRangeTest extends AbstractMigratedBeaconHandlerTest { +public class GetLightClientUpdatesByRangeTest + extends AbstractMigratedBeaconHandlerWithChainDataProviderTest { @BeforeEach void setup() { @@ -76,6 +78,19 @@ void shouldCapCountAtMaxRequestLightClientUpdates() throws Exception { assertThat(request.getResponseBody()).isEqualTo(List.of()); } + @Test + void shouldReturnEmptyListWhenGenesisDataIsUnavailableAndRangeIsEmpty() throws Exception { + initialise(SpecMilestone.ALTAIR); + setHandler(new GetLightClientUpdatesByRange(chainDataProvider, schemaDefinitionCache)); + request.setQueryParameter("start_period", "1"); + request.setQueryParameter("count", "1"); + + handler.handleRequest(request); + + assertThat(request.getResponseCode()).isEqualTo(SC_OK); + assertThat(request.getResponseBody()).isEqualTo(List.of()); + } + @Test void metadata_shouldHandleJson200() throws IOException { final LightClientUpdate lightClientUpdate = diff --git a/data/provider/src/main/java/tech/pegasys/teku/api/ChainDataProvider.java b/data/provider/src/main/java/tech/pegasys/teku/api/ChainDataProvider.java index 7d13c237368..bd8a553d1e0 100644 --- a/data/provider/src/main/java/tech/pegasys/teku/api/ChainDataProvider.java +++ b/data/provider/src/main/java/tech/pegasys/teku/api/ChainDataProvider.java @@ -171,8 +171,15 @@ public ChainDataProvider( public List getBestLightClientUpdates( final UInt64 startPeriod, final int count) { + final List updates = + lightClientUpdateStore.getBestUpdatesInRange(startPeriod, count); + + if (updates.isEmpty()) { + return List.of(); + } + final Bytes32 genesisValidatorsRoot = getGenesisStateData().getGenesisValidatorsRoot(); - return lightClientUpdateStore.getBestUpdatesInRange(startPeriod, count).stream() + return updates.stream() .map( update -> new LightClientUpdateWithContext(