Skip to content

Commit abca9a3

Browse files
authored
Add GetDataColumnSidecars REST API (#154)
1 parent 1190e9e commit abca9a3

File tree

12 files changed

+485
-0
lines changed

12 files changed

+485
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
{
2+
"get" : {
3+
"tags" : [ "Beacon" ],
4+
"operationId" : "getDataColumnSidecars",
5+
"summary" : "Get data column sidecars",
6+
"description" : "Retrieves data column sidecars for a given block id.\n Depending on `Accept` header it can be returned either as json or as bytes serialized by SSZ.\n If the `indices` parameter is specified, only the data column sidecars with the specified indices will be returned. There are no guarantees\n for the returned data column sidecars in terms of ordering.",
7+
"parameters" : [ {
8+
"name" : "block_id",
9+
"required" : true,
10+
"in" : "path",
11+
"schema" : {
12+
"type" : "string",
13+
"description" : "Block identifier. Can be one of: \"head\" (canonical head in node's view), \"genesis\", \"finalized\", <slot>, <hex encoded blockRoot with 0x prefix>.",
14+
"example" : "head"
15+
}
16+
}, {
17+
"name" : "indices",
18+
"in" : "query",
19+
"schema" : {
20+
"type" : "array",
21+
"items" : {
22+
"type" : "string",
23+
"description" : "Array of indices for data column sidecars to request for in the specified block. Returns all data column sidecars in the block if not specified.",
24+
"example" : "1",
25+
"format" : "uint64"
26+
},
27+
"minItems" : "1"
28+
}
29+
} ],
30+
"responses" : {
31+
"200" : {
32+
"description" : "Request successful",
33+
"content" : {
34+
"application/json" : {
35+
"schema" : {
36+
"$ref" : "#/components/schemas/GetDataColumnSidecarsResponse"
37+
}
38+
},
39+
"application/octet-stream" : {
40+
"schema" : {
41+
"type" : "string",
42+
"format" : "binary"
43+
}
44+
}
45+
}
46+
},
47+
"404" : {
48+
"description" : "Not found",
49+
"content" : {
50+
"application/json" : {
51+
"schema" : {
52+
"$ref" : "#/components/schemas/HttpErrorResponse"
53+
}
54+
}
55+
}
56+
},
57+
"400" : {
58+
"description" : "The request could not be processed, check the response for more information.",
59+
"content" : {
60+
"application/json" : {
61+
"schema" : {
62+
"$ref" : "#/components/schemas/HttpErrorResponse"
63+
}
64+
}
65+
}
66+
},
67+
"500" : {
68+
"description" : "Internal server error",
69+
"content" : {
70+
"application/json" : {
71+
"schema" : {
72+
"$ref" : "#/components/schemas/HttpErrorResponse"
73+
}
74+
}
75+
}
76+
}
77+
}
78+
}
79+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
{
2+
"title" : "DataColumnSidecar",
3+
"type" : "object",
4+
"required" : [ "index", "column", "kzg_commitments", "kzg_proofs", "signed_block_header", "kzg_commitments_inclusion_proof" ],
5+
"properties" : {
6+
"index" : {
7+
"type" : "string",
8+
"description" : "unsigned 64 bit integer",
9+
"example" : "1",
10+
"format" : "uint64"
11+
},
12+
"column" : {
13+
"type" : "array",
14+
"items" : {
15+
"type" : "string",
16+
"pattern" : "^0x[a-fA-F0-9]{2,}$",
17+
"description" : "SSZ hexadecimal",
18+
"format" : "bytes"
19+
}
20+
},
21+
"kzg_commitments" : {
22+
"type" : "array",
23+
"items" : {
24+
"type" : "string",
25+
"pattern" : "^0x[a-fA-F0-9]{2,}$",
26+
"description" : "Bytes48 hexadecimal",
27+
"format" : "bytes"
28+
}
29+
},
30+
"kzg_proofs" : {
31+
"type" : "array",
32+
"items" : {
33+
"type" : "string",
34+
"pattern" : "^0x[a-fA-F0-9]{2,}$",
35+
"description" : "Bytes48 hexadecimal",
36+
"format" : "bytes"
37+
}
38+
},
39+
"signed_block_header" : {
40+
"$ref" : "#/components/schemas/SignedBeaconBlockHeader"
41+
},
42+
"kzg_commitments_inclusion_proof" : {
43+
"type" : "array",
44+
"items" : {
45+
"type" : "string",
46+
"description" : "Bytes32 hexadecimal",
47+
"example" : "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
48+
"format" : "byte"
49+
}
50+
}
51+
}
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"title" : "GetDataColumnSidecarsResponse",
3+
"type" : "object",
4+
"required" : [ "data" ],
5+
"properties" : {
6+
"data" : {
7+
"type" : "array",
8+
"items" : {
9+
"$ref" : "#/components/schemas/DataColumnSidecar"
10+
}
11+
}
12+
}
13+
}

data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/BeaconRestApiTypes.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,12 @@ public class BeaconRestApiTypes {
230230
CoreTypes.UINT64_TYPE.withDescription(
231231
"Array of indices for blob sidecars to request for in the specified block. Returns all blob sidecars in the block if not specified."));
232232

233+
public static final ParameterMetadata<UInt64> DATA_COLUMN_INDICES_PARAMETER =
234+
new ParameterMetadata<>(
235+
"indices",
236+
CoreTypes.UINT64_TYPE.withDescription(
237+
"Array of indices for data column sidecars to request for in the specified block. Returns all data column sidecars in the block if not specified."));
238+
233239
private static final StringValueTypeDefinition<BroadcastValidationParameter>
234240
BROADCAST_VALIDATION_VALUE =
235241
DeserializableTypeDefinition.string(BroadcastValidationParameter.class)

data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/JsonTypeDefinitionBeaconRestApi.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import tech.pegasys.teku.api.exceptions.ServiceUnavailableException;
2525
import tech.pegasys.teku.beaconrestapi.addon.CapellaRestApiBuilderAddon;
2626
import tech.pegasys.teku.beaconrestapi.addon.DenebRestApiBuilderAddon;
27+
import tech.pegasys.teku.beaconrestapi.addon.Eip7594RestApiBuilderAddon;
2728
import tech.pegasys.teku.beaconrestapi.addon.LightClientRestApiBuilderAddon;
2829
import tech.pegasys.teku.beaconrestapi.handlers.tekuv1.admin.Liveness;
2930
import tech.pegasys.teku.beaconrestapi.handlers.tekuv1.admin.PutLogLevel;
@@ -320,6 +321,7 @@ private static RestApiBuilder applyAddons(
320321
List.of(
321322
new CapellaRestApiBuilderAddon(spec, dataProvider, schemaCache),
322323
new DenebRestApiBuilderAddon(spec, dataProvider, schemaCache),
324+
new Eip7594RestApiBuilderAddon(spec, dataProvider, schemaCache),
323325
new LightClientRestApiBuilderAddon(config, dataProvider, schemaCache));
324326

325327
RestApiBuilder builderUpdated = builder;
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright Consensys Software Inc., 2022
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*/
13+
14+
package tech.pegasys.teku.beaconrestapi.addon;
15+
16+
import tech.pegasys.teku.api.DataProvider;
17+
import tech.pegasys.teku.beaconrestapi.RestApiBuilderAddon;
18+
import tech.pegasys.teku.beaconrestapi.handlers.v1.beacon.GetDataColumnSidecars;
19+
import tech.pegasys.teku.infrastructure.restapi.RestApiBuilder;
20+
import tech.pegasys.teku.spec.Spec;
21+
import tech.pegasys.teku.spec.SpecMilestone;
22+
import tech.pegasys.teku.spec.schemas.SchemaDefinitionCache;
23+
24+
public class Eip7594RestApiBuilderAddon implements RestApiBuilderAddon {
25+
26+
final Spec spec;
27+
final DataProvider dataProvider;
28+
final SchemaDefinitionCache schemaCache;
29+
30+
public Eip7594RestApiBuilderAddon(
31+
final Spec spec, final DataProvider dataProvider, final SchemaDefinitionCache schemaCache) {
32+
this.spec = spec;
33+
this.dataProvider = dataProvider;
34+
this.schemaCache = schemaCache;
35+
}
36+
37+
@Override
38+
public boolean isEnabled() {
39+
return spec.isMilestoneSupported(SpecMilestone.EIP7594);
40+
}
41+
42+
@Override
43+
public RestApiBuilder apply(final RestApiBuilder builder) {
44+
return builder.endpoint(new GetDataColumnSidecars(dataProvider, schemaCache));
45+
}
46+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*
2+
* Copyright Consensys Software Inc., 2022
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*/
13+
14+
package tech.pegasys.teku.beaconrestapi.handlers.v1.beacon;
15+
16+
import static tech.pegasys.teku.beaconrestapi.BeaconRestApiTypes.DATA_COLUMN_INDICES_PARAMETER;
17+
import static tech.pegasys.teku.beaconrestapi.BeaconRestApiTypes.PARAMETER_BLOCK_ID;
18+
import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_OK;
19+
import static tech.pegasys.teku.infrastructure.http.RestApiConstants.TAG_BEACON;
20+
import static tech.pegasys.teku.infrastructure.json.types.SerializableTypeDefinition.listOf;
21+
22+
import com.fasterxml.jackson.core.JsonProcessingException;
23+
import java.util.Collections;
24+
import java.util.List;
25+
import java.util.Optional;
26+
import java.util.function.Function;
27+
import tech.pegasys.teku.api.ChainDataProvider;
28+
import tech.pegasys.teku.api.DataProvider;
29+
import tech.pegasys.teku.infrastructure.async.SafeFuture;
30+
import tech.pegasys.teku.infrastructure.json.types.DeserializableTypeDefinition;
31+
import tech.pegasys.teku.infrastructure.json.types.SerializableTypeDefinition;
32+
import tech.pegasys.teku.infrastructure.restapi.endpoints.AsyncApiResponse;
33+
import tech.pegasys.teku.infrastructure.restapi.endpoints.EndpointMetadata;
34+
import tech.pegasys.teku.infrastructure.restapi.endpoints.RestApiEndpoint;
35+
import tech.pegasys.teku.infrastructure.restapi.endpoints.RestApiRequest;
36+
import tech.pegasys.teku.infrastructure.restapi.openapi.response.OctetStreamResponseContentTypeDefinition;
37+
import tech.pegasys.teku.infrastructure.restapi.openapi.response.ResponseContentTypeDefinition;
38+
import tech.pegasys.teku.infrastructure.unsigned.UInt64;
39+
import tech.pegasys.teku.spec.SpecMilestone;
40+
import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar;
41+
import tech.pegasys.teku.spec.schemas.SchemaDefinitionCache;
42+
import tech.pegasys.teku.spec.schemas.SchemaDefinitionsEip7594;
43+
44+
public class GetDataColumnSidecars extends RestApiEndpoint {
45+
public static final String ROUTE = "/eth/v1/beacon/data_column_sidecars/{block_id}";
46+
private final ChainDataProvider chainDataProvider;
47+
48+
public GetDataColumnSidecars(
49+
final DataProvider dataProvider, final SchemaDefinitionCache schemaCache) {
50+
this(dataProvider.getChainDataProvider(), schemaCache);
51+
}
52+
53+
public GetDataColumnSidecars(
54+
final ChainDataProvider chainDataProvider, final SchemaDefinitionCache schemaCache) {
55+
super(createEndpointMetadata(schemaCache));
56+
this.chainDataProvider = chainDataProvider;
57+
}
58+
59+
private static EndpointMetadata createEndpointMetadata(final SchemaDefinitionCache schemaCache) {
60+
return EndpointMetadata.get(ROUTE)
61+
.operationId("getDataColumnSidecars")
62+
.summary("Get data column sidecars")
63+
.description(
64+
"Retrieves data column sidecars for a given block id.\n"
65+
+ " Depending on `Accept` header it can be returned either as json or as bytes serialized by SSZ.\n"
66+
+ " If the `indices` parameter is specified, only the data column sidecars with the specified indices will be returned. There are no guarantees\n"
67+
+ " for the returned data column sidecars in terms of ordering.")
68+
.tags(TAG_BEACON)
69+
.pathParam(PARAMETER_BLOCK_ID)
70+
.queryListParam(DATA_COLUMN_INDICES_PARAMETER)
71+
.response(SC_OK, "Request successful", getResponseType(schemaCache), getSszResponseType())
72+
.withNotFoundResponse()
73+
.build();
74+
}
75+
76+
@Override
77+
public void handleRequest(RestApiRequest request) throws JsonProcessingException {
78+
final List<UInt64> indices = request.getQueryParameterList(DATA_COLUMN_INDICES_PARAMETER);
79+
final SafeFuture<Optional<List<DataColumnSidecar>>> future =
80+
chainDataProvider.getDataColumnSidecars(
81+
request.getPathParameter(PARAMETER_BLOCK_ID), indices);
82+
83+
request.respondAsync(
84+
future.thenApply(
85+
dataColumnSidecars ->
86+
dataColumnSidecars
87+
.map(AsyncApiResponse::respondOk)
88+
.orElse(AsyncApiResponse.respondNotFound())));
89+
}
90+
91+
private static SerializableTypeDefinition<List<DataColumnSidecar>> getResponseType(
92+
final SchemaDefinitionCache schemaCache) {
93+
final DeserializableTypeDefinition<DataColumnSidecar> dataColumnSidecarType =
94+
SchemaDefinitionsEip7594.required(schemaCache.getSchemaDefinition(SpecMilestone.EIP7594))
95+
.getDataColumnSidecarSchema()
96+
.getJsonTypeDefinition();
97+
return SerializableTypeDefinition.<List<DataColumnSidecar>>object()
98+
.name("GetDataColumnSidecarsResponse")
99+
.withField("data", listOf(dataColumnSidecarType), Function.identity())
100+
.build();
101+
}
102+
103+
private static ResponseContentTypeDefinition<List<DataColumnSidecar>> getSszResponseType() {
104+
final OctetStreamResponseContentTypeDefinition.OctetStreamSerializer<List<DataColumnSidecar>>
105+
serializer =
106+
(data, out) -> data.forEach(dataColumnSidecar -> dataColumnSidecar.sszSerialize(out));
107+
108+
return new OctetStreamResponseContentTypeDefinition<>(serializer, __ -> Collections.emptyMap());
109+
}
110+
}

data/provider/src/main/java/tech/pegasys/teku/api/ChainDataProvider.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.apache.tuweni.bytes.Bytes48;
3636
import tech.pegasys.teku.api.blobselector.BlobSidecarSelectorFactory;
3737
import tech.pegasys.teku.api.blockselector.BlockSelectorFactory;
38+
import tech.pegasys.teku.api.datacolumnselector.DataColumnSidecarSelectorFactory;
3839
import tech.pegasys.teku.api.exceptions.BadRequestException;
3940
import tech.pegasys.teku.api.exceptions.ServiceUnavailableException;
4041
import tech.pegasys.teku.api.migrated.AttestationRewardsData;
@@ -59,6 +60,7 @@
5960
import tech.pegasys.teku.spec.Spec;
6061
import tech.pegasys.teku.spec.SpecMilestone;
6162
import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar;
63+
import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar;
6264
import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlock;
6365
import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock;
6466
import tech.pegasys.teku.spec.datastructures.execution.versions.capella.Withdrawal;
@@ -84,6 +86,7 @@ public class ChainDataProvider {
8486
private final BlockSelectorFactory blockSelectorFactory;
8587
private final StateSelectorFactory stateSelectorFactory;
8688
private final BlobSidecarSelectorFactory blobSidecarSelectorFactory;
89+
private final DataColumnSidecarSelectorFactory dataColumnSidecarSelectorFactory;
8790
private final Spec spec;
8891
private final CombinedChainDataClient combinedChainDataClient;
8992
private final SchemaObjectProvider schemaObjectProvider;
@@ -104,6 +107,7 @@ public ChainDataProvider(
104107
new StateSelectorFactory(spec, combinedChainDataClient),
105108
new BlobSidecarSelectorFactory(
106109
spec, combinedChainDataClient, blobSidecarReconstructionProvider),
110+
new DataColumnSidecarSelectorFactory(combinedChainDataClient),
107111
rewardCalculator);
108112
}
109113

@@ -115,6 +119,7 @@ public ChainDataProvider(
115119
final BlockSelectorFactory blockSelectorFactory,
116120
final StateSelectorFactory stateSelectorFactory,
117121
final BlobSidecarSelectorFactory blobSidecarSelectorFactory,
122+
final DataColumnSidecarSelectorFactory dataColumnSidecarSelectorFactory,
118123
final RewardCalculator rewardCalculator) {
119124
this.spec = spec;
120125
this.combinedChainDataClient = combinedChainDataClient;
@@ -123,6 +128,7 @@ public ChainDataProvider(
123128
this.blockSelectorFactory = blockSelectorFactory;
124129
this.stateSelectorFactory = stateSelectorFactory;
125130
this.blobSidecarSelectorFactory = blobSidecarSelectorFactory;
131+
this.dataColumnSidecarSelectorFactory = dataColumnSidecarSelectorFactory;
126132
this.rewardCalculator = rewardCalculator;
127133
}
128134

@@ -194,6 +200,13 @@ public SafeFuture<Optional<List<BlobSidecar>>> getAllBlobSidecarsAtSlot(
194200
return blobSidecarSelectorFactory.slotSelectorForAll(slot).getBlobSidecars(indices);
195201
}
196202

203+
public SafeFuture<Optional<List<DataColumnSidecar>>> getDataColumnSidecars(
204+
final String blockIdParam, final List<UInt64> indices) {
205+
return dataColumnSidecarSelectorFactory
206+
.createSelectorForBlockId(blockIdParam)
207+
.getDataColumnSidecars(indices);
208+
}
209+
197210
public SafeFuture<Optional<ObjectAndMetaData<Bytes32>>> getBlockRoot(final String blockIdParam) {
198211
return fromBlock(blockIdParam, SignedBeaconBlock::getRoot);
199212
}

0 commit comments

Comments
 (0)