Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Get new BlindedBlockContents Remote api handler #7031

Merged
merged 4 commits into from
Apr 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 getUnsignedBlockContentsAtSlot_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>
getBlindedBlockContentsResponseDefinition;
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();
getBlindedBlockContentsResponseDefinition =
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<>(getBlindedBlockContentsResponseDefinition)
.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(), getBlindedBlockContentsResponseDefinition));
} 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