Prague Engine API validation fixes (#8250)

- Prague newPayload validation fixes
- Testing the unsupported fork timestamp cases

Signed-off-by: Simon Dudley <simon.dudley@consensys.net>
This commit is contained in:
Simon Dudley
2025-02-06 12:02:29 +10:00
committed by GitHub
parent 952deb79e9
commit 2680901cb9
18 changed files with 276 additions and 68 deletions

View File

@@ -170,11 +170,6 @@ public abstract class AbstractEngineForkchoiceUpdated extends ExecutionEngineJso
.log();
return maybeError.get();
}
ValidationResult<RpcErrorType> forkValidationResult =
validateForkSupported(payloadAttributes.getTimestamp());
if (!forkValidationResult.isValid()) {
return new JsonRpcErrorResponse(requestId, forkValidationResult);
}
}
final BlockHeader newHead = maybeNewHead.get();

View File

@@ -153,6 +153,12 @@ public abstract class AbstractEngineNewPayload extends ExecutionEngineJsonRpcMet
e);
}
final ValidationResult<RpcErrorType> forkValidationResult =
validateForkSupported(blockParam.getTimestamp());
if (!forkValidationResult.isValid()) {
return new JsonRpcErrorResponse(reqId, forkValidationResult);
}
final ValidationResult<RpcErrorType> parameterValidationResult =
validateParameters(
blockParam,
@@ -163,12 +169,6 @@ public abstract class AbstractEngineNewPayload extends ExecutionEngineJsonRpcMet
return new JsonRpcErrorResponse(reqId, parameterValidationResult);
}
final ValidationResult<RpcErrorType> forkValidationResult =
validateForkSupported(blockParam.getTimestamp());
if (!forkValidationResult.isValid()) {
return new JsonRpcErrorResponse(reqId, forkValidationResult);
}
final Optional<List<VersionedHash>> maybeVersionedHashes;
try {
maybeVersionedHashes = extractVersionedHashes(maybeVersionedHashParam);

View File

@@ -59,6 +59,12 @@ public class EngineForkchoiceUpdatedV2 extends AbstractEngineForkchoiceUpdated {
return Optional.of(new JsonRpcErrorResponse(requestId, getInvalidPayloadAttributesError()));
}
ValidationResult<RpcErrorType> forkValidationResult =
validateForkSupported(payloadAttributes.getTimestamp());
if (!forkValidationResult.isValid()) {
return Optional.of(new JsonRpcErrorResponse(requestId, forkValidationResult));
}
return Optional.empty();
}

View File

@@ -81,6 +81,7 @@ public class EngineForkchoiceUpdatedV3 extends AbstractEngineForkchoiceUpdated {
@Override
protected Optional<JsonRpcErrorResponse> isPayloadAttributesValid(
final Object requestId, final EnginePayloadAttributesParameter payloadAttributes) {
if (payloadAttributes.getParentBeaconBlockRoot() == null) {
LOG.error(
"Parent beacon block root hash not present in payload attributes after cancun hardfork");
@@ -91,8 +92,10 @@ public class EngineForkchoiceUpdatedV3 extends AbstractEngineForkchoiceUpdated {
return Optional.of(new JsonRpcErrorResponse(requestId, getInvalidPayloadAttributesError()));
}
if (cancunMilestone.isEmpty() || payloadAttributes.getTimestamp() < cancunMilestone.get()) {
return Optional.of(new JsonRpcErrorResponse(requestId, RpcErrorType.UNSUPPORTED_FORK));
ValidationResult<RpcErrorType> forkValidationResult =
validateForkSupported(payloadAttributes.getTimestamp());
if (!forkValidationResult.isValid()) {
return Optional.of(new JsonRpcErrorResponse(requestId, forkValidationResult));
}
return Optional.empty();

View File

@@ -15,6 +15,7 @@
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine;
import static org.hyperledger.besu.datatypes.HardforkId.MainnetHardforkId.CANCUN;
import static org.hyperledger.besu.datatypes.HardforkId.MainnetHardforkId.PRAGUE;
import org.hyperledger.besu.consensus.merge.PayloadWrapper;
import org.hyperledger.besu.consensus.merge.blockcreation.MergeMiningCoordinator;
@@ -68,6 +69,11 @@ public class EngineGetPayloadV3 extends AbstractEngineGetPayload {
@Override
protected ValidationResult<RpcErrorType> validateForkSupported(final long blockTimestamp) {
return ForkSupportHelper.validateForkSupported(CANCUN, cancunMilestone, blockTimestamp);
return ForkSupportHelper.validateForkSupported(
CANCUN,
cancunMilestone,
PRAGUE,
protocolSchedule.flatMap(s -> s.milestoneFor(PRAGUE)),
blockTimestamp);
}
}

View File

@@ -15,6 +15,7 @@
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine;
import static org.hyperledger.besu.datatypes.HardforkId.MainnetHardforkId.CANCUN;
import static org.hyperledger.besu.datatypes.HardforkId.MainnetHardforkId.PRAGUE;
import org.hyperledger.besu.consensus.merge.blockcreation.MergeMiningCoordinator;
import org.hyperledger.besu.ethereum.ProtocolContext;
@@ -89,6 +90,11 @@ public class EngineNewPayloadV3 extends AbstractEngineNewPayload {
@Override
protected ValidationResult<RpcErrorType> validateForkSupported(final long blockTimestamp) {
return ForkSupportHelper.validateForkSupported(CANCUN, cancunMilestone, blockTimestamp);
return ForkSupportHelper.validateForkSupported(
CANCUN,
cancunMilestone,
PRAGUE,
protocolSchedule.flatMap(s -> s.milestoneFor(PRAGUE)),
blockTimestamp);
}
}

View File

@@ -21,22 +21,54 @@ import org.hyperledger.besu.ethereum.mainnet.ValidationResult;
import java.util.Optional;
public class ForkSupportHelper {
public static ValidationResult<RpcErrorType> validateForkSupported(
final HardforkId firstSupportedHardforkId,
final Optional<Long> maybeForkMilestone,
final Optional<Long> maybeFirstSupportedForkMilestone,
final long blockTimestamp) {
if (maybeForkMilestone.isEmpty()) {
if (maybeFirstSupportedForkMilestone.isEmpty()) {
return ValidationResult.invalid(
RpcErrorType.UNSUPPORTED_FORK,
"Configuration error, no schedule for " + firstSupportedHardforkId.name() + " fork set");
}
if (Long.compareUnsigned(blockTimestamp, maybeForkMilestone.get()) < 0) {
if (Long.compareUnsigned(blockTimestamp, maybeFirstSupportedForkMilestone.get()) < 0) {
return ValidationResult.invalid(
RpcErrorType.UNSUPPORTED_FORK,
firstSupportedHardforkId.name()
+ " configured to start at timestamp: "
+ maybeForkMilestone.get());
+ maybeFirstSupportedForkMilestone.get());
}
return ValidationResult.valid();
}
public static ValidationResult<RpcErrorType> validateForkSupported(
final HardforkId firstSupportedHardforkId,
final Optional<Long> maybeFirstSupportedForkMilestone,
final HardforkId firstUnsupportedHardforkId,
final Optional<Long> maybeFirstUnsupportedMilestone,
final long blockTimestamp) {
var result =
validateForkSupported(
firstSupportedHardforkId, maybeFirstSupportedForkMilestone, blockTimestamp);
if (!result.isValid()) {
return result;
}
if (maybeFirstUnsupportedMilestone.isPresent()
&& Long.compareUnsigned(blockTimestamp, maybeFirstUnsupportedMilestone.get()) >= 0) {
return ValidationResult.invalid(
RpcErrorType.UNSUPPORTED_FORK,
"block timestamp "
+ blockTimestamp
+ " is after the first unsupported milestone: "
+ firstUnsupportedHardforkId.name()
+ " at timestamp "
+ maybeFirstUnsupportedMilestone.get());
}
return ValidationResult.valid();

View File

@@ -20,6 +20,7 @@ import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.Executi
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod.EngineStatus.INVALID;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod.EngineStatus.SYNCING;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod.EngineStatus.VALID;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineTestSupport.fromErrorResp;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
@@ -43,7 +44,6 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngin
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.EnginePayloadParameter;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.WithdrawalParameter;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.EnginePayloadStatusResult;
@@ -429,14 +429,6 @@ public abstract class AbstractEngineNewPayloadTest extends AbstractScheduledApiT
.get();
}
protected JsonRpcError fromErrorResp(final JsonRpcResponse resp) {
assertThat(resp.getType()).isEqualTo(RpcResponseType.ERROR);
return Optional.of(resp)
.map(JsonRpcErrorResponse.class::cast)
.map(JsonRpcErrorResponse::getError)
.get();
}
protected BlockHeader createBlockHeader(final Optional<List<Withdrawal>> maybeWithdrawals) {
return createBlockHeaderFixture(maybeWithdrawals).buildHeader();
}

View File

@@ -44,9 +44,9 @@ public class AbstractScheduledApiTest {
protected final ScheduledProtocolSpec.Hardfork cancunHardfork =
new ScheduledProtocolSpec.Hardfork("Cancun", 30);
protected final ScheduledProtocolSpec.Hardfork pragueHardfork =
new ScheduledProtocolSpec.Hardfork("Prague", 40);
new ScheduledProtocolSpec.Hardfork("Prague", 50);
protected final ScheduledProtocolSpec.Hardfork experimentalHardfork =
new ScheduledProtocolSpec.Hardfork("ExperimentalEips", 50);
new ScheduledProtocolSpec.Hardfork("ExperimentalEips", 70);
@Mock protected DefaultProtocolSchedule protocolSchedule;

View File

@@ -15,6 +15,7 @@
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineTestSupport.fromErrorResp;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@@ -32,7 +33,6 @@ import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType;
@@ -256,12 +256,4 @@ public class EngineGetBlobsV1Test {
list.forEach(obj -> blobAndProofV1s.add((BlobAndProofV1) obj));
return blobAndProofV1s;
}
private RpcErrorType fromErrorResp(final JsonRpcResponse resp) {
assertThat(resp.getType()).isEqualTo(RpcResponseType.ERROR);
return Optional.of(resp)
.map(JsonRpcErrorResponse.class::cast)
.map(JsonRpcErrorResponse::getErrorType)
.get();
}
}

View File

@@ -15,6 +15,7 @@
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineTestSupport.fromErrorResp;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType.INVALID_RANGE_REQUEST_TOO_LARGE;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
@@ -29,10 +30,8 @@ import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlockResultFactory;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.EngineGetPayloadBodiesResultV1;
import org.hyperledger.besu.ethereum.chain.MutableBlockchain;
@@ -259,12 +258,4 @@ public class EngineGetPayloadBodiesByHashV1Test {
.map(EngineGetPayloadBodiesResultV1.class::cast)
.get();
}
private RpcErrorType fromErrorResp(final JsonRpcResponse resp) {
assertThat(resp.getType()).isEqualTo(RpcResponseType.ERROR);
return Optional.of(resp)
.map(JsonRpcErrorResponse.class::cast)
.map(JsonRpcErrorResponse::getErrorType)
.get();
}
}

View File

@@ -14,7 +14,10 @@
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine;
import static java.util.Collections.emptyList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineTestSupport.fromErrorResp;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType.UNSUPPORTED_FORK;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
@@ -30,7 +33,10 @@ import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.TransactionType;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.EngineGetPayloadResultV3;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.Quantity;
import org.hyperledger.besu.ethereum.core.BlobTestFixture;
@@ -86,6 +92,76 @@ public class EngineGetPayloadV3Test extends AbstractEngineGetPayloadTest {
assertThat(method.getName()).isEqualTo("engine_getPayloadV3");
}
@Test
public void shouldReturnUnsupportedForkIfBlockTimestampIsBeforeCancunMilestone() {
BlockHeader shanghaiHeader =
new BlockHeaderTestFixture()
.prevRandao(Bytes32.random())
.timestamp(cancunHardfork.milestone() - 1)
.excessBlobGas(BlobGas.of(10L))
.buildHeader();
PayloadIdentifier shanghaiPid =
PayloadIdentifier.forPayloadParams(
Hash.ZERO,
cancunHardfork.milestone() - 1,
Bytes32.random(),
Address.fromHexString("0x42"),
Optional.empty(),
Optional.empty());
BlockWithReceipts shanghaiBlock =
new BlockWithReceipts(
new Block(
shanghaiHeader, new BlockBody(emptyList(), emptyList(), Optional.of(emptyList()))),
emptyList());
PayloadWrapper payloadShanghai =
new PayloadWrapper(shanghaiPid, shanghaiBlock, Optional.empty());
when(mergeContext.retrievePayloadById(shanghaiPid)).thenReturn(Optional.of(payloadShanghai));
final var resp = resp(RpcMethod.ENGINE_GET_PAYLOAD_V3.getMethodName(), shanghaiPid);
assertThat(resp).isInstanceOf(JsonRpcErrorResponse.class);
assertThat(((JsonRpcErrorResponse) resp).getErrorType())
.isEqualTo(RpcErrorType.UNSUPPORTED_FORK);
}
@Test
public void shouldReturnUnsupportedForkIfBlockTimestampIsAfterPragueMilestone() {
BlockHeader pragueHeader =
new BlockHeaderTestFixture()
.prevRandao(Bytes32.random())
.timestamp(pragueHardfork.milestone())
.excessBlobGas(BlobGas.of(10L))
.buildHeader();
PayloadIdentifier postCancunPid =
PayloadIdentifier.forPayloadParams(
Hash.ZERO,
cancunHardfork.milestone(),
Bytes32.random(),
Address.fromHexString("0x42"),
Optional.empty(),
Optional.empty());
BlockWithReceipts pragueBlock =
new BlockWithReceipts(
new Block(
pragueHeader, new BlockBody(emptyList(), emptyList(), Optional.of(emptyList()))),
emptyList());
PayloadWrapper payloadPostCancun =
new PayloadWrapper(postCancunPid, pragueBlock, Optional.empty());
when(mergeContext.retrievePayloadById(postCancunPid))
.thenReturn(Optional.of(payloadPostCancun));
final var resp = resp(RpcMethod.ENGINE_GET_PAYLOAD_V3.getMethodName(), postCancunPid);
final JsonRpcError jsonRpcError = fromErrorResp(resp);
assertThat(jsonRpcError.getCode()).isEqualTo(UNSUPPORTED_FORK.getCode());
verify(engineCallListener, times(1)).executionEngineCalled();
}
@Override
@Test
public void shouldReturnBlockForKnownPayloadId() {

View File

@@ -93,6 +93,15 @@ public class EngineGetPayloadV4Test extends AbstractEngineGetPayloadTest {
assertThat(method.getName()).isEqualTo("engine_getPayloadV4");
}
@Test
public void shouldReturnUnsupportedForkIfBlockTimestampIsBeforePragueMilestone() {
final var resp = resp(RpcMethod.ENGINE_GET_PAYLOAD_V4.getMethodName(), mockPid);
assertThat(resp).isInstanceOf(JsonRpcErrorResponse.class);
assertThat(((JsonRpcErrorResponse) resp).getErrorType())
.isEqualTo(RpcErrorType.UNSUPPORTED_FORK);
}
@Override
@Test
public void shouldReturnBlockForKnownPayloadId() {
@@ -220,15 +229,6 @@ public class EngineGetPayloadV4Test extends AbstractEngineGetPayloadTest {
});
}
@Test
public void shouldReturnUnsupportedFork() {
final var resp = resp(RpcMethod.ENGINE_GET_PAYLOAD_V4.getMethodName(), mockPid);
assertThat(resp).isInstanceOf(JsonRpcErrorResponse.class);
assertThat(((JsonRpcErrorResponse) resp).getErrorType())
.isEqualTo(RpcErrorType.UNSUPPORTED_FORK);
}
@Override
protected String getMethodName() {
return RpcMethod.ENGINE_GET_PAYLOAD_V4.getMethodName();

View File

@@ -16,6 +16,7 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod.EngineStatus.INVALID;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineTestSupport.fromErrorResp;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.WithdrawalParameterTestFixture.WITHDRAWAL_PARAM_1;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType.INVALID_BLOB_GAS_USED_PARAMS;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType.INVALID_PARAMS;
@@ -34,12 +35,14 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.WithdrawalP
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.Withdrawal;
import org.hyperledger.besu.ethereum.mainnet.ScheduledProtocolSpec;
import org.hyperledger.besu.ethereum.mainnet.WithdrawalsValidator;
import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -52,6 +55,10 @@ import org.mockito.quality.Strictness;
@MockitoSettings(strictness = Strictness.LENIENT)
public class EngineNewPayloadV2Test extends AbstractEngineNewPayloadTest {
protected Set<ScheduledProtocolSpec.Hardfork> supportedHardforks() {
return Set.of(shanghaiHardfork);
}
public EngineNewPayloadV2Test() {}
@Override
@@ -177,7 +184,7 @@ public class EngineNewPayloadV2Test extends AbstractEngineNewPayloadTest {
@Test
public void shouldReturnUnsupportedForkIfBlockTimestampIsAfterCancunMilestone() {
// Cancun starte at timestamp 30
// Cancun started at timestamp 30
final long blockTimestamp = 31L;
BlockHeader blockHeader =
createBlockHeaderFixture(Optional.of(Collections.emptyList()))

View File

@@ -17,7 +17,9 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine;
import static java.util.Collections.emptyList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod.EngineStatus.INVALID;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineTestSupport.fromErrorResp;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType.INVALID_PARAMS;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType.UNSUPPORTED_FORK;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
@@ -52,6 +54,7 @@ import org.hyperledger.besu.ethereum.core.encoding.EncodingContext;
import org.hyperledger.besu.ethereum.core.encoding.TransactionEncoder;
import org.hyperledger.besu.ethereum.mainnet.BodyValidation;
import org.hyperledger.besu.ethereum.mainnet.CancunTargetingGasLimitCalculator;
import org.hyperledger.besu.ethereum.mainnet.ScheduledProtocolSpec;
import org.hyperledger.besu.ethereum.mainnet.ValidationResult;
import org.hyperledger.besu.evm.gascalculator.CancunGasCalculator;
import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem;
@@ -59,13 +62,14 @@ import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem;
import java.math.BigInteger;
import java.util.List;
import java.util.Optional;
import java.util.Random;
import java.util.Set;
import java.util.function.Supplier;
import com.google.common.base.Suppliers;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
@@ -78,6 +82,11 @@ public class EngineNewPayloadV3Test extends EngineNewPayloadV2Test {
public EngineNewPayloadV3Test() {}
@Override
protected Set<ScheduledProtocolSpec.Hardfork> supportedHardforks() {
return Set.of(cancunHardfork);
}
@Override
@Test
public void shouldReturnExpectedMethodName() {
@@ -104,6 +113,37 @@ public class EngineNewPayloadV3Test extends EngineNewPayloadV2Test {
.thenReturn(mock(CancunTargetingGasLimitCalculator.class));
}
@Override
public void shouldReturnUnsupportedForkIfBlockTimestampIsAfterCancunMilestone() {
// only relevant for v2
}
@Test
public void shouldReturnUnsupportedForkIfBlockTimestampIsAfterPragueMilestone() {
final BlockHeader pragueHeader =
createBlockHeaderFixture(Optional.of(emptyList()))
.timestamp(pragueHardfork.milestone())
.buildHeader();
var resp = resp(mockEnginePayload(pragueHeader, emptyList(), null));
final JsonRpcError jsonRpcError = fromErrorResp(resp);
assertThat(jsonRpcError.getCode()).isEqualTo(UNSUPPORTED_FORK.getCode());
verify(engineCallListener, times(1)).executionEngineCalled();
}
@Test
public void shouldReturnUnsupportedForkIfBlockTimestampIsBeforeCancunMilestone() {
final BlockHeader shanghaiHeader =
createBlockHeaderFixture(Optional.of(emptyList()))
.timestamp(cancunHardfork.milestone() - 1)
.buildHeader();
var resp = resp(mockEnginePayload(shanghaiHeader, emptyList(), null));
final JsonRpcError jsonRpcError = fromErrorResp(resp);
assertThat(jsonRpcError.getCode()).isEqualTo(UNSUPPORTED_FORK.getCode());
verify(engineCallListener, times(1)).executionEngineCalled();
}
@Test
public void shouldInvalidVersionedHash_whenShortVersionedHash() {
final Bytes shortHash = Bytes.fromHexString("0x" + "69".repeat(31));
@@ -213,6 +253,7 @@ public class EngineNewPayloadV3Test extends EngineNewPayloadV2Test {
// V3 must return error if null blobGasUsed
BlockHeader blockHeader =
createBlockHeaderFixture(Optional.of(emptyList()))
.timestamp(getTimestampForSupportedFork())
.excessBlobGas(BlobGas.MAX_BLOB_GAS)
.blobGasUsed(null)
.buildHeader();
@@ -231,6 +272,7 @@ public class EngineNewPayloadV3Test extends EngineNewPayloadV2Test {
// V3 must return error if null excessBlobGas
BlockHeader blockHeader =
createBlockHeaderFixture(Optional.of(emptyList()))
.timestamp(getTimestampForSupportedFork())
.excessBlobGas(null)
.blobGasUsed(100L)
.buildHeader();
@@ -280,12 +322,6 @@ public class EngineNewPayloadV3Test extends EngineNewPayloadV2Test {
.createTransaction(senderKeys);
}
@Override
@Disabled
public void shouldReturnUnsupportedForkIfBlockTimestampIsAfterCancunMilestone() {
// only relevant for v2
}
@Override
protected JsonRpcResponse resp(final EnginePayloadParameter payload) {
Object[] params =
@@ -295,4 +331,9 @@ public class EngineNewPayloadV3Test extends EngineNewPayloadV2Test {
return method.response(
new JsonRpcRequestContext(new JsonRpcRequest("2.0", this.method.getName(), params)));
}
protected long getTimestampForSupportedFork() {
final int index = new Random().nextInt(supportedHardforks().size());
return supportedHardforks().stream().toList().get(index).milestone();
}
}

View File

@@ -17,7 +17,9 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine;
import static java.util.Collections.emptyList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hyperledger.besu.ethereum.api.graphql.internal.response.GraphQLError.INVALID_PARAMS;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineTestSupport.fromErrorResp;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType.INVALID_EXECUTION_REQUESTS_PARAMS;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType.UNSUPPORTED_FORK;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
@@ -41,6 +43,7 @@ import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture;
import org.hyperledger.besu.ethereum.core.Request;
import org.hyperledger.besu.ethereum.core.Withdrawal;
import org.hyperledger.besu.ethereum.mainnet.BodyValidation;
import org.hyperledger.besu.ethereum.mainnet.ScheduledProtocolSpec;
import org.hyperledger.besu.ethereum.mainnet.ValidationResult;
import org.hyperledger.besu.ethereum.mainnet.requests.MainnetRequestsValidator;
import org.hyperledger.besu.ethereum.mainnet.requests.ProhibitedRequestValidator;
@@ -50,6 +53,7 @@ import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
@@ -63,6 +67,11 @@ public class EngineNewPayloadV4Test extends EngineNewPayloadV3Test {
public EngineNewPayloadV4Test() {}
@Override
protected Set<ScheduledProtocolSpec.Hardfork> supportedHardforks() {
return Set.of(pragueHardfork);
}
private static final List<Request> VALID_REQUESTS =
List.of(
new Request(RequestType.DEPOSIT, Bytes.of(1)),
@@ -93,6 +102,22 @@ public class EngineNewPayloadV4Test extends EngineNewPayloadV3Test {
assertThat(method.getName()).isEqualTo("engine_newPayloadV4");
}
@Override
public void shouldReturnUnsupportedForkIfBlockTimestampIsAfterPragueMilestone() {
// Only relevant for V3
}
@Test
public void shouldReturnUnsupportedForkIfBlockTimestampIsBeforePragueMilestone() {
final BlockHeader cancunHeader = createBlockHeaderFixtureForV3(Optional.empty()).buildHeader();
var resp = resp(mockEnginePayload(cancunHeader, emptyList()));
final JsonRpcError jsonRpcError = fromErrorResp(resp);
assertThat(jsonRpcError.getCode()).isEqualTo(UNSUPPORTED_FORK.getCode());
verify(engineCallListener, times(1)).executionEngineCalled();
}
@Test
public void shouldReturnInvalidIfRequestsIsNull_WhenRequestsAllowed() {
var resp =
@@ -173,6 +198,7 @@ public class EngineNewPayloadV4Test extends EngineNewPayloadV3Test {
final Optional<List<Withdrawal>> maybeWithdrawals) {
return createBlockHeaderFixtureForV3(maybeWithdrawals)
.requestsHash(BodyValidation.requestsHash(VALID_REQUESTS))
.timestamp(pragueHardfork.milestone())
.buildHeader();
}
@@ -181,7 +207,7 @@ public class EngineNewPayloadV4Test extends EngineNewPayloadV3Test {
BlockHeader parentBlockHeader =
new BlockHeaderTestFixture()
.baseFeePerGas(Wei.ONE)
.timestamp(pragueHardfork.milestone())
.timestamp(pragueHardfork.milestone() - 2) // cancun parent
.excessBlobGas(BlobGas.ZERO)
.blobGasUsed(0L)
.buildHeader();

View File

@@ -0,0 +1,35 @@
/*
* Copyright contributors to Besu.
*
* 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.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine;
import static org.assertj.core.api.Assertions.assertThat;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse;
import org.hyperledger.besu.plugin.services.rpc.RpcResponseType;
import java.util.Optional;
public class EngineTestSupport {
static JsonRpcError fromErrorResp(final JsonRpcResponse resp) {
assertThat(resp.getType()).isEqualTo(RpcResponseType.ERROR);
return Optional.of(resp)
.map(JsonRpcErrorResponse.class::cast)
.map(JsonRpcErrorResponse::getError)
.get();
}
}

View File

@@ -24,7 +24,7 @@ public class ExcessBlobGasCalculator {
* public class ExcessBlobGasCalculator { /** Calculates the excess blob gas for a parent block
* header.
*
* @param protocolSpec The protocol specification.
* @param protocolSpec The protocol specification of the current block.
* @param parentHeader The parent block header.
* @return The excess blob gas.
*/