Skip to content

Commit

Permalink
Add create blinded block contents request
Browse files Browse the repository at this point in the history
  • Loading branch information
mehdi-aouadi committed Apr 14, 2023
1 parent 99b4132 commit e5c6666
Show file tree
Hide file tree
Showing 7 changed files with 234 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@
import tech.pegasys.teku.infrastructure.restapi.endpoints.RestApiEndpoint;
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.blocks.BeaconBlock;
import tech.pegasys.teku.spec.datastructures.blocks.versions.deneb.BlindedBlockContents;
import tech.pegasys.teku.spec.util.DataStructureUtil;
import tech.pegasys.teku.storage.client.ChainDataUnavailableException;

public abstract class AbstractGetNewBlockTest extends AbstractMigratedBeaconHandlerTest {
Expand Down Expand Up @@ -63,6 +66,20 @@ void shouldReturnBlockWithoutGraffiti() throws Exception {
Resources.getResource(AbstractGetNewBlockTest.class, "beaconBlock.json"), UTF_8));
}

@Test
void shouldReturnBlockContentsPostDeneb() throws Exception {
spec = TestSpecFactory.createMinimalDeneb();
DataStructureUtil denebData = new DataStructureUtil(spec);
final BlindedBlockContents blindedBlockContents = denebData.randomBlindedBlockContents(ONE);
doReturn(SafeFuture.completedFuture(Optional.of(blindedBlockContents)))
.when(validatorDataProvider)
.getUnsignedBeaconBlockAtSlot(ONE, signature, Optional.empty(), isBlindedBlocks());

handler.handleRequest(request);

assertThat(request.getResponseBody()).isEqualTo(blindedBlockContents);
}

@Test
void shouldThrowExceptionWithEmptyBlock() throws Exception {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.fail;
import static org.assertj.core.api.Assumptions.assumeThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
Expand Down Expand Up @@ -81,6 +82,7 @@ public class ValidatorDataProviderTest {

private final JsonProvider jsonProvider = new JsonProvider();
private Spec spec;
private SpecMilestone specMilestone;
private DataStructureUtil dataStructureUtil;
private SchemaObjectProvider schemaProvider;
private final CombinedChainDataClient combinedChainDataClient =
Expand All @@ -98,6 +100,7 @@ public void setup(SpecContext specContext) {
schemaProvider = new SchemaObjectProvider(spec);
provider = new ValidatorDataProvider(spec, validatorApiChannel, combinedChainDataClient);
blockInternal = dataStructureUtil.randomBeaconBlock(123);
specMilestone = specContext.getSpecMilestone();
}

@TestTemplate
Expand Down Expand Up @@ -133,40 +136,40 @@ void getUnsignedBeaconBlockAtSlot_shouldThrowIfFarFutureSlotRequested() {
}

@TestTemplate
void getUnsignedBeaconBlockAtSlot_shouldCreateAnUnsignedBlock() {
void getUnsignedBeaconBlockAtSlot_PreDeneb_shouldCreateAnUnsignedBlock() {
assumeThat(specMilestone).isLessThan(SpecMilestone.DENEB);
when(combinedChainDataClient.getCurrentSlot()).thenReturn(ZERO);
when(validatorApiChannel.createUnsignedBlock(ONE, signatureInternal, Optional.empty(), false))
.thenReturn(completedFuture(Optional.of(blockInternal)));

if (denebMilestoneReached()) {
blockContents = dataStructureUtil.randomBlockContents();
when(validatorApiChannel.createUnsignedBlockContents(
ONE, signatureInternal, Optional.empty()))
.thenReturn(completedFuture(Optional.of(blockContents)));
}
SafeFuture<? extends Optional<? extends SszData>> data =
provider.getUnsignedBeaconBlockAtSlot(ONE, signatureInternal, Optional.empty());

verify(validatorApiChannel)
.createUnsignedBlock(ONE, signatureInternal, Optional.empty(), false);

assertThat(data).isCompleted();

assertThat(data.getNow(null).orElseThrow()).usingRecursiveComparison().isEqualTo(blockInternal);
}

@TestTemplate
void getUnsignedBeaconBlockAtSlot_PostDeneb_shouldCreateAnUnsignedBlockContents() {
assumeThat(specMilestone).isGreaterThanOrEqualTo(SpecMilestone.DENEB);
when(combinedChainDataClient.getCurrentSlot()).thenReturn(ZERO);
blockContents = dataStructureUtil.randomBlockContents();
when(validatorApiChannel.createUnsignedBlockContents(ONE, signatureInternal, Optional.empty()))
.thenReturn(completedFuture(Optional.of(blockContents)));

SafeFuture<? extends Optional<? extends SszData>> data =
provider.getUnsignedBeaconBlockAtSlot(ONE, signatureInternal, Optional.empty());

if (denebMilestoneReached()) {
verify(validatorApiChannel)
.createUnsignedBlockContents(ONE, signatureInternal, Optional.empty());
} else {
verify(validatorApiChannel)
.createUnsignedBlock(ONE, signatureInternal, Optional.empty(), false);
}
verify(validatorApiChannel)
.createUnsignedBlockContents(ONE, signatureInternal, Optional.empty());

assertThat(data).isCompleted();

if (denebMilestoneReached()) {
assertThat(data.getNow(null).orElseThrow())
.usingRecursiveComparison()
.isEqualTo(blockContents);
} else {
assertThat(data.getNow(null).orElseThrow())
.usingRecursiveComparison()
.isEqualTo(blockInternal);
}
assertThat(data.getNow(null).orElseThrow()).usingRecursiveComparison().isEqualTo(blockContents);
}

@TestTemplate
Expand Down Expand Up @@ -401,10 +404,4 @@ public void getAttesterDuties_shouldReturnDutiesForKnownValidator() {
final AttesterDuties list = maybeList.orElseThrow();
assertThat(list.getDuties()).containsExactlyInAnyOrder(v1, v2);
}

private boolean denebMilestoneReached() {
return spec.getForkSchedule()
.getSpecMilestoneAtSlot(ONE)
.isGreaterThanOrEqualTo(SpecMilestone.DENEB);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2159,11 +2159,15 @@ public BlockContents randomBlockContents() {
}

public BlindedBlockContents randomBlindedBlockContents() {
return randomBlindedBlockContents(randomUInt64());
}

public BlindedBlockContents randomBlindedBlockContents(UInt64 slot) {
final List<BlindedBlobSidecar> blindedBlobSidecarList = randomBlindedBlobSidecars(4);
final BlindedBlobSidecars blindedBlobSidecars =
new BlindedBlobSidecars(
getSchemaDefinitionDeneb().getBlindedBlobSidecarsSchema(), blindedBlobSidecarList);
final BeaconBlock beaconBlock = randomBeaconBlock();
final BeaconBlock beaconBlock = randomBeaconBlock(slot);
return getSchemaDefinitionDeneb()
.getBlindedBlockContentsSchema()
.create(beaconBlock, blindedBlobSidecars);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,8 @@ public SafeFuture<Optional<BeaconBlock>> createUnsignedBlock(
@Override
public SafeFuture<Optional<BlindedBlockContents>> createUnsignedBlindedBlockContents(
final UInt64 slot, final BLSSignature randaoReveal, Optional<Bytes32> graffiti) {
throw new NotImplementedException("Not Yet Implemented");
return sendRequest(
() -> typeDefClient.createUnsignedBlindedBlockContents(slot, randaoReveal, graffiti));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,14 @@
import tech.pegasys.teku.spec.Spec;
import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlock;
import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock;
import tech.pegasys.teku.spec.datastructures.blocks.versions.deneb.BlindedBlockContents;
import tech.pegasys.teku.spec.datastructures.builder.SignedValidatorRegistration;
import tech.pegasys.teku.spec.datastructures.genesis.GenesisData;
import tech.pegasys.teku.spec.datastructures.operations.AttestationData;
import tech.pegasys.teku.validator.api.SendSignedBlockResult;
import tech.pegasys.teku.validator.api.required.SyncingStatus;
import tech.pegasys.teku.validator.remote.typedef.handlers.CreateAttestationDataRequest;
import tech.pegasys.teku.validator.remote.typedef.handlers.CreateBlindedBlockContentsRequest;
import tech.pegasys.teku.validator.remote.typedef.handlers.CreateBlockRequest;
import tech.pegasys.teku.validator.remote.typedef.handlers.GetGenesisRequest;
import tech.pegasys.teku.validator.remote.typedef.handlers.GetSyncingStatusRequest;
Expand Down Expand Up @@ -105,6 +107,15 @@ public Optional<BeaconBlock> createUnsignedBlock(
}
}

public Optional<BlindedBlockContents> createUnsignedBlindedBlockContents(
final UInt64 slot, final BLSSignature randaoReveal, final Optional<Bytes32> graffiti) {
final CreateBlindedBlockContentsRequest createBlindedBlockContentsRequest =
new CreateBlindedBlockContentsRequest(
baseEndpoint, okHttpClient, spec, slot, preferSszBlockEncoding);
return createBlindedBlockContentsRequest.createUnsignedBlindedBlockContents(
randaoReveal, graffiti);
}

public void registerValidators(
final SszList<SignedValidatorRegistration> validatorRegistrations) {
registerValidatorsRequest.registerValidators(validatorRegistrations);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/*
* Copyright ConsenSys Software Inc., 2023
*
* 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.validator.remote.typedef.handlers;

import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_OK;
import static tech.pegasys.teku.validator.remote.apiclient.ValidatorApiMethod.GET_UNSIGNED_BLINDED_BLOCK;

import com.google.common.net.MediaType;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import tech.pegasys.teku.bls.BLSSignature;
import tech.pegasys.teku.infrastructure.json.JsonUtil;
import tech.pegasys.teku.infrastructure.json.types.DeserializableTypeDefinition;
import tech.pegasys.teku.infrastructure.unsigned.UInt64;
import tech.pegasys.teku.spec.Spec;
import tech.pegasys.teku.spec.SpecMilestone;
import tech.pegasys.teku.spec.datastructures.blocks.versions.deneb.BlindedBlockContents;
import tech.pegasys.teku.spec.datastructures.blocks.versions.deneb.BlindedBlockContentsSchema;
import tech.pegasys.teku.validator.remote.typedef.ResponseHandler;

public class CreateBlindedBlockContentsRequest extends AbstractTypeDefRequest {

private static final Logger LOG = LogManager.getLogger();

private final UInt64 slot;
private final boolean preferSszBlockContentsEncoding;
private final BlindedBlockContentsSchema blindedBlockContentsSchema;
private final DeserializableTypeDefinition<GetBlindedBlockContentsResponse>
getBlockContentsResponseDefinition;
private final ResponseHandler<GetBlindedBlockContentsResponse> responseHandler;

public CreateBlindedBlockContentsRequest(
final HttpUrl baseEndpoint,
final OkHttpClient okHttpClient,
final Spec spec,
final UInt64 slot,
final boolean preferSszBlockContentsEncoding) {
super(baseEndpoint, okHttpClient);
this.slot = slot;
this.preferSszBlockContentsEncoding = preferSszBlockContentsEncoding;
blindedBlockContentsSchema =
spec.atSlot(slot)
.getSchemaDefinitions()
.toVersionDeneb()
.orElseThrow()
.getBlindedBlockContentsSchema();
getBlockContentsResponseDefinition =
DeserializableTypeDefinition.object(GetBlindedBlockContentsResponse.class)
.initializer(GetBlindedBlockContentsResponse::new)
.withField(
"data",
blindedBlockContentsSchema.getJsonTypeDefinition(),
GetBlindedBlockContentsResponse::getData,
GetBlindedBlockContentsResponse::setData)
.withField(
"version",
DeserializableTypeDefinition.enumOf(SpecMilestone.class),
GetBlindedBlockContentsResponse::getSpecMilestone,
GetBlindedBlockContentsResponse::setSpecMilestone)
.build();
final ResponseHandler<GetBlindedBlockContentsResponse> responseHandler =
new ResponseHandler<>(getBlockContentsResponseDefinition)
.withHandler(SC_OK, this::handleBlockContentsResult);
this.responseHandler = responseHandler;
}

public Optional<BlindedBlockContents> createUnsignedBlindedBlockContents(
final BLSSignature randaoReveal, final Optional<Bytes32> graffiti) {
final Map<String, String> queryParams = new HashMap<>();
queryParams.put("randao_reveal", randaoReveal.toString());
final Map<String, String> headers = new HashMap<>();
graffiti.ifPresent(bytes32 -> queryParams.put("graffiti", bytes32.toHexString()));

if (preferSszBlockContentsEncoding) {
// application/octet-stream is preferred, but will accept application/json
headers.put("Accept", "application/octet-stream;q=0.9, application/json;q=0.4");
}
return get(
GET_UNSIGNED_BLINDED_BLOCK,
Map.of("slot", slot.toString()),
queryParams,
headers,
responseHandler)
.map(GetBlindedBlockContentsResponse::getData);
}

private Optional<GetBlindedBlockContentsResponse> handleBlockContentsResult(
final Request request, final Response response) {
try {
final String responseContentType = response.header("Content-Type");
if (responseContentType != null
&& MediaType.parse(responseContentType).is(MediaType.OCTET_STREAM)) {
return Optional.of(
new GetBlindedBlockContentsResponse(
blindedBlockContentsSchema.sszDeserialize(Bytes.of(response.body().bytes()))));
}
return Optional.of(
JsonUtil.parse(response.body().string(), getBlockContentsResponseDefinition));
} catch (IOException e) {
LOG.trace("Failed to parse response object creating block contents", e);
}
return Optional.empty();
}

public static class GetBlindedBlockContentsResponse {
private BlindedBlockContents data;
private SpecMilestone specMilestone;

public GetBlindedBlockContentsResponse() {}

public GetBlindedBlockContentsResponse(final BlindedBlockContents data) {
this.data = data;
}

public BlindedBlockContents getData() {
return data;
}

public void setData(final BlindedBlockContents data) {
this.data = data;
}

public SpecMilestone getSpecMilestone() {
return specMilestone;
}

public void setSpecMilestone(final SpecMilestone specMilestone) {
this.specMilestone = specMilestone;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
import tech.pegasys.teku.spec.TestSpecFactory;
import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlock;
import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock;
import tech.pegasys.teku.spec.datastructures.blocks.versions.deneb.BlindedBlockContents;
import tech.pegasys.teku.spec.datastructures.builder.SignedValidatorRegistration;
import tech.pegasys.teku.spec.datastructures.genesis.GenesisData;
import tech.pegasys.teku.spec.datastructures.operations.AggregateAndProof;
Expand Down Expand Up @@ -409,6 +410,26 @@ public void createUnsignedBlock_WhenFound_ReturnsBlock() {
assertThatSszData(unwrapToValue(future)).isEqualByAllMeansTo(beaconBlock);
}

@Test
public void createUnsignedBlindedBlockContents_WhenFound_ReturnsBlockContents() {
final Spec denebSpec = TestSpecFactory.createMinimalDeneb();
final DataStructureUtil denebDataStructureUtil = new DataStructureUtil(denebSpec);
final BeaconBlock beaconBlock = denebDataStructureUtil.randomBeaconBlock(UInt64.ONE);
final BlindedBlockContents blindedBlockContents =
denebDataStructureUtil.randomBlindedBlockContents(UInt64.ONE);
final BLSSignature blsSignature = denebDataStructureUtil.randomSignature();
final Optional<Bytes32> graffiti = Optional.of(Bytes32.random());

when(typeDefClient.createUnsignedBlindedBlockContents(
eq(beaconBlock.getSlot()), eq(blsSignature), eq(graffiti)))
.thenReturn(Optional.of(blindedBlockContents));

SafeFuture<Optional<BlindedBlockContents>> future =
apiHandler.createUnsignedBlindedBlockContents(UInt64.ONE, blsSignature, graffiti);

assertThatSszData(unwrapToValue(future)).isEqualByAllMeansTo(blindedBlockContents);
}

@Test
public void sendSignedBlock_InvokeApiWithCorrectRequest() {
final BeaconBlock beaconBlock = dataStructureUtil.randomBeaconBlock(UInt64.ONE);
Expand Down

0 comments on commit e5c6666

Please sign in to comment.