Skip to content
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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"
} ]
}
}
}
Expand All @@ -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" : {
Expand Down
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,80 +15,121 @@

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()
.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<List<ObjectAndMetaData<LightClientUpdate>>>
getJsonResponseType(final SchemaDefinitionCache schemaDefinitionCache) {
private static SerializableTypeDefinition<List<LightClientUpdateWithContext>> getJsonResponseType(
final SchemaDefinitionCache schemaDefinitionCache) {
final SerializableTypeDefinition<LightClientUpdate> 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<ObjectAndMetaData<LightClientUpdate>>
lightClientUpdateObjectType =
SerializableTypeDefinition.<ObjectAndMetaData<LightClientUpdate>>object()
.withField("version", MILESTONE_TYPE, ObjectAndMetaData::getMilestone)
.withField("data", lightClientUpdateType, ObjectAndMetaData::getData)
.build();
return SerializableTypeDefinition.listOf(
SerializableTypeDefinition.<LightClientUpdateWithContext>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<List<LightClientUpdateResponse>>
private static ResponseContentTypeDefinition<List<LightClientUpdateWithContext>>
getSszResponseType() {
OctetStreamResponseContentTypeDefinition.OctetStreamSerializer<List<LightClientUpdateResponse>>
OctetStreamResponseContentTypeDefinition.OctetStreamSerializer<
List<LightClientUpdateWithContext>>
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());
}
Expand Down
Loading
Loading