diff --git a/ethereum/core/src/testFixtures/java/tech/pegasys/teku/core/BlockProposalTestUtil.java b/ethereum/core/src/testFixtures/java/tech/pegasys/teku/core/BlockProposalTestUtil.java index 8089c80ef72..9e8f23f89e9 100644 --- a/ethereum/core/src/testFixtures/java/tech/pegasys/teku/core/BlockProposalTestUtil.java +++ b/ethereum/core/src/testFixtures/java/tech/pegasys/teku/core/BlockProposalTestUtil.java @@ -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(); @@ -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) { diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/executionengine/ExecutionEngineChannel.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/executionengine/ExecutionEngineChannel.java index 83a3ad445d9..9cce7c4bf9d 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/executionengine/ExecutionEngineChannel.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/executionengine/ExecutionEngineChannel.java @@ -21,6 +21,7 @@ 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 { @@ -28,13 +29,13 @@ public interface ExecutionEngineChannel extends ChannelInterface { new ExecutionEngineChannel() { @Override - public SafeFuture prepareBlock( - Bytes32 parentHash, UInt64 timestamp, UInt64 payloadId) { - return SafeFuture.completedFuture(null); + public SafeFuture preparePayload( + Bytes32 parentHash, UInt64 timestamp, Bytes32 random, Bytes20 feeRecipient) { + return SafeFuture.completedFuture(UInt64.ZERO); } @Override - public SafeFuture assembleBlock(Bytes32 parentHash, UInt64 timestamp) { + public SafeFuture getPayload(UInt64 payloadId) { return SafeFuture.completedFuture(new ExecutionPayload()); } @@ -64,16 +65,10 @@ public SafeFuture getPowChainHead() { } }; - SafeFuture prepareBlock(Bytes32 parentHash, UInt64 timestamp, UInt64 payloadId); + SafeFuture 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 assembleBlock(Bytes32 parentHash, UInt64 timestamp); + SafeFuture getPayload(UInt64 payloadId); /** * Requests execution-engine to process a block. diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/util/ExecutionPayloadUtil.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/util/ExecutionPayloadUtil.java index 65844e2705c..760c61f801b 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/util/ExecutionPayloadUtil.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/util/ExecutionPayloadUtil.java @@ -19,6 +19,7 @@ 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 { @@ -30,19 +31,21 @@ public boolean verifyExecutionStateTransition( return executionEngineChannel.newBlock(executionPayload).join(); } - public void prepareExecutionPayload( + public UInt64 prepareExecutionPayload( ExecutionEngineChannel executionEngineChannel, Bytes32 parentHash, UInt64 timestamp, - UInt64 payloadId) { - executionEngineChannel.prepareBlock(parentHash, timestamp, payloadId).join(); + Bytes32 random, + Bytes20 feeRecipient) { + + return executionEngineChannel + .preparePayload(parentHash, timestamp, random, feeRecipient) + .join(); } public ExecutionPayload getExecutionPayload( - ExecutionEngineChannel executionEngineChannel, - Bytes32 parentHash, - UInt64 timestamp, - UInt64 payloadId) { - return executionEngineChannel.assembleBlock(parentHash, timestamp).join(); + ExecutionEngineChannel executionEngineChannel, UInt64 payloadId) { + + return executionEngineChannel.getPayload(payloadId).join(); } } diff --git a/services/powchain/src/main/java/tech/pegasys/teku/services/powchain/execution/ExecutionEngineChannelImpl.java b/services/powchain/src/main/java/tech/pegasys/teku/services/powchain/execution/ExecutionEngineChannelImpl.java index fa2ee8825b7..85faa0df7b6 100644 --- a/services/powchain/src/main/java/tech/pegasys/teku/services/powchain/execution/ExecutionEngineChannelImpl.java +++ b/services/powchain/src/main/java/tech/pegasys/teku/services/powchain/execution/ExecutionEngineChannelImpl.java @@ -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 { @@ -66,24 +68,34 @@ private static void printConsole(String formatString, Object... args) { } @Override - public SafeFuture prepareBlock(Bytes32 parentHash, UInt64 timestamp, UInt64 payloadId) { - return SafeFuture.completedFuture(null); + public SafeFuture 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 assembleBlock( - Bytes32 parentHash, UInt64 timestamp) { - AssembleBlockRequest request = new AssembleBlockRequest(parentHash, timestamp); + public SafeFuture 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 diff --git a/services/powchain/src/main/java/tech/pegasys/teku/services/powchain/execution/client/ExecutionEngineClient.java b/services/powchain/src/main/java/tech/pegasys/teku/services/powchain/execution/client/ExecutionEngineClient.java index 2cd238e158a..a57ee950cb4 100644 --- a/services/powchain/src/main/java/tech/pegasys/teku/services/powchain/execution/client/ExecutionEngineClient.java +++ b/services/powchain/src/main/java/tech/pegasys/teku/services/powchain/execution/client/ExecutionEngineClient.java @@ -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> consensusAssembleBlock(AssembleBlockRequest request); + SafeFuture> preparePayload(PreparePayloadRequest request); + + SafeFuture> getPayload(UInt64 payloadId); SafeFuture> consensusNewBlock(ExecutionPayload request); @@ -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 lastPreparePayloadRequest = Optional.empty(); + + @Override + public SafeFuture> preparePayload( + PreparePayloadRequest request) { + lastPreparePayloadRequest = Optional.of(request); + payloadId = payloadId.increment(); + return SafeFuture.completedFuture(new Response<>(new PreparePayloadResponse(payloadId))); + } @Override - public SafeFuture> consensusAssembleBlock( - AssembleBlockRequest request) { + public SafeFuture> 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, @@ -62,7 +77,7 @@ public SafeFuture> 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))))); diff --git a/services/powchain/src/main/java/tech/pegasys/teku/services/powchain/execution/client/Web3JExecutionEngineClient.java b/services/powchain/src/main/java/tech/pegasys/teku/services/powchain/execution/client/Web3JExecutionEngineClient.java index f6806fbacd1..8a8002c597d 100644 --- a/services/powchain/src/main/java/tech/pegasys/teku/services/powchain/execution/client/Web3JExecutionEngineClient.java +++ b/services/powchain/src/main/java/tech/pegasys/teku/services/powchain/execution/client/Web3JExecutionEngineClient.java @@ -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 { @@ -40,14 +42,25 @@ public Web3JExecutionEngineClient(String eth1Endpoint) { } @Override - public SafeFuture> consensusAssembleBlock( - AssembleBlockRequest request) { - Request web3jRequest = + public SafeFuture> preparePayload( + PreparePayloadRequest request) { + Request web3jRequest = new Request<>( - "consensus_assembleBlock", + "engine_preparePayload", Collections.singletonList(request), web3jService, - AssembleBlockWeb3jResponse.class); + PreparePayloadWeb3jResponse.class); + return doRequest(web3jRequest); + } + + @Override + public SafeFuture> getPayload(UInt64 payloadId) { + Request web3jRequest = + new Request<>( + "engine_getPayload", + Collections.singletonList(payloadId.toString()), + web3jService, + GetPayloadWeb3jResponse.class); return doRequest(web3jRequest); } @@ -117,8 +130,10 @@ private SafeFuture> doRequest( return SafeFuture.of(responseFuture); } - static class AssembleBlockWeb3jResponse - extends org.web3j.protocol.core.Response {} + static class GetPayloadWeb3jResponse extends org.web3j.protocol.core.Response {} + + static class PreparePayloadWeb3jResponse + extends org.web3j.protocol.core.Response {} static class NewBlockWeb3jResponse extends org.web3j.protocol.core.Response {} diff --git a/services/powchain/src/main/java/tech/pegasys/teku/services/powchain/execution/client/schema/PreparePayloadRequest.java b/services/powchain/src/main/java/tech/pegasys/teku/services/powchain/execution/client/schema/PreparePayloadRequest.java new file mode 100644 index 00000000000..ddfc8c31a08 --- /dev/null +++ b/services/powchain/src/main/java/tech/pegasys/teku/services/powchain/execution/client/schema/PreparePayloadRequest.java @@ -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 + + '}'; + } +} diff --git a/services/powchain/src/main/java/tech/pegasys/teku/services/powchain/execution/client/schema/PreparePayloadResponse.java b/services/powchain/src/main/java/tech/pegasys/teku/services/powchain/execution/client/schema/PreparePayloadResponse.java new file mode 100644 index 00000000000..d9001053331 --- /dev/null +++ b/services/powchain/src/main/java/tech/pegasys/teku/services/powchain/execution/client/schema/PreparePayloadResponse.java @@ -0,0 +1,52 @@ +/* + * 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.annotation.JsonProperty; +import java.util.Objects; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; + +public class PreparePayloadResponse { + private UInt64 payloadId; + + public PreparePayloadResponse(@JsonProperty("payloadId") UInt64 payloadId) { + this.payloadId = payloadId; + } + + public UInt64 getPayloadId() { + return payloadId; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PreparePayloadResponse that = (PreparePayloadResponse) o; + return Objects.equals(payloadId, that.payloadId); + } + + @Override + public int hashCode() { + return Objects.hash(payloadId); + } + + @Override + public String toString() { + return "PreparePayloadResponse{" + "payloadId=" + payloadId + '}'; + } +} diff --git a/validator/coordinator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockFactory.java b/validator/coordinator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockFactory.java index 2411afe7c6c..121091acfbb 100644 --- a/validator/coordinator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockFactory.java +++ b/validator/coordinator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockFactory.java @@ -21,6 +21,7 @@ import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.units.bigints.UInt256; import tech.pegasys.teku.bls.BLSSignature; +import tech.pegasys.teku.infrastructure.collections.cache.LRUCache; import tech.pegasys.teku.infrastructure.logging.ColorConsolePrinter; import tech.pegasys.teku.infrastructure.logging.ColorConsolePrinter.Color; import tech.pegasys.teku.infrastructure.unsigned.UInt64; @@ -44,6 +45,7 @@ import tech.pegasys.teku.spec.logic.common.statetransition.exceptions.StateTransitionException; import tech.pegasys.teku.spec.logic.common.util.ExecutionPayloadUtil; import tech.pegasys.teku.ssz.SszList; +import tech.pegasys.teku.ssz.type.Bytes20; import tech.pegasys.teku.statetransition.OperationPool; import tech.pegasys.teku.statetransition.attestation.AggregatingAttestationPool; import tech.pegasys.teku.statetransition.attestation.AttestationForkChecker; @@ -64,6 +66,10 @@ public class BlockFactory { private final Spec spec; private final ExecutionEngineChannel executionEngineChannel; + // 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 final LRUCache slotToPayloadIdMap; + public BlockFactory( final AggregatingAttestationPool attestationPool, final OperationPool attesterSlashingPool, @@ -85,29 +91,68 @@ public BlockFactory( this.graffiti = graffiti; this.spec = spec; this.executionEngineChannel = executionEngineChannel; + + this.slotToPayloadIdMap = + LRUCache.create( + 10); // TODO check if makes sense to remember payloadId for the latest 10 slots } - public void prepareExecutionPayload( - final Optional maybeCurrentSlotState, UInt64 payloadId) { + public void prepareExecutionPayload(final Optional maybeCurrentSlotState) { if (maybeCurrentSlotState.isEmpty()) { + LOG.error("prepareExecutionPayload - empty state!"); return; } final Optional maybeCurrentMergeState = maybeCurrentSlotState.get().toVersionMerge(); if (maybeCurrentMergeState.isEmpty()) { + LOG.trace("prepareExecutionPayload - not yet in Merge state!"); return; } final BeaconStateMerge currentMergeState = maybeCurrentMergeState.get(); + final UInt64 slot = currentMergeState.getSlot(); + final UInt64 epoch = spec.computeEpochAtSlot(slot); + final UInt64 timestamp = spec.computeTimeAtSlot(currentMergeState, slot); + final Bytes32 random = + spec.atEpoch(epoch).beaconStateAccessors().getRandaoMix(currentMergeState, epoch); + + final MergeTransitionHelpers mergeTransitionHelpers = + spec.atSlot(slot).getMergeTransitionHelpers().orElseThrow(); + final TransitionStore transitionStore = spec.atSlot(slot).getTransitionStore().orElseThrow(); + + Bytes32 executionParentHash; + + if (!mergeTransitionHelpers.isMergeComplete(currentMergeState)) { + PowBlock powHead = mergeTransitionHelpers.getPowChainHead(executionEngineChannel); + if (!mergeTransitionHelpers.isValidTerminalPowBlock(powHead, transitionStore)) { + LOG.trace("prepareExecutionPayload - terminal block not yet reached!"); + return; + } + // terminal block + executionParentHash = powHead.blockHash; + LOG.trace("prepareExecutionPayload - terminal block reached!"); + } else { + executionParentHash = currentMergeState.getLatest_execution_payload_header().getBlock_hash(); + } + final ExecutionPayloadUtil executionPayloadUtil = - spec.atSlot(currentMergeState.getSlot()).getExecutionPayloadUtil().orElseThrow(); + spec.atSlot(slot) + .getExecutionPayloadUtil() + .orElseThrow( + () -> + new IllegalStateException( + "unable to retrieve ExecutionPayloadUtil from slot " + slot)); + + final UInt64 payloadId = + executionPayloadUtil.prepareExecutionPayload( + executionEngineChannel, + executionParentHash, + timestamp, + random, + Bytes20.ZERO); // TODO let's burn fees for now! - UInt64 timestamp = spec.computeTimeAtSlot(currentMergeState, currentMergeState.getSlot()); - final Bytes32 executionParentHash = - currentMergeState.getLatest_execution_payload_header().getBlock_hash(); - executionPayloadUtil.prepareExecutionPayload( - executionEngineChannel, executionParentHash, timestamp, payloadId); + slotToPayloadIdMap.invalidateWithNewValue(slot, payloadId); } public BeaconBlock createUnsignedBlock( @@ -115,8 +160,7 @@ public BeaconBlock createUnsignedBlock( final Optional maybeBlockSlotState, final UInt64 newSlot, final BLSSignature randaoReveal, - final Optional optionalGraffiti, - final UInt64 executionPayloadId) + final Optional optionalGraffiti) throws EpochProcessingException, SlotProcessingException, StateTransitionException { checkArgument( maybeBlockSlotState.isEmpty() || maybeBlockSlotState.get().getSlot().equals(newSlot), @@ -177,21 +221,20 @@ public BeaconBlock createUnsignedBlock( .attesterSlashings(attesterSlashings) .deposits(deposits) .voluntaryExits(voluntaryExits) - .executionPayload(() -> getExecutionPayload(blockSlotState, executionPayloadId)) + .executionPayload(() -> getExecutionPayload(blockSlotState)) .syncAggregate( () -> contributionPool.createSyncAggregateForBlock(newSlot, parentRoot))) .getBlock(); } - private ExecutionPayload getExecutionPayload( - BeaconState genericState, UInt64 executionPayloadId) { + private ExecutionPayload getExecutionPayload(BeaconState genericState) { final BeaconStateMerge state = BeaconStateMerge.required(genericState); + final UInt64 slot = state.getSlot(); final ExecutionPayloadUtil executionPayloadUtil = - spec.atSlot(state.getSlot()).getExecutionPayloadUtil().orElseThrow(); + spec.atSlot(slot).getExecutionPayloadUtil().orElseThrow(); final MergeTransitionHelpers mergeTransitionHelpers = - spec.atSlot(state.getSlot()).getMergeTransitionHelpers().orElseThrow(); - final TransitionStore transitionStore = - spec.atSlot(state.getSlot()).getTransitionStore().orElseThrow(); + spec.atSlot(slot).getMergeTransitionHelpers().orElseThrow(); + final TransitionStore transitionStore = spec.atSlot(slot).getTransitionStore().orElseThrow(); if (!mergeTransitionHelpers.isMergeComplete(state)) { PowBlock powHead = mergeTransitionHelpers.getPowChainHead(executionEngineChannel); @@ -220,16 +263,38 @@ private ExecutionPayload getExecutionPayload( powHead.totalDifficulty.toBigInteger(), transitionStore.getTransitionTotalDifficulty().toBigInteger()), Color.YELLOW)); - UInt64 timestamp = spec.computeTimeAtSlot(state, state.getSlot()); - return executionPayloadUtil.getExecutionPayload( - executionEngineChannel, powHead.blockHash, timestamp, executionPayloadId); + + return validateExecutionPayload( + powHead.blockHash, + executionPayloadUtil.getExecutionPayload( + executionEngineChannel, getExecutionPayloadIdFromSlot(slot))); } } // Post-merge, normal payload - final Bytes32 executionParentHash = state.getLatest_execution_payload_header().getBlock_hash(); - final UInt64 timestamp = spec.computeTimeAtSlot(state, state.getSlot()); - return executionPayloadUtil.getExecutionPayload( - executionEngineChannel, executionParentHash, timestamp, executionPayloadId); + return validateExecutionPayload( + state.getLatest_execution_payload_header().getBlock_hash(), + executionPayloadUtil.getExecutionPayload( + executionEngineChannel, getExecutionPayloadIdFromSlot(slot))); + } + + private UInt64 getExecutionPayloadIdFromSlot(UInt64 slot) { + return slotToPayloadIdMap + .getCached(slot) + .orElseThrow( + () -> + new IllegalStateException( + "Unable to retrieve execution payloadId from slot " + slot)); + } + + private ExecutionPayload validateExecutionPayload( + Bytes32 expectedExecutionParentHash, ExecutionPayload executionPayload) { + + if (!executionPayload.getParent_hash().equals(expectedExecutionParentHash)) { + throw new IllegalStateException( + "Execution Payload returned by the execution client has an unexpected parent block hash"); + } + + return executionPayload; } } diff --git a/validator/coordinator/src/main/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandler.java b/validator/coordinator/src/main/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandler.java index 3e30560ad28..e20428e3360 100644 --- a/validator/coordinator/src/main/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandler.java +++ b/validator/coordinator/src/main/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandler.java @@ -268,8 +268,7 @@ public SafeFuture prepareExecutionPayload(UInt64 preparingSlot) { return currentSlotStateFuture.thenApply( preState -> { - blockFactory.prepareExecutionPayload( - preState, preparingSlot); // we are using slot number as payloadId + blockFactory.prepareExecutionPayload(preState); return null; }); } @@ -312,12 +311,7 @@ private Optional createBlock( "Delegating to block factory. Has block slot state? {}", maybeBlockSlotState.isPresent()); return Optional.of( blockFactory.createUnsignedBlock( - maybePreState.get(), - maybeBlockSlotState, - slot, - randaoReveal, - graffiti, - slot)); // we are using slot number as payloadId + maybePreState.get(), maybeBlockSlotState, slot, randaoReveal, graffiti)); } @Override diff --git a/validator/coordinator/src/test/java/tech/pegasys/teku/validator/coordinator/BlockFactoryTest.java b/validator/coordinator/src/test/java/tech/pegasys/teku/validator/coordinator/BlockFactoryTest.java index 7fd9c718ed4..394f99adeeb 100644 --- a/validator/coordinator/src/test/java/tech/pegasys/teku/validator/coordinator/BlockFactoryTest.java +++ b/validator/coordinator/src/test/java/tech/pegasys/teku/validator/coordinator/BlockFactoryTest.java @@ -141,7 +141,7 @@ private BeaconBlock assertBlockCreated(final int blockSlot, final Spec spec) final BeaconBlock block = blockFactory.createUnsignedBlock( - previousState, Optional.empty(), newSlot, randaoReveal, Optional.empty(), UInt64.ZERO); + previousState, Optional.empty(), newSlot, randaoReveal, Optional.empty()); assertThat(block).isNotNull(); assertThat(block.getSlot()).isEqualTo(newSlot); diff --git a/validator/coordinator/src/test/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerTest.java b/validator/coordinator/src/test/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerTest.java index ed501c2d062..9f3946a4366 100644 --- a/validator/coordinator/src/test/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerTest.java +++ b/validator/coordinator/src/test/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerTest.java @@ -433,12 +433,7 @@ public void createUnsignedBlock_shouldCreateBlock() throws Exception { when(chainDataClient.getStateAtSlotExact(newSlot)) .thenReturn(SafeFuture.completedFuture(Optional.of(blockSlotState))); when(blockFactory.createUnsignedBlock( - previousState, - Optional.of(blockSlotState), - newSlot, - randaoReveal, - Optional.empty(), - newSlot)) + previousState, Optional.of(blockSlotState), newSlot, randaoReveal, Optional.empty())) .thenReturn(createdBlock); final SafeFuture> result = @@ -446,12 +441,7 @@ public void createUnsignedBlock_shouldCreateBlock() throws Exception { verify(blockFactory) .createUnsignedBlock( - previousState, - Optional.of(blockSlotState), - newSlot, - randaoReveal, - Optional.empty(), - newSlot); + previousState, Optional.of(blockSlotState), newSlot, randaoReveal, Optional.empty()); assertThat(result).isCompletedWithValue(Optional.of(createdBlock)); }