From 02b476c3d3333f0eb33d463b19b3c6df21d7f035 Mon Sep 17 00:00:00 2001 From: Lucas Saldanha Date: Thu, 10 Oct 2024 11:26:10 +1300 Subject: [PATCH] Updated GetPayloadV4 with executionRequests decoding (#8698) --- .../methods/EngineGetPayloadV4.java | 15 ++++++++- .../schema/GetPayloadV4Response.java | 22 +++++++++++-- .../methods/EngineGetPayloadV4Test.java | 27 +++++++++++++--- .../ElectraExecutionClientHandlerTest.java | 12 ++++--- .../execution/GetPayloadResponse.java | 32 +++++++++++++++++-- .../electra/ExecutionRequestsDataCodec.java | 32 ++++++++++++------- .../teku/spec/util/DataStructureUtil.java | 11 +++++++ 7 files changed, 125 insertions(+), 26 deletions(-) diff --git a/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/methods/EngineGetPayloadV4.java b/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/methods/EngineGetPayloadV4.java index cedf1fbe2aa..60a1fbd62fa 100644 --- a/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/methods/EngineGetPayloadV4.java +++ b/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/methods/EngineGetPayloadV4.java @@ -21,25 +21,35 @@ import tech.pegasys.teku.infrastructure.async.SafeFuture; 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.blobs.versions.deneb.BlobSchema; import tech.pegasys.teku.spec.datastructures.execution.BlobsBundle; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayload; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadContext; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadSchema; import tech.pegasys.teku.spec.datastructures.execution.GetPayloadResponse; +import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionRequests; +import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionRequestsDataCodec; import tech.pegasys.teku.spec.schemas.SchemaDefinitions; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsBellatrix; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsDeneb; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; public class EngineGetPayloadV4 extends AbstractEngineJsonRpcMethod { private static final Logger LOG = LogManager.getLogger(); private final Spec spec; + private final ExecutionRequestsDataCodec executionRequestsDataDecoder; public EngineGetPayloadV4(final ExecutionEngineClient executionEngineClient, final Spec spec) { super(executionEngineClient); this.spec = spec; + this.executionRequestsDataDecoder = + new ExecutionRequestsDataCodec( + SchemaDefinitionsElectra.required( + spec.forMilestone(SpecMilestone.ELECTRA).getSchemaDefinitions()) + .getExecutionRequestsSchema()); } @Override @@ -76,11 +86,14 @@ public SafeFuture execute(final JsonRpcRequestParams params) final ExecutionPayload executionPayload = response.executionPayload.asInternalExecutionPayload(payloadSchema); final BlobsBundle blobsBundle = getBlobsBundle(response, schemaDefinitions); + final ExecutionRequests executionRequests = + executionRequestsDataDecoder.decode(response.executionRequests); return new GetPayloadResponse( executionPayload, response.blockValue, blobsBundle, - response.shouldOverrideBuilder); + response.shouldOverrideBuilder, + executionRequests); }) .thenPeek( getPayloadResponse -> diff --git a/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/schema/GetPayloadV4Response.java b/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/schema/GetPayloadV4Response.java index e414321626b..8ee030d0c53 100644 --- a/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/schema/GetPayloadV4Response.java +++ b/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/schema/GetPayloadV4Response.java @@ -18,12 +18,18 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import java.util.List; +import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.units.bigints.UInt256; +import tech.pegasys.teku.ethereum.executionclient.serialization.BytesDeserializer; +import tech.pegasys.teku.ethereum.executionclient.serialization.BytesSerializer; import tech.pegasys.teku.ethereum.executionclient.serialization.UInt256AsHexDeserializer; import tech.pegasys.teku.ethereum.executionclient.serialization.UInt256AsHexSerializer; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSchema; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadSchema; import tech.pegasys.teku.spec.datastructures.execution.GetPayloadResponse; +import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionRequestsDataCodec; +import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionRequestsSchema; public class GetPayloadV4Response { public final ExecutionPayloadV3 executionPayload; @@ -36,26 +42,36 @@ public class GetPayloadV4Response { public final boolean shouldOverrideBuilder; + @JsonSerialize(contentUsing = BytesSerializer.class) + @JsonDeserialize(contentUsing = BytesDeserializer.class) + public final List executionRequests; + public GetPayloadV4Response( final @JsonProperty("executionPayload") ExecutionPayloadV3 executionPayload, final @JsonProperty("blockValue") UInt256 blockValue, final @JsonProperty("blobsBundle") BlobsBundleV1 blobsBundle, - final @JsonProperty("shouldOverrideBuilder") boolean shouldOverrideBuilder) { + final @JsonProperty("shouldOverrideBuilder") boolean shouldOverrideBuilder, + final @JsonProperty("executionRequests") List executionRequests) { checkNotNull(executionPayload, "executionPayload"); checkNotNull(blockValue, "blockValue"); checkNotNull(blobsBundle, "blobsBundle"); + checkNotNull(executionRequests, "executionRequests"); this.executionPayload = executionPayload; this.blockValue = blockValue; this.blobsBundle = blobsBundle; this.shouldOverrideBuilder = shouldOverrideBuilder; + this.executionRequests = executionRequests; } public GetPayloadResponse asInternalGetPayloadResponse( - final ExecutionPayloadSchema executionPayloadSchema, final BlobSchema blobSchema) { + final ExecutionPayloadSchema executionPayloadSchema, + final BlobSchema blobSchema, + final ExecutionRequestsSchema executionRequestsSchema) { return new GetPayloadResponse( executionPayload.asInternalExecutionPayload(executionPayloadSchema), blockValue, blobsBundle.asInternalBlobsBundle(blobSchema), - shouldOverrideBuilder); + shouldOverrideBuilder, + new ExecutionRequestsDataCodec(executionRequestsSchema).decode(executionRequests)); } } diff --git a/ethereum/executionclient/src/test/java/tech/pegasys/teku/ethereum/executionclient/methods/EngineGetPayloadV4Test.java b/ethereum/executionclient/src/test/java/tech/pegasys/teku/ethereum/executionclient/methods/EngineGetPayloadV4Test.java index 160f7ba0d49..860e2c2bb51 100644 --- a/ethereum/executionclient/src/test/java/tech/pegasys/teku/ethereum/executionclient/methods/EngineGetPayloadV4Test.java +++ b/ethereum/executionclient/src/test/java/tech/pegasys/teku/ethereum/executionclient/methods/EngineGetPayloadV4Test.java @@ -23,8 +23,10 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; +import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.units.bigints.UInt256; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -37,12 +39,16 @@ import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.SpecMilestone; import tech.pegasys.teku.spec.TestSpecFactory; import tech.pegasys.teku.spec.datastructures.execution.BlobsBundle; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayload; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadContext; import tech.pegasys.teku.spec.datastructures.execution.GetPayloadResponse; import tech.pegasys.teku.spec.datastructures.execution.versions.deneb.ExecutionPayloadDeneb; +import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionRequests; +import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionRequestsDataCodec; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; import tech.pegasys.teku.spec.util.DataStructureUtil; class EngineGetPayloadV4Test { @@ -50,6 +56,11 @@ class EngineGetPayloadV4Test { private final Spec spec = TestSpecFactory.createMinimalElectra(); private final DataStructureUtil dataStructureUtil = new DataStructureUtil(spec); private final ExecutionEngineClient executionEngineClient = mock(ExecutionEngineClient.class); + private final ExecutionRequestsDataCodec executionRequestsDataCodec = + new ExecutionRequestsDataCodec( + SchemaDefinitionsElectra.required( + spec.forMilestone(SpecMilestone.ELECTRA).getSchemaDefinitions()) + .getExecutionRequestsSchema()); private EngineGetPayloadV4 jsonRpcMethod; @BeforeEach @@ -117,10 +128,15 @@ public void shouldCallGetPayloadV4AndParseResponseSuccessfully() { final UInt256 blockValue = UInt256.MAX_VALUE; final BlobsBundle blobsBundle = dataStructureUtil.randomBlobsBundle(); final ExecutionPayload executionPayloadElectra = dataStructureUtil.randomExecutionPayload(); + final ExecutionRequests executionRequests = dataStructureUtil.randomExecutionRequests(); + final List encodedExecutionRequests = + executionRequestsDataCodec.encodeWithoutTypePrefix(executionRequests); assertThat(executionPayloadElectra).isInstanceOf(ExecutionPayloadDeneb.class); when(executionEngineClient.getPayloadV4(eq(executionPayloadContext.getPayloadId()))) - .thenReturn(dummySuccessfulResponse(executionPayloadElectra, blockValue, blobsBundle)); + .thenReturn( + dummySuccessfulResponse( + executionPayloadElectra, blockValue, blobsBundle, encodedExecutionRequests)); final JsonRpcRequestParams params = new JsonRpcRequestParams.Builder().add(executionPayloadContext).add(UInt64.ZERO).build(); @@ -128,7 +144,8 @@ public void shouldCallGetPayloadV4AndParseResponseSuccessfully() { jsonRpcMethod = new EngineGetPayloadV4(executionEngineClient, spec); final GetPayloadResponse expectedGetPayloadResponse = - new GetPayloadResponse(executionPayloadElectra, blockValue, blobsBundle, false); + new GetPayloadResponse( + executionPayloadElectra, blockValue, blobsBundle, false, executionRequests); assertThat(jsonRpcMethod.execute(params)).isCompletedWithValue(expectedGetPayloadResponse); verify(executionEngineClient).getPayloadV4(eq(executionPayloadContext.getPayloadId())); @@ -138,14 +155,16 @@ public void shouldCallGetPayloadV4AndParseResponseSuccessfully() { private SafeFuture> dummySuccessfulResponse( final ExecutionPayload executionPayload, final UInt256 blockValue, - final BlobsBundle blobsBundle) { + final BlobsBundle blobsBundle, + final List encodedExecutionRequests) { return SafeFuture.completedFuture( new Response<>( new GetPayloadV4Response( ExecutionPayloadV3.fromInternalExecutionPayload(executionPayload), blockValue, BlobsBundleV1.fromInternalBlobsBundle(blobsBundle), - false))); + false, + encodedExecutionRequests))); } private SafeFuture> dummyFailedResponse( diff --git a/ethereum/executionlayer/src/test/java/tech/pegasys/teku/ethereum/executionlayer/ElectraExecutionClientHandlerTest.java b/ethereum/executionlayer/src/test/java/tech/pegasys/teku/ethereum/executionlayer/ElectraExecutionClientHandlerTest.java index a0b10002bd7..0bd039d2d65 100644 --- a/ethereum/executionlayer/src/test/java/tech/pegasys/teku/ethereum/executionlayer/ElectraExecutionClientHandlerTest.java +++ b/ethereum/executionlayer/src/test/java/tech/pegasys/teku/ethereum/executionlayer/ElectraExecutionClientHandlerTest.java @@ -70,7 +70,8 @@ void engineGetPayload_shouldCallGetPayloadV4() { dataStructureUtil.randomExecutionPayload()), UInt256.MAX_VALUE, BlobsBundleV1.fromInternalBlobsBundle(dataStructureUtil.randomBlobsBundle()), - true); + true, + dataStructureUtil.randomEncodedExecutionRequests()); final SafeFuture> dummyResponse = SafeFuture.completedFuture(new Response<>(responseData)); when(executionEngineClient.getPayloadV4(context.getPayloadId())).thenReturn(dummyResponse); @@ -83,9 +84,12 @@ void engineGetPayload_shouldCallGetPayloadV4() { final ExecutionPayloadSchema executionPayloadSchema = schemaDefinitionElectra.getExecutionPayloadSchema(); final BlobSchema blobSchema = schemaDefinitionElectra.getBlobSchema(); - assertThat(future) - .isCompletedWithValue( - responseData.asInternalGetPayloadResponse(executionPayloadSchema, blobSchema)); + final GetPayloadResponse expectedGetPayloadResponse = + responseData.asInternalGetPayloadResponse( + executionPayloadSchema, + blobSchema, + schemaDefinitionElectra.getExecutionRequestsSchema()); + assertThat(future).isCompletedWithValue(expectedGetPayloadResponse); } @Test diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/GetPayloadResponse.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/GetPayloadResponse.java index 49076414a51..52ef3bb38ac 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/GetPayloadResponse.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/GetPayloadResponse.java @@ -17,6 +17,7 @@ import java.util.Objects; import java.util.Optional; import org.apache.tuweni.units.bigints.UInt256; +import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionRequests; public class GetPayloadResponse { @@ -24,12 +25,14 @@ public class GetPayloadResponse { private final UInt256 executionPayloadValue; private final Optional blobsBundle; private final boolean shouldOverrideBuilder; + private final Optional executionRequests; public GetPayloadResponse(final ExecutionPayload executionPayload) { this.executionPayload = executionPayload; this.executionPayloadValue = UInt256.ZERO; this.blobsBundle = Optional.empty(); this.shouldOverrideBuilder = false; + this.executionRequests = Optional.empty(); } public GetPayloadResponse( @@ -38,6 +41,7 @@ public GetPayloadResponse( this.executionPayloadValue = executionPayloadValue; this.blobsBundle = Optional.empty(); this.shouldOverrideBuilder = false; + this.executionRequests = Optional.empty(); } public GetPayloadResponse( @@ -49,6 +53,20 @@ public GetPayloadResponse( this.executionPayloadValue = executionPayloadValue; this.blobsBundle = Optional.of(blobsBundle); this.shouldOverrideBuilder = shouldOverrideBuilder; + this.executionRequests = Optional.empty(); + } + + public GetPayloadResponse( + final ExecutionPayload executionPayload, + final UInt256 executionPayloadValue, + final BlobsBundle blobsBundle, + final boolean shouldOverrideBuilder, + final ExecutionRequests executionRequests) { + this.executionPayload = executionPayload; + this.executionPayloadValue = executionPayloadValue; + this.blobsBundle = Optional.of(blobsBundle); + this.shouldOverrideBuilder = shouldOverrideBuilder; + this.executionRequests = Optional.of(executionRequests); } public ExecutionPayload getExecutionPayload() { @@ -67,6 +85,10 @@ public boolean getShouldOverrideBuilder() { return shouldOverrideBuilder; } + public Optional getExecutionRequests() { + return executionRequests; + } + @Override public boolean equals(final Object o) { if (this == o) { @@ -79,13 +101,18 @@ public boolean equals(final Object o) { return shouldOverrideBuilder == that.shouldOverrideBuilder && Objects.equals(executionPayload, that.executionPayload) && Objects.equals(executionPayloadValue, that.executionPayloadValue) - && Objects.equals(blobsBundle, that.blobsBundle); + && Objects.equals(blobsBundle, that.blobsBundle) + && Objects.equals(executionRequests, that.executionRequests); } @Override public int hashCode() { return Objects.hash( - executionPayload, executionPayloadValue, blobsBundle, shouldOverrideBuilder); + executionPayload, + executionPayloadValue, + blobsBundle, + shouldOverrideBuilder, + executionRequests); } @Override @@ -95,6 +122,7 @@ public String toString() { .add("executionPayloadValue", executionPayloadValue) .add("blobsBundle", blobsBundle) .add("shouldOverrideBuilder", shouldOverrideBuilder) + .add("executionRequests", executionRequests) .toString(); } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/ExecutionRequestsDataCodec.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/ExecutionRequestsDataCodec.java index a31930f38c7..1e51ad64264 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/ExecutionRequestsDataCodec.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/ExecutionRequestsDataCodec.java @@ -78,8 +78,17 @@ public ExecutionRequests decode(final List executionRequestData) { return executionRequestsBuilder.build(); } + public Bytes32 hash(final ExecutionRequests executionRequests) { + final Bytes sortedEncodedRequests = + encodeWithTypePrefix(executionRequests).stream() + .map(Hash::sha256) + .map(Bytes.class::cast) + .reduce(Bytes.EMPTY, Bytes::concatenate); + return Hash.sha256(sortedEncodedRequests); + } + @VisibleForTesting - List encodeWithTypePrefix(final ExecutionRequests executionRequests) { + public List encodeWithoutTypePrefix(final ExecutionRequests executionRequests) { final SszList depositRequestsSszList = executionRequestsSchema .getDepositRequestsSchema() @@ -94,18 +103,17 @@ List encodeWithTypePrefix(final ExecutionRequests executionRequests) { .createFromElements(executionRequests.getConsolidations()); return List.of( - Bytes.concatenate(DEPOSIT_REQUEST_PREFIX, depositRequestsSszList.sszSerialize()), - Bytes.concatenate(WITHDRAWAL_REQUEST_PREFIX, withdrawalRequestsSszList.sszSerialize()), - Bytes.concatenate( - CONSOLIDATION_REQUEST_PREFIX, consolidationRequestsSszList.sszSerialize())); + depositRequestsSszList.sszSerialize(), + withdrawalRequestsSszList.sszSerialize(), + consolidationRequestsSszList.sszSerialize()); } - public Bytes32 hash(final ExecutionRequests executionRequests) { - final Bytes sortedEncodedRequests = - encodeWithTypePrefix(executionRequests).stream() - .map(Hash::sha256) - .map(Bytes.class::cast) - .reduce(Bytes.EMPTY, Bytes::concatenate); - return Hash.sha256(sortedEncodedRequests); + @VisibleForTesting + List encodeWithTypePrefix(final ExecutionRequests executionRequests) { + final List encodeWithoutTypePrefix = encodeWithoutTypePrefix(executionRequests); + return List.of( + Bytes.concatenate(DEPOSIT_REQUEST_PREFIX, encodeWithoutTypePrefix.get(0)), + Bytes.concatenate(WITHDRAWAL_REQUEST_PREFIX, encodeWithoutTypePrefix.get(1)), + Bytes.concatenate(CONSOLIDATION_REQUEST_PREFIX, encodeWithoutTypePrefix.get(2))); } } 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 8a46648f436..98c31a885e7 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 @@ -132,6 +132,8 @@ import tech.pegasys.teku.spec.datastructures.execution.versions.electra.DepositRequest; import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionRequests; import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionRequestsBuilderElectra; +import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionRequestsDataCodec; +import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionRequestsSchema; import tech.pegasys.teku.spec.datastructures.execution.versions.electra.WithdrawalRequest; import tech.pegasys.teku.spec.datastructures.forkchoice.VoteTracker; import tech.pegasys.teku.spec.datastructures.lightclient.LightClientBootstrap; @@ -2504,6 +2506,15 @@ public ExecutionRequests randomExecutionRequests() { .build(); } + public List randomEncodedExecutionRequests() { + final ExecutionRequestsSchema executionRequestsSchema = + SchemaDefinitionsElectra.required( + spec.forMilestone(SpecMilestone.ELECTRA).getSchemaDefinitions()) + .getExecutionRequestsSchema(); + return new ExecutionRequestsDataCodec(executionRequestsSchema) + .encodeWithoutTypePrefix(randomExecutionRequests()); + } + public WithdrawalRequest randomWithdrawalRequest() { return getElectraSchemaDefinitions(randomSlot()) .getWithdrawalRequestSchema()