Skip to content

Commit

Permalink
implement engine_preparePayload and engine_getPayload (Consensys#…
Browse files Browse the repository at this point in the history
  • Loading branch information
tbenr committed Sep 21, 2021
1 parent 78ca516 commit 9d44980
Show file tree
Hide file tree
Showing 10 changed files with 262 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ public SignedBlockAndState createNewBlock(
.attesterSlashings(blockBodyLists.createAttesterSlashings())
.deposits(deposits)
.voluntaryExits(exits)
.executionPayload(() -> createExecutionPayload(spec, blockSlotState)));
.executionPayload(() -> getExecutionPayload(spec, blockSlotState)));

// Sign block and set block signature
final BeaconBlock block = newBlockAndState.getBlock();
Expand Down Expand Up @@ -128,16 +128,13 @@ private Eth1Data get_eth1_data_stub(BeaconState state, UInt64 current_epoch) {
Hash.sha2_256(Hash.sha2_256(SSZ.encodeUInt64(votingPeriod.longValue()))));
}

private static ExecutionPayload createExecutionPayload(Spec spec, BeaconState genericState) {
private static ExecutionPayload getExecutionPayload(Spec spec, BeaconState genericState) {
final BeaconStateMerge state = BeaconStateMerge.required(genericState);
final Bytes32 executionParentHash = state.getLatest_execution_payload_header().getBlock_hash();
final UInt64 timestamp = spec.computeTimeAtSlot(state, state.getSlot());

return spec.atSlot(state.getSlot())
.getExecutionPayloadUtil()
.orElseThrow()
.getExecutionPayload(
ExecutionEngineChannel.NOOP, executionParentHash, timestamp, UInt64.ZERO);
.getExecutionPayload(ExecutionEngineChannel.NOOP, UInt64.ZERO);
}

public int getProposerIndexForSlot(final BeaconState preState, final UInt64 slot) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,21 @@
import tech.pegasys.teku.infrastructure.events.ChannelInterface;
import tech.pegasys.teku.infrastructure.unsigned.UInt64;
import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayload;
import tech.pegasys.teku.ssz.type.Bytes20;

public interface ExecutionEngineChannel extends ChannelInterface {

ExecutionEngineChannel NOOP =
new ExecutionEngineChannel() {

@Override
public SafeFuture<Void> prepareBlock(
Bytes32 parentHash, UInt64 timestamp, UInt64 payloadId) {
return SafeFuture.completedFuture(null);
public SafeFuture<UInt64> preparePayload(
Bytes32 parentHash, UInt64 timestamp, Bytes32 random, Bytes20 feeRecipient) {
return SafeFuture.completedFuture(UInt64.ZERO);
}

@Override
public SafeFuture<ExecutionPayload> assembleBlock(Bytes32 parentHash, UInt64 timestamp) {
public SafeFuture<ExecutionPayload> getPayload(UInt64 payloadId) {
return SafeFuture.completedFuture(new ExecutionPayload());
}

Expand Down Expand Up @@ -64,16 +65,10 @@ public SafeFuture<Block> getPowChainHead() {
}
};

SafeFuture<Void> prepareBlock(Bytes32 parentHash, UInt64 timestamp, UInt64 payloadId);
SafeFuture<UInt64> preparePayload(
Bytes32 parentHash, UInt64 timestamp, Bytes32 random, Bytes20 feeRecipient);

/**
* Requests execution-engine to produce a block.
*
* @param parentHash the hash of execution block to produce atop of
* @param timestamp the timestamp of the beginning of the slot
* @return a response with execution payload
*/
SafeFuture<ExecutionPayload> assembleBlock(Bytes32 parentHash, UInt64 timestamp);
SafeFuture<ExecutionPayload> getPayload(UInt64 payloadId);

/**
* Requests execution-engine to process a block.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,22 @@
import static com.google.common.base.Preconditions.checkNotNull;

import org.apache.tuweni.bytes.Bytes32;
import tech.pegasys.teku.infrastructure.collections.cache.LRUCache;
import tech.pegasys.teku.infrastructure.unsigned.UInt64;
import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayload;
import tech.pegasys.teku.spec.executionengine.ExecutionEngineChannel;
import tech.pegasys.teku.ssz.type.Bytes20;

public class ExecutionPayloadUtil {
// we are going to store latest payloadId returned by preparePayload for a given slot
// in this way validator doesn't need to know anything about payloadId
private LRUCache<UInt64, UInt64> slotToPayloadIdMap;

public ExecutionPayloadUtil() {}
public ExecutionPayloadUtil() {
this.slotToPayloadIdMap =
LRUCache.create(
10); // TODO check if makes sense to remember payloadId for the latest 10 slots
}

public boolean verifyExecutionStateTransition(
ExecutionEngineChannel executionEngineChannel, ExecutionPayload executionPayload) {
Expand All @@ -32,17 +41,27 @@ public boolean verifyExecutionStateTransition(

public void prepareExecutionPayload(
ExecutionEngineChannel executionEngineChannel,
UInt64 slot,
Bytes32 parentHash,
UInt64 timestamp,
UInt64 payloadId) {
executionEngineChannel.prepareBlock(parentHash, timestamp, payloadId).join();
Bytes32 random,
Bytes20 feeRecipient) {

UInt64 payloadId =
executionEngineChannel.preparePayload(parentHash, timestamp, random, feeRecipient).join();

slotToPayloadIdMap.invalidateWithNewValue(slot, payloadId);
}

public ExecutionPayload getExecutionPayload(
ExecutionEngineChannel executionEngineChannel,
Bytes32 parentHash,
UInt64 timestamp,
UInt64 payloadId) {
return executionEngineChannel.assembleBlock(parentHash, timestamp).join();
ExecutionEngineChannel executionEngineChannel, UInt64 slot) {
UInt64 payloadId =
slotToPayloadIdMap
.getCached(slot)
.orElseThrow(
() ->
new IllegalStateException(
"Unable to retrieve execution payloadId from slot " + slot));
return executionEngineChannel.getPayload(payloadId).join();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,13 @@
import tech.pegasys.teku.infrastructure.unsigned.UInt64;
import tech.pegasys.teku.services.powchain.execution.client.ExecutionEngineClient;
import tech.pegasys.teku.services.powchain.execution.client.Web3JExecutionEngineClient;
import tech.pegasys.teku.services.powchain.execution.client.schema.AssembleBlockRequest;
import tech.pegasys.teku.services.powchain.execution.client.schema.ExecutionPayload;
import tech.pegasys.teku.services.powchain.execution.client.schema.NewBlockResponse;
import tech.pegasys.teku.services.powchain.execution.client.schema.PreparePayloadRequest;
import tech.pegasys.teku.services.powchain.execution.client.schema.PreparePayloadResponse;
import tech.pegasys.teku.services.powchain.execution.client.schema.Response;
import tech.pegasys.teku.spec.executionengine.ExecutionEngineChannel;
import tech.pegasys.teku.ssz.type.Bytes20;

public class ExecutionEngineChannelImpl implements ExecutionEngineChannel {

Expand Down Expand Up @@ -66,24 +68,34 @@ private static void printConsole(String formatString, Object... args) {
}

@Override
public SafeFuture<Void> prepareBlock(Bytes32 parentHash, UInt64 timestamp, UInt64 payloadId) {
return SafeFuture.completedFuture(null);
public SafeFuture<UInt64> preparePayload(
Bytes32 parentHash, UInt64 timestamp, Bytes32 random, Bytes20 feeRecipient) {
return executionEngineClient
.preparePayload(new PreparePayloadRequest(parentHash, timestamp, random, feeRecipient))
.thenApply(ExecutionEngineChannelImpl::unwrapResponseOrThrow)
.thenApply(PreparePayloadResponse::getPayloadId)
.thenPeek(
getPayloadId ->
printConsole(
"engine_preparePayload(parentHash=%s, timestamp=%s, random=%s, feeRecipient=%s) ~> %s",
LogFormatter.formatHashRoot(parentHash),
timestamp.toString(),
LogFormatter.formatHashRoot(random),
feeRecipient.toHexString(),
getPayloadId));
}

@Override
public SafeFuture<tech.pegasys.teku.spec.datastructures.execution.ExecutionPayload> assembleBlock(
Bytes32 parentHash, UInt64 timestamp) {
AssembleBlockRequest request = new AssembleBlockRequest(parentHash, timestamp);
public SafeFuture<tech.pegasys.teku.spec.datastructures.execution.ExecutionPayload> getPayload(
UInt64 payloadId) {

return executionEngineClient
.consensusAssembleBlock(request)
.getPayload(payloadId)
.thenApply(ExecutionEngineChannelImpl::unwrapResponseOrThrow)
.thenApply(ExecutionPayload::asInternalExecutionPayload)
.thenPeek(
executionPayload ->
printConsole(
"consensus_assembleBlock(parent_hash=%s, timestamp=%s) ~> %s",
LogFormatter.formatHashRoot(parentHash), timestamp, executionPayload));
printConsole("engine_getPayload(payloadId=%s) ~> %s", payloadId, executionPayload));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,19 @@
import org.web3j.protocol.core.methods.response.EthBlock;
import tech.pegasys.teku.infrastructure.async.SafeFuture;
import tech.pegasys.teku.infrastructure.unsigned.UInt64;
import tech.pegasys.teku.services.powchain.execution.client.schema.AssembleBlockRequest;
import tech.pegasys.teku.services.powchain.execution.client.schema.ExecutionPayload;
import tech.pegasys.teku.services.powchain.execution.client.schema.GenericResponse;
import tech.pegasys.teku.services.powchain.execution.client.schema.NewBlockResponse;
import tech.pegasys.teku.services.powchain.execution.client.schema.PreparePayloadRequest;
import tech.pegasys.teku.services.powchain.execution.client.schema.PreparePayloadResponse;
import tech.pegasys.teku.services.powchain.execution.client.schema.Response;
import tech.pegasys.teku.ssz.type.Bytes20;

public interface ExecutionEngineClient {

SafeFuture<Response<ExecutionPayload>> consensusAssembleBlock(AssembleBlockRequest request);
SafeFuture<Response<PreparePayloadResponse>> preparePayload(PreparePayloadRequest request);

SafeFuture<Response<ExecutionPayload>> getPayload(UInt64 payloadId);

SafeFuture<Response<NewBlockResponse>> consensusNewBlock(ExecutionPayload request);

Expand All @@ -45,15 +48,27 @@ public interface ExecutionEngineClient {
new ExecutionEngineClient() {
private final Bytes ZERO_LOGS_BLOOM = Bytes.wrap(new byte[256]);
private UInt64 number = UInt64.ZERO;
private UInt64 payloadId = UInt64.ZERO;
private Optional<PreparePayloadRequest> lastPreparePayloadRequest = Optional.empty();

@Override
public SafeFuture<Response<PreparePayloadResponse>> preparePayload(
PreparePayloadRequest request) {
lastPreparePayloadRequest = Optional.of(request);
payloadId = payloadId.increment();
return SafeFuture.completedFuture(new Response<>(new PreparePayloadResponse(payloadId)));
}

@Override
public SafeFuture<Response<ExecutionPayload>> consensusAssembleBlock(
AssembleBlockRequest request) {
public SafeFuture<Response<ExecutionPayload>> getPayload(UInt64 payloadId) {
PreparePayloadRequest preparePayloadRequest =
lastPreparePayloadRequest.orElseThrow(
() -> new IllegalStateException("preparePayload was not called."));
number = number.increment();
return SafeFuture.completedFuture(
new Response<>(
new ExecutionPayload(
request.parentHash,
preparePayloadRequest.parentHash,
Bytes20.ZERO,
Bytes32.ZERO,
Bytes32.ZERO,
Expand All @@ -62,7 +77,7 @@ public SafeFuture<Response<ExecutionPayload>> consensusAssembleBlock(
number,
UInt64.ZERO,
UInt64.ZERO,
request.timestamp,
preparePayloadRequest.timestamp,
Bytes32.ZERO,
Bytes32.random(),
Arrays.asList(Bytes.random(128), Bytes.random(256), Bytes.random(512)))));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@
import org.web3j.protocol.core.methods.response.EthBlock;
import org.web3j.protocol.http.HttpService;
import tech.pegasys.teku.infrastructure.async.SafeFuture;
import tech.pegasys.teku.services.powchain.execution.client.schema.AssembleBlockRequest;
import tech.pegasys.teku.infrastructure.unsigned.UInt64;
import tech.pegasys.teku.services.powchain.execution.client.schema.ExecutionPayload;
import tech.pegasys.teku.services.powchain.execution.client.schema.GenericResponse;
import tech.pegasys.teku.services.powchain.execution.client.schema.NewBlockResponse;
import tech.pegasys.teku.services.powchain.execution.client.schema.PreparePayloadRequest;
import tech.pegasys.teku.services.powchain.execution.client.schema.PreparePayloadResponse;
import tech.pegasys.teku.services.powchain.execution.client.schema.Response;

public class Web3JExecutionEngineClient implements ExecutionEngineClient {
Expand All @@ -40,14 +42,25 @@ public Web3JExecutionEngineClient(String eth1Endpoint) {
}

@Override
public SafeFuture<Response<ExecutionPayload>> consensusAssembleBlock(
AssembleBlockRequest request) {
Request<?, AssembleBlockWeb3jResponse> web3jRequest =
public SafeFuture<Response<PreparePayloadResponse>> preparePayload(
PreparePayloadRequest request) {
Request<?, PreparePayloadWeb3jResponse> web3jRequest =
new Request<>(
"consensus_assembleBlock",
"engine_preparePayload",
Collections.singletonList(request),
web3jService,
AssembleBlockWeb3jResponse.class);
PreparePayloadWeb3jResponse.class);
return doRequest(web3jRequest);
}

@Override
public SafeFuture<Response<ExecutionPayload>> getPayload(UInt64 payloadId) {
Request<?, GetPayloadWeb3jResponse> web3jRequest =
new Request<>(
"engine_getPayload",
Collections.singletonList(payloadId.toString()),
web3jService,
GetPayloadWeb3jResponse.class);
return doRequest(web3jRequest);
}

Expand Down Expand Up @@ -117,8 +130,10 @@ private <T> SafeFuture<Response<T>> doRequest(
return SafeFuture.of(responseFuture);
}

static class AssembleBlockWeb3jResponse
extends org.web3j.protocol.core.Response<ExecutionPayload> {}
static class GetPayloadWeb3jResponse extends org.web3j.protocol.core.Response<ExecutionPayload> {}

static class PreparePayloadWeb3jResponse
extends org.web3j.protocol.core.Response<PreparePayloadResponse> {}

static class NewBlockWeb3jResponse extends org.web3j.protocol.core.Response<NewBlockResponse> {}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Copyright 2021 ConsenSys AG.
*
* 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.services.powchain.execution.client.schema;

import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.util.Objects;
import org.apache.tuweni.bytes.Bytes32;
import tech.pegasys.teku.infrastructure.unsigned.UInt64;
import tech.pegasys.teku.services.powchain.execution.client.serializer.BytesSerializer;
import tech.pegasys.teku.services.powchain.execution.client.serializer.UInt64AsHexSerializer;
import tech.pegasys.teku.ssz.type.Bytes20;

public class PreparePayloadRequest {
@JsonSerialize(using = BytesSerializer.class)
public final Bytes32 parentHash;

@JsonSerialize(using = UInt64AsHexSerializer.class)
public final UInt64 timestamp;

@JsonSerialize(using = BytesSerializer.class)
public final Bytes32 random;

@JsonSerialize(using = BytesSerializer.class)
public final Bytes20 feeRecipient;

public PreparePayloadRequest(
Bytes32 parentHash, UInt64 timestamp, Bytes32 random, Bytes20 feeRecipient) {
this.parentHash = parentHash;
this.timestamp = timestamp;
this.random = random;
this.feeRecipient = feeRecipient;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
PreparePayloadRequest that = (PreparePayloadRequest) o;
return Objects.equals(parentHash, that.parentHash)
&& Objects.equals(timestamp, that.timestamp)
&& Objects.equals(random, that.random)
&& Objects.equals(feeRecipient, that.feeRecipient);
}

@Override
public int hashCode() {
return Objects.hash(parentHash, timestamp, random, feeRecipient);
}

@Override
public String toString() {
return "PreparePayloadRequest{"
+ "parentHash="
+ parentHash
+ ", timestamp="
+ timestamp
+ ", random="
+ random
+ ", feeRecipient="
+ feeRecipient
+ '}';
}
}
Loading

0 comments on commit 9d44980

Please sign in to comment.