Eip4788 parentBeaconBlockRoot (#5758)

Implement EIP 4788
Unit tests need to be added
Implementation is working for test net 8, address of the contract will probably change
EIP 4788 is still not finalized. Some changes might be necessary.
 
Signed-off-by: Stefan <stefan.pingel@consensys.net>
Signed-off-by: Sally MacFarlane <macfarla.github@gmail.com>
Co-authored-by: Sally MacFarlane <macfarla.github@gmail.com>
pull/5783/head
Stefan Pingel 1 year ago committed by GitHub
parent c3c7205fe8
commit 47285a48f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/BlockUtils.java
  2. 2
      besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java
  3. 12
      config/src/main/java/org/hyperledger/besu/config/GenesisConfigFile.java
  4. 5
      consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeBlockCreator.java
  5. 25
      consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeCoordinator.java
  6. 4
      consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeMiningCoordinator.java
  7. 5
      consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/TransitionCoordinator.java
  8. 20
      consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeCoordinatorTest.java
  9. 26
      datatypes/src/main/java/org/hyperledger/besu/datatypes/Address.java
  10. 1
      ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcResponseUtils.java
  11. 1
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java
  12. 30
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineForkchoiceUpdated.java
  13. 18
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineNewPayload.java
  14. 39
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdatedV3.java
  15. 34
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV3.java
  16. 4
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EnginePreparePayloadDebug.java
  17. 13
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EnginePayloadAttributesParameter.java
  18. 2
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EnginePayloadParameter.java
  19. 9
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EnginePreparePayloadParameter.java
  20. 9
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlockResult.java
  21. 10
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineGetPayloadResultV3.java
  22. 7
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/ExecutionEngineJsonRpcMethods.java
  23. 2
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGasPriceTest.java
  24. 22
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineForkchoiceUpdatedTest.java
  25. 52
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineNewPayloadTest.java
  26. 1
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineExchangeTransitionConfigurationTest.java
  27. 12
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadEIP6110Test.java
  28. 2
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV1Test.java
  29. 9
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV2Test.java
  30. 12
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV3Test.java
  31. 6
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EnginePayloadAttributesParameterTest.java
  32. 1
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/BlockchainQueriesLogCacheTest.java
  33. 2
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/cache/TransactionLogBloomCacherTest.java
  34. 21
      ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java
  35. 22
      ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreatorTest.java
  36. 8
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/GenesisState.java
  37. 60
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeader.java
  38. 15
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeaderBuilder.java
  39. 10
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/ProcessableBlockHeader.java
  40. 4
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/SealableBlockHeader.java
  41. 7
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessor.java
  42. 60
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ParentBeaconBlockRootHelper.java
  43. 8
      ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockHeaderTestFixture.java
  44. 6
      ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/NonBesuBlockHeader.java
  45. 2
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/LogRollingTests.java
  46. 2
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/GenesisStateTest.java
  47. 1
      ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/MessageWrapperTest.java
  48. 3
      ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/ChainForTestCreator.java
  49. 9
      ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BlockchainReferenceTestCaseSpec.java
  50. 3
      ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestEnv.java
  51. 62
      ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/blob
  52. 5
      ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/block
  53. 1
      ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/block3
  54. 2
      plugin-api/build.gradle
  55. 8
      plugin-api/src/main/java/org/hyperledger/besu/plugin/data/BlockHeader.java

@ -57,6 +57,7 @@ public class BlockUtils {
null,
null,
null,
null,
blockHeaderFunctions);
}
}

@ -200,7 +200,7 @@ public class BesuEventsImplTest {
mock(EthPeer.class),
new org.hyperledger.besu.ethereum.core.BlockHeader(
null, null, null, null, null, null, null, null, 1, 1, 1, 1, null, null, null, 1, null,
null, null, null, null));
null, null, null, null, null));
}
private void clearSyncTarget() {

@ -265,6 +265,18 @@ public class GenesisConfigFile {
return JsonUtil.getValueAsString(configRoot, "blobgasused", "0x0");
}
/**
* Gets parent beacon block root.
*
* @return the parent beacon block root
*/
public String getParentBeaconBlockRoot() {
return JsonUtil.getValueAsString(
configRoot,
"parentbeaconblockroot",
"0x0000000000000000000000000000000000000000000000000000000000000000");
}
/**
* Gets coinbase.
*

@ -90,19 +90,22 @@ class MergeBlockCreator extends AbstractBlockCreator {
* @param random the random
* @param timestamp the timestamp
* @param withdrawals optional list of withdrawals
* @param parentBeaconBlockRoot optional root hash of the parent beacon block
* @return the block creation result
*/
public BlockCreationResult createBlock(
final Optional<List<Transaction>> maybeTransactions,
final Bytes32 random,
final long timestamp,
final Optional<List<Withdrawal>> withdrawals) {
final Optional<List<Withdrawal>> withdrawals,
final Optional<Bytes32> parentBeaconBlockRoot) {
return createBlock(
maybeTransactions,
Optional.of(Collections.emptyList()),
withdrawals,
Optional.of(random),
parentBeaconBlockRoot,
timestamp,
false);
}

@ -246,7 +246,8 @@ public class MergeCoordinator implements MergeMiningCoordinator, BadChainListene
final Long timestamp,
final Bytes32 prevRandao,
final Address feeRecipient,
final Optional<List<Withdrawal>> withdrawals) {
final Optional<List<Withdrawal>> withdrawals,
final Optional<Bytes32> parentBeaconBlockRoot) {
// we assume that preparePayload is always called sequentially, since the RPC Engine calls
// are sequential, if this assumption changes then more synchronization should be added to
@ -273,7 +274,12 @@ public class MergeCoordinator implements MergeMiningCoordinator, BadChainListene
// put the empty block in first
final Block emptyBlock =
mergeBlockCreator
.createBlock(Optional.of(Collections.emptyList()), prevRandao, timestamp, withdrawals)
.createBlock(
Optional.of(Collections.emptyList()),
prevRandao,
timestamp,
withdrawals,
parentBeaconBlockRoot)
.getBlock();
BlockProcessingResult result = validateProposedBlock(emptyBlock);
@ -294,7 +300,13 @@ public class MergeCoordinator implements MergeMiningCoordinator, BadChainListene
}
}
tryToBuildBetterBlock(timestamp, prevRandao, payloadIdentifier, mergeBlockCreator, withdrawals);
tryToBuildBetterBlock(
timestamp,
prevRandao,
payloadIdentifier,
mergeBlockCreator,
withdrawals,
parentBeaconBlockRoot);
return payloadIdentifier;
}
@ -334,10 +346,13 @@ public class MergeCoordinator implements MergeMiningCoordinator, BadChainListene
final Bytes32 random,
final PayloadIdentifier payloadIdentifier,
final MergeBlockCreator mergeBlockCreator,
final Optional<List<Withdrawal>> withdrawals) {
final Optional<List<Withdrawal>> withdrawals,
final Optional<Bytes32> parentBeaconBlockRoot) {
final Supplier<BlockCreationResult> blockCreator =
() -> mergeBlockCreator.createBlock(Optional.empty(), random, timestamp, withdrawals);
() ->
mergeBlockCreator.createBlock(
Optional.empty(), random, timestamp, withdrawals, parentBeaconBlockRoot);
LOG.debug(
"Block creation started for payload id {}, remaining time is {}ms",

@ -41,6 +41,7 @@ public interface MergeMiningCoordinator extends MiningCoordinator {
* @param prevRandao the prev randao
* @param feeRecipient the fee recipient
* @param withdrawals the optional list of withdrawals
* @param parentBeaconBlockRoot optional root hash of the parent beacon block
* @return the payload identifier
*/
PayloadIdentifier preparePayload(
@ -48,7 +49,8 @@ public interface MergeMiningCoordinator extends MiningCoordinator {
final Long timestamp,
final Bytes32 prevRandao,
final Address feeRecipient,
final Optional<List<Withdrawal>> withdrawals);
final Optional<List<Withdrawal>> withdrawals,
final Optional<Bytes32> parentBeaconBlockRoot);
@Override
default boolean isCompatibleWithEngineApi() {

@ -147,9 +147,10 @@ public class TransitionCoordinator extends TransitionUtils<MiningCoordinator>
final Long timestamp,
final Bytes32 prevRandao,
final Address feeRecipient,
final Optional<List<Withdrawal>> withdrawals) {
final Optional<List<Withdrawal>> withdrawals,
final Optional<Bytes32> parentBeaconBlockRoot) {
return mergeCoordinator.preparePayload(
parentHeader, timestamp, prevRandao, feeRecipient, withdrawals);
parentHeader, timestamp, prevRandao, feeRecipient, withdrawals, parentBeaconBlockRoot);
}
@Override

@ -60,7 +60,6 @@ import org.hyperledger.besu.ethereum.core.Difficulty;
import org.hyperledger.besu.ethereum.core.MiningParameters;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.core.TransactionTestFixture;
import org.hyperledger.besu.ethereum.core.Withdrawal;
import org.hyperledger.besu.ethereum.eth.manager.EthContext;
import org.hyperledger.besu.ethereum.eth.sync.backwardsync.BackwardSyncContext;
import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration;
@ -122,7 +121,6 @@ public class MergeCoordinatorTest implements MergeGenesisConfigHelper {
"ae6ae8e5ccbfb04590405997ee2d52d2b330726137b875053c36d94e974d162f"));
private static final KeyPair KEYS1 =
new KeyPair(PRIVATE_KEY1, SIGNATURE_ALGORITHM.get().createPublicKey(PRIVATE_KEY1));
private static final Optional<List<Withdrawal>> EMPTY_WITHDRAWALS = Optional.empty();
private static final long REPETITION_MIN_DURATION = 100;
@Mock MergeContext mergeContext;
@ -256,7 +254,8 @@ public class MergeCoordinatorTest implements MergeGenesisConfigHelper {
System.currentTimeMillis() / 1000,
Bytes32.ZERO,
suggestedFeeRecipient,
EMPTY_WITHDRAWALS);
Optional.empty(),
Optional.empty());
ArgumentCaptor<BlockWithReceipts> blockWithReceipts =
ArgumentCaptor.forClass(BlockWithReceipts.class);
@ -294,7 +293,8 @@ public class MergeCoordinatorTest implements MergeGenesisConfigHelper {
.doThrow(new MerkleTrieException("missing leaf"))
.doCallRealMethod()
.when(beingSpiedOn)
.createBlock(any(), any(Bytes32.class), anyLong(), eq(Optional.empty()));
.createBlock(
any(), any(Bytes32.class), anyLong(), eq(Optional.empty()), eq(Optional.empty()));
return beingSpiedOn;
};
@ -330,6 +330,7 @@ public class MergeCoordinatorTest implements MergeGenesisConfigHelper {
System.currentTimeMillis() / 1000,
Bytes32.random(),
suggestedFeeRecipient,
Optional.empty(),
Optional.empty());
verify(willThrow, never()).addBadBlock(any(), any());
@ -362,6 +363,7 @@ public class MergeCoordinatorTest implements MergeGenesisConfigHelper {
System.currentTimeMillis() / 1000,
Bytes32.ZERO,
suggestedFeeRecipient,
Optional.empty(),
Optional.empty());
verify(badBlockManager, never()).addBadBlock(any(), any());
@ -394,6 +396,7 @@ public class MergeCoordinatorTest implements MergeGenesisConfigHelper {
System.currentTimeMillis() / 1000,
Bytes32.ZERO,
suggestedFeeRecipient,
Optional.empty(),
Optional.empty());
blockCreationTask.get();
@ -445,6 +448,7 @@ public class MergeCoordinatorTest implements MergeGenesisConfigHelper {
System.currentTimeMillis() / 1000,
Bytes32.ZERO,
suggestedFeeRecipient,
Optional.empty(),
Optional.empty());
blockCreationTask.get();
@ -491,6 +495,7 @@ public class MergeCoordinatorTest implements MergeGenesisConfigHelper {
System.currentTimeMillis() / 1000,
Bytes32.ZERO,
suggestedFeeRecipient,
Optional.empty(),
Optional.empty());
blockCreationTask.get();
@ -525,6 +530,7 @@ public class MergeCoordinatorTest implements MergeGenesisConfigHelper {
System.currentTimeMillis() / 1000,
Bytes32.ZERO,
suggestedFeeRecipient,
Optional.empty(),
Optional.empty());
try {
@ -567,6 +573,7 @@ public class MergeCoordinatorTest implements MergeGenesisConfigHelper {
System.currentTimeMillis() / 1000,
Bytes32.ZERO,
suggestedFeeRecipient,
Optional.empty(),
Optional.empty());
waitForBlockCreationInProgress.await();
@ -613,6 +620,7 @@ public class MergeCoordinatorTest implements MergeGenesisConfigHelper {
timestamp,
Bytes32.ZERO,
suggestedFeeRecipient,
Optional.empty(),
Optional.empty());
final CompletableFuture<Void> task1 = blockCreationTask;
@ -623,6 +631,7 @@ public class MergeCoordinatorTest implements MergeGenesisConfigHelper {
timestamp,
Bytes32.ZERO,
suggestedFeeRecipient,
Optional.empty(),
Optional.empty());
assertThat(payloadId1).isEqualTo(payloadId2);
@ -658,6 +667,7 @@ public class MergeCoordinatorTest implements MergeGenesisConfigHelper {
timestamp,
Bytes32.ZERO,
suggestedFeeRecipient,
Optional.empty(),
Optional.empty());
assertThat(coordinator.isBlockCreationCancelled(payloadId1)).isFalse();
@ -668,6 +678,7 @@ public class MergeCoordinatorTest implements MergeGenesisConfigHelper {
timestamp + 1,
Bytes32.ZERO,
suggestedFeeRecipient,
Optional.empty(),
Optional.empty());
assertThat(payloadId1).isNotEqualTo(payloadId2);
@ -697,6 +708,7 @@ public class MergeCoordinatorTest implements MergeGenesisConfigHelper {
1L,
Bytes32.ZERO,
suggestedFeeRecipient,
Optional.empty(),
Optional.empty());
ArgumentCaptor<BlockWithReceipts> blockWithReceipts =

@ -57,26 +57,30 @@ public class Address extends DelegatingBytes {
public static final Address ALTBN128_PAIRING = Address.precompiled(0x08);
/** The constant BLAKE2B_F_COMPRESSION. */
public static final Address BLAKE2B_F_COMPRESSION = Address.precompiled(0x09);
/** The constant KZG_POINT_EVAL aka POINT_EVALUATION_PRECOMPILE_ADDRESS. */
public static final Address KZG_POINT_EVAL = Address.precompiled(0xA);
/** The constant PARENT_BEACON_BLOCK_ROOT_REGISTRY aka HISTORY_STORAGE_ADDRESS. */
public static final Address PARENT_BEACON_BLOCK_ROOT_REGISTRY = Address.precompiled(0xB);
// TODO: this is not a precompile anymore. The address is correct for testnet 8. Fix after testnet
// 8 when we know what the real address is
/** The constant BLS12_G1ADD. */
public static final Address BLS12_G1ADD = Address.precompiled(0xA);
public static final Address BLS12_G1ADD = Address.precompiled(0xC);
/** The constant BLS12_G1MUL. */
public static final Address BLS12_G1MUL = Address.precompiled(0xB);
public static final Address BLS12_G1MUL = Address.precompiled(0xD);
/** The constant BLS12_G1MULTIEXP. */
public static final Address BLS12_G1MULTIEXP = Address.precompiled(0xC);
public static final Address BLS12_G1MULTIEXP = Address.precompiled(0xE);
/** The constant BLS12_G2ADD. */
public static final Address BLS12_G2ADD = Address.precompiled(0xD);
public static final Address BLS12_G2ADD = Address.precompiled(0xF);
/** The constant BLS12_G2MUL. */
public static final Address BLS12_G2MUL = Address.precompiled(0xE);
public static final Address BLS12_G2MUL = Address.precompiled(0x10);
/** The constant BLS12_G2MULTIEXP. */
public static final Address BLS12_G2MULTIEXP = Address.precompiled(0xF);
public static final Address BLS12_G2MULTIEXP = Address.precompiled(0x11);
/** The constant BLS12_PAIRING. */
public static final Address BLS12_PAIRING = Address.precompiled(0x10);
public static final Address BLS12_PAIRING = Address.precompiled(0x12);
/** The constant BLS12_MAP_FP_TO_G1. */
public static final Address BLS12_MAP_FP_TO_G1 = Address.precompiled(0x11);
public static final Address BLS12_MAP_FP_TO_G1 = Address.precompiled(0x13);
/** The constant BLS12_MAP_FP2_TO_G2. */
public static final Address BLS12_MAP_FP2_TO_G2 = Address.precompiled(0x12);
/** The constant KZG_POINT_EVAL. */
public static final Address KZG_POINT_EVAL = Address.precompiled(0x14);
public static final Address BLS12_MAP_FP2_TO_G2 = Address.precompiled(0x14);
/** The constant ZERO. */
public static final Address ZERO = Address.fromHexString("0x0");

@ -129,6 +129,7 @@ public class JsonRpcResponseUtils {
withdrawalsRoot,
null, // ToDo 4844: set with the value of blob_gas_used field
null, // ToDo 4844: set with the value of excess_blob_gas field
null, // TODO 4788: set with the value of the parent beacon block root field
depositsRoot,
blockHeaderFunctions);

@ -58,6 +58,7 @@ public enum RpcMethod {
ENGINE_NEW_PAYLOAD_V3("engine_newPayloadV3"),
ENGINE_FORKCHOICE_UPDATED_V1("engine_forkchoiceUpdatedV1"),
ENGINE_FORKCHOICE_UPDATED_V2("engine_forkchoiceUpdatedV2"),
ENGINE_FORKCHOICE_UPDATED_V3("engine_forkchoiceUpdatedV3"),
ENGINE_EXCHANGE_TRANSITION_CONFIGURATION("engine_exchangeTransitionConfigurationV1"),
ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1("engine_getPayloadBodiesByHashV1"),
ENGINE_GET_PAYLOAD_BODIES_BY_RANGE_V1("engine_getPayloadBodiesByRangeV1"),

@ -38,6 +38,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.EngineUpdateFo
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.Withdrawal;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.ScheduledProtocolSpec;
import java.util.List;
import java.util.Optional;
@ -51,6 +52,7 @@ public abstract class AbstractEngineForkchoiceUpdated extends ExecutionEngineJso
private static final Logger LOG = LoggerFactory.getLogger(AbstractEngineForkchoiceUpdated.class);
private final ProtocolSchedule protocolSchedule;
private final MergeMiningCoordinator mergeCoordinator;
private final Long cancunTimestamp;
public AbstractEngineForkchoiceUpdated(
final Vertx vertx,
@ -61,6 +63,9 @@ public abstract class AbstractEngineForkchoiceUpdated extends ExecutionEngineJso
super(vertx, protocolContext, engineCallListener);
this.protocolSchedule = protocolSchedule;
this.mergeCoordinator = mergeCoordinator;
Optional<ScheduledProtocolSpec.Hardfork> cancun =
protocolSchedule.hardforkFor(s -> s.fork().name().equalsIgnoreCase("Cancun"));
cancunTimestamp = cancun.map(ScheduledProtocolSpec.Hardfork::milestone).orElse(Long.MAX_VALUE);
}
@Override
@ -178,7 +183,8 @@ public abstract class AbstractEngineForkchoiceUpdated extends ExecutionEngineJso
payloadAttributes.getTimestamp(),
payloadAttributes.getPrevRandao(),
payloadAttributes.getSuggestedFeeRecipient(),
withdrawals));
withdrawals,
Optional.ofNullable(payloadAttributes.getParentBeaconBlockRoot())));
payloadId.ifPresent(
pid ->
@ -204,12 +210,24 @@ public abstract class AbstractEngineForkchoiceUpdated extends ExecutionEngineJso
final Optional<List<Withdrawal>> maybeWithdrawals,
final BlockHeader headBlockHeader) {
final boolean newTimestampGreaterThanHead =
payloadAttributes.getTimestamp() > headBlockHeader.getTimestamp();
return newTimestampGreaterThanHead
&& getWithdrawalsValidator(
if (payloadAttributes.getTimestamp() <= headBlockHeader.getTimestamp()) {
LOG.warn(
"Payload attributes timestamp is smaller than timestamp of header in fork choice update");
return false;
}
if (payloadAttributes.getTimestamp() < cancunTimestamp) {
LOG.warn("Payload attributes are present before cancun hardfork");
} else if (payloadAttributes.getParentBeaconBlockRoot() == null) {
LOG.warn("Parent beacon block root not present in payload attributes after cancun hardfork");
return false;
}
if (!getWithdrawalsValidator(
protocolSchedule, headBlockHeader, payloadAttributes.getTimestamp())
.validateWithdrawals(maybeWithdrawals);
.validateWithdrawals(maybeWithdrawals)) {
return false;
}
return true;
}
private JsonRpcResponse handleNonValidForkchoiceUpdate(

@ -110,8 +110,14 @@ public abstract class AbstractEngineNewPayload extends ExecutionEngineJsonRpcMet
final Object reqId = requestContext.getRequest().getId();
final ValidationResult<RpcErrorType> forkValidationResult =
validateForkSupported(reqId, blockParam);
Optional<String> maybeParentBeaconBlockRootParam =
requestContext.getOptionalParameter(2, String.class);
final Optional<Bytes32> maybeParentBeaconBlockRoot =
maybeParentBeaconBlockRootParam.map(Bytes32::fromHexString);
ValidationResult<RpcErrorType> forkValidationResult =
validateParamsAndForkSupported(
reqId, blockParam, maybeVersionedHashParam, maybeParentBeaconBlockRoot);
if (!forkValidationResult.isValid()) {
return new JsonRpcErrorResponse(reqId, forkValidationResult);
}
@ -204,6 +210,7 @@ public abstract class AbstractEngineNewPayload extends ExecutionEngineJsonRpcMet
blockParam.getExcessBlobGas() == null
? null
: BlobGas.fromHexString(blockParam.getExcessBlobGas()),
maybeParentBeaconBlockRoot.orElse(null),
maybeDeposits.map(BodyValidation::depositsRoot).orElse(null),
headerFunctions);
@ -391,8 +398,11 @@ public abstract class AbstractEngineNewPayload extends ExecutionEngineJsonRpcMet
return INVALID;
}
protected ValidationResult<RpcErrorType> validateForkSupported(
final Object id, final EnginePayloadParameter payloadParameter) {
protected ValidationResult<RpcErrorType> validateParamsAndForkSupported(
final Object id,
final EnginePayloadParameter payloadParameter,
final Optional<List<String>> maybeVersionedHashParam,
final Optional<Bytes32> parentBeaconBlockRoot) {
return ValidationResult.valid();
}

@ -0,0 +1,39 @@
/*
* Copyright Hyperledger Besu Contributors.
*
* 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 org.hyperledger.besu.consensus.merge.blockcreation.MergeMiningCoordinator;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import io.vertx.core.Vertx;
public class EngineForkchoiceUpdatedV3 extends AbstractEngineForkchoiceUpdated {
public EngineForkchoiceUpdatedV3(
final Vertx vertx,
final ProtocolSchedule protocolSchedule,
final ProtocolContext protocolContext,
final MergeMiningCoordinator mergeCoordinator,
final EngineCallListener engineCallListener) {
super(vertx, protocolSchedule, protocolContext, mergeCoordinator, engineCallListener);
}
@Override
public String getName() {
return RpcMethod.ENGINE_FORKCHOICE_UPDATED_V3.getMethodName();
}
}

@ -21,13 +21,18 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.EnginePaylo
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType;
import org.hyperledger.besu.ethereum.eth.manager.EthPeers;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.ScheduledProtocolSpec;
import org.hyperledger.besu.ethereum.mainnet.ValidationResult;
import java.util.List;
import java.util.Optional;
import io.vertx.core.Vertx;
import org.apache.tuweni.bytes.Bytes32;
public class EngineNewPayloadV3 extends AbstractEngineNewPayload {
private final ProtocolSchedule timestampSchedule;
private final Long cancunTimestamp;
public EngineNewPayloadV3(
final Vertx vertx,
@ -38,7 +43,9 @@ public class EngineNewPayloadV3 extends AbstractEngineNewPayload {
final EngineCallListener engineCallListener) {
super(
vertx, timestampSchedule, protocolContext, mergeCoordinator, ethPeers, engineCallListener);
this.timestampSchedule = timestampSchedule;
Optional<ScheduledProtocolSpec.Hardfork> cancun =
timestampSchedule.hardforkFor(s -> s.fork().name().equalsIgnoreCase("Cancun"));
cancunTimestamp = cancun.map(ScheduledProtocolSpec.Hardfork::milestone).orElse(Long.MAX_VALUE);
}
@Override
@ -47,19 +54,24 @@ public class EngineNewPayloadV3 extends AbstractEngineNewPayload {
}
@Override
protected ValidationResult<RpcErrorType> validateForkSupported(
final Object reqId, final EnginePayloadParameter payloadParameter) {
var cancun = timestampSchedule.hardforkFor(s -> s.fork().name().equalsIgnoreCase("Cancun"));
protected ValidationResult<RpcErrorType> validateParamsAndForkSupported(
final Object reqId,
final EnginePayloadParameter payloadParameter,
final Optional<List<String>> maybeVersionedHashParam,
final Optional<Bytes32> maybeParentBeaconBlockRoot) {
if (cancun.isPresent() && payloadParameter.getTimestamp() >= cancun.get().milestone()) {
if (payloadParameter.getBlobGasUsed() == null
|| payloadParameter.getExcessBlobGas() == null) {
if (payloadParameter.getBlobGasUsed() == null || payloadParameter.getExcessBlobGas() == null) {
return ValidationResult.invalid(RpcErrorType.INVALID_PARAMS, "Missing blob gas fields");
} else {
return ValidationResult.valid();
} else if (maybeVersionedHashParam == null) {
return ValidationResult.invalid(
RpcErrorType.INVALID_PARAMS, "Missing versioned hashes field");
} else if (maybeParentBeaconBlockRoot.isEmpty()) {
return ValidationResult.invalid(
RpcErrorType.INVALID_PARAMS, "Missing parent beacon block root field");
}
} else {
if (payloadParameter.getTimestamp() < cancunTimestamp) {
return ValidationResult.invalid(RpcErrorType.UNSUPPORTED_FORK, "Fork not supported");
}
return ValidationResult.valid();
}
}

@ -67,6 +67,7 @@ public class EnginePreparePayloadDebug extends ExecutionEngineJsonRpcMethod {
Optional.empty(),
Optional.empty(),
Optional.empty(),
Optional.empty(),
Optional.empty()));
final var requestId = requestContext.getRequest().getId();
@ -99,6 +100,7 @@ public class EnginePreparePayloadDebug extends ExecutionEngineJsonRpcMethod {
param.getTimestamp().orElse(parentHeader.getTimestamp() + 1L),
param.getPrevRandao(),
param.getFeeRecipient(),
Optional.of(withdrawals)));
Optional.of(withdrawals),
param.getParentBeaconBlockRoot()));
}
}

@ -30,17 +30,21 @@ public class EnginePayloadAttributesParameter {
final Bytes32 prevRandao;
final Address suggestedFeeRecipient;
final List<WithdrawalParameter> withdrawals;
private final Bytes32 parentBeaconBlockRoot;
@JsonCreator
public EnginePayloadAttributesParameter(
@JsonProperty("timestamp") final String timestamp,
@JsonProperty("prevRandao") final String prevRandao,
@JsonProperty("suggestedFeeRecipient") final String suggestedFeeRecipient,
@JsonProperty("withdrawals") final List<WithdrawalParameter> withdrawals) {
@JsonProperty("withdrawals") final List<WithdrawalParameter> withdrawals,
@JsonProperty("parentBeaconBlockRoot") final String parentBeaconBlockRoot) {
this.timestamp = Long.decode(timestamp);
this.prevRandao = Bytes32.fromHexString(prevRandao);
this.suggestedFeeRecipient = Address.fromHexString(suggestedFeeRecipient);
this.withdrawals = withdrawals;
this.parentBeaconBlockRoot =
parentBeaconBlockRoot == null ? null : Bytes32.fromHexString(parentBeaconBlockRoot);
}
public Long getTimestamp() {
@ -55,6 +59,10 @@ public class EnginePayloadAttributesParameter {
return suggestedFeeRecipient;
}
public Bytes32 getParentBeaconBlockRoot() {
return parentBeaconBlockRoot;
}
public List<WithdrawalParameter> getWithdrawals() {
return withdrawals;
}
@ -70,6 +78,9 @@ public class EnginePayloadAttributesParameter {
"withdrawals",
withdrawals.stream().map(WithdrawalParameter::asJsonObject).collect(Collectors.toList()));
}
if (parentBeaconBlockRoot != null) {
json.put("parentBeaconBlockRoot", parentBeaconBlockRoot.toHexString());
}
return json.encode();
}
}

@ -64,7 +64,7 @@ public class EnginePayloadParameter {
@JsonProperty("gasUsed") final UnsignedLongParameter gasUsed,
@JsonProperty("timestamp") final UnsignedLongParameter timestamp,
@JsonProperty("extraData") final String extraData,
@JsonProperty("receiptRoot") final Hash receiptsRoot,
@JsonProperty("receiptsRoot") final Hash receiptsRoot,
@JsonProperty("logsBloom") final LogsBloomFilter logsBloom,
@JsonProperty("prevRandao") final String prevRandao,
@JsonProperty("transactions") final List<String> transactions,

@ -31,6 +31,7 @@ public class EnginePreparePayloadParameter {
private final Bytes32 prevRandao;
private final Optional<Long> timestamp;
final List<WithdrawalParameter> withdrawals;
private final Optional<Bytes32> parentBeaconBlockRoot;
@JsonCreator
public EnginePreparePayloadParameter(
@ -38,12 +39,14 @@ public class EnginePreparePayloadParameter {
@JsonProperty("feeRecipient") final Optional<Address> feeRecipient,
@JsonProperty("timestamp") final Optional<UnsignedLongParameter> timestamp,
@JsonProperty("prevRandao") final Optional<String> prevRandao,
@JsonProperty("withdrawals") final Optional<List<WithdrawalParameter>> withdrawals) {
@JsonProperty("withdrawals") final Optional<List<WithdrawalParameter>> withdrawals,
@JsonProperty("parentBeaconBlockRoot") final Optional<Bytes32> parentBeaconBlockRoot) {
this.parentHash = parentHash;
this.feeRecipient = feeRecipient.orElse(Address.ZERO);
this.timestamp = timestamp.map(UnsignedLongParameter::getValue);
this.prevRandao = Bytes32.fromHexStringLenient(prevRandao.orElse("deadbeef"));
this.withdrawals = withdrawals.orElse(Collections.emptyList());
this.parentBeaconBlockRoot = parentBeaconBlockRoot;
}
public Optional<Hash> getParentHash() {
@ -65,4 +68,8 @@ public class EnginePreparePayloadParameter {
public List<WithdrawalParameter> getWithdrawals() {
return withdrawals;
}
public Optional<Bytes32> getParentBeaconBlockRoot() {
return parentBeaconBlockRoot;
}
}

@ -30,6 +30,7 @@ import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.databind.JsonNode;
import org.apache.tuweni.bytes.Bytes32;
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({
@ -86,6 +87,7 @@ public class BlockResult implements JsonRpcResult {
private final String blobGasUsed;
private final String excessBlobGas;
private final String parentBeaconBlockRoot;
public BlockResult(
final BlockHeader header,
@ -134,6 +136,8 @@ public class BlockResult implements JsonRpcResult {
this.blobGasUsed = header.getBlobGasUsed().map(Quantity::create).orElse(null);
this.excessBlobGas = header.getExcessBlobGas().map(Quantity::create).orElse(null);
this.parentBeaconBlockRoot =
header.getParentBeaconBlockRoot().map(Bytes32::toHexString).orElse(null);
}
@JsonGetter(value = "number")
@ -266,4 +270,9 @@ public class BlockResult implements JsonRpcResult {
public String getExcessBlobGas() {
return excessBlobGas;
}
@JsonGetter(value = "parentBeaconBlockRoot")
public String getParentBeaconBlockRoot() {
return parentBeaconBlockRoot;
}
}

@ -81,10 +81,9 @@ public class EngineGetPayloadResultV3 {
private final String timestamp;
private final String extraData;
private final String baseFeePerGas;
private final String excessBlobGas;
private final String blobGasUsed;
private final String parentBeaconBlockRoot;
protected final List<String> transactions;
private final List<WithdrawalParameter> withdrawals;
@ -118,6 +117,8 @@ public class EngineGetPayloadResultV3 {
this.blobGasUsed = header.getBlobGasUsed().map(Quantity::create).orElse(Quantity.HEX_ZERO);
this.excessBlobGas =
header.getExcessBlobGas().map(Quantity::create).orElse(Quantity.HEX_ZERO);
this.parentBeaconBlockRoot =
header.getParentBeaconBlockRoot().map(Bytes32::toHexString).orElse(null);
}
@JsonGetter(value = "blockNumber")
@ -205,5 +206,10 @@ public class EngineGetPayloadResultV3 {
public String getBlobGasUseds() {
return blobGasUsed;
}
@JsonGetter(value = "parentBeaconBlockRoot")
public String getParentBeaconBlockRoot() {
return parentBeaconBlockRoot;
}
}
}

@ -22,6 +22,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineE
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineExchangeTransitionConfiguration;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineForkchoiceUpdatedV1;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineForkchoiceUpdatedV2;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineForkchoiceUpdatedV3;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineGetPayloadBodiesByHashV1;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineGetPayloadBodiesByRangeV1;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineGetPayloadV1;
@ -128,6 +129,12 @@ public class ExecutionEngineJsonRpcMethods extends ApiGroupJsonRpcMethods {
protocolContext,
mergeCoordinator.get(),
engineQosTimer),
new EngineForkchoiceUpdatedV3(
consensusEngineServer,
protocolSchedule,
protocolContext,
mergeCoordinator.get(),
engineQosTimer),
new EngineExchangeTransitionConfiguration(
consensusEngineServer, protocolContext, engineQosTimer),
new EngineGetPayloadBodiesByHashV1(

@ -165,6 +165,7 @@ public class EthGasPriceTest {
null,
null,
null,
null,
null),
new BlockBody(
List.of(
@ -206,6 +207,7 @@ public class EthGasPriceTest {
null,
null,
null,
null,
null),
new BlockBody(List.of(), List.of())));
}

@ -243,6 +243,7 @@ public abstract class AbstractEngineForkchoiceUpdatedTest {
String.valueOf(System.currentTimeMillis()),
Bytes32.fromHexStringLenient("0xDEADBEEF").toHexString(),
Address.ECREC.toString(),
null,
null);
var mockPayloadId =
PayloadIdentifier.forPayloadParams(
@ -257,6 +258,7 @@ public abstract class AbstractEngineForkchoiceUpdatedTest {
payloadParams.getTimestamp(),
payloadParams.getPrevRandao(),
Address.ECREC,
Optional.empty(),
Optional.empty()))
.thenReturn(mockPayloadId);
@ -432,6 +434,7 @@ public abstract class AbstractEngineForkchoiceUpdatedTest {
String.valueOf(System.currentTimeMillis()),
Bytes32.fromHexStringLenient("0xDEADBEEF").toHexString(),
Address.ECREC.toString(),
null,
null);
var resp =
@ -443,7 +446,7 @@ public abstract class AbstractEngineForkchoiceUpdatedTest {
var forkchoiceRes = (EngineUpdateForkchoiceResult) resp.getResult();
verify(mergeCoordinator, never()).preparePayload(any(), any(), any(), any(), any());
verify(mergeCoordinator, never()).preparePayload(any(), any(), any(), any(), any(), any());
assertThat(forkchoiceRes.getPayloadStatus().getStatus()).isEqualTo(VALID);
assertThat(forkchoiceRes.getPayloadStatus().getError()).isNull();
@ -468,7 +471,8 @@ public abstract class AbstractEngineForkchoiceUpdatedTest {
String.valueOf(timestampNotGreaterThanHead),
Bytes32.fromHexStringLenient("0xDEADBEEF").toHexString(),
Address.ECREC.toString(),
emptyList());
emptyList(),
null);
var resp =
resp(
@ -492,7 +496,8 @@ public abstract class AbstractEngineForkchoiceUpdatedTest {
String.valueOf(System.currentTimeMillis()),
Bytes32.fromHexStringLenient("0xDEADBEEF").toHexString(),
Address.ECREC.toString(),
emptyList());
emptyList(),
null);
var resp =
resp(
@ -516,6 +521,7 @@ public abstract class AbstractEngineForkchoiceUpdatedTest {
String.valueOf(System.currentTimeMillis()),
Bytes32.fromHexStringLenient("0xDEADBEEF").toHexString(),
Address.ECREC.toString(),
null,
null);
var mockPayloadId =
@ -531,6 +537,7 @@ public abstract class AbstractEngineForkchoiceUpdatedTest {
payloadParams.getTimestamp(),
payloadParams.getPrevRandao(),
Address.ECREC,
Optional.empty(),
Optional.empty()))
.thenReturn(mockPayloadId);
@ -557,6 +564,7 @@ public abstract class AbstractEngineForkchoiceUpdatedTest {
String.valueOf(System.currentTimeMillis()),
Bytes32.fromHexStringLenient("0xDEADBEEF").toHexString(),
Address.ECREC.toString(),
null,
null);
var resp =
@ -592,7 +600,8 @@ public abstract class AbstractEngineForkchoiceUpdatedTest {
String.valueOf(System.currentTimeMillis()),
Bytes32.fromHexStringLenient("0xDEADBEEF").toHexString(),
Address.ECREC.toString(),
withdrawalParameters);
withdrawalParameters,
null);
final Optional<List<Withdrawal>> withdrawals =
Optional.of(
@ -613,7 +622,8 @@ public abstract class AbstractEngineForkchoiceUpdatedTest {
payloadParams.getTimestamp(),
payloadParams.getPrevRandao(),
Address.ECREC,
withdrawals))
withdrawals,
Optional.empty()))
.thenReturn(mockPayloadId);
assertSuccessWithPayloadForForkchoiceResult(
@ -638,6 +648,7 @@ public abstract class AbstractEngineForkchoiceUpdatedTest {
String.valueOf(System.currentTimeMillis()),
Bytes32.fromHexStringLenient("0xDEADBEEF").toHexString(),
Address.ECREC.toString(),
null,
null);
var mockPayloadId =
@ -653,6 +664,7 @@ public abstract class AbstractEngineForkchoiceUpdatedTest {
payloadParams.getTimestamp(),
payloadParams.getPrevRandao(),
Address.ECREC,
Optional.empty(),
Optional.empty()))
.thenReturn(mockPayloadId);

@ -98,6 +98,7 @@ public abstract class AbstractEngineNewPayloadTest {
new ScheduledProtocolSpec.Hardfork("Cancun", 30);
protected final ScheduledProtocolSpec.Hardfork experimentalHardfork =
new ScheduledProtocolSpec.Hardfork("Experimental", 40);
protected Optional<Bytes32> maybeParentBeaconBlockRoot = Optional.empty();
@FunctionalInterface
interface MethodFactory {
@ -183,7 +184,7 @@ public abstract class AbstractEngineNewPayloadTest {
lenient()
.when(blockchain.getBlockHeader(mockHeader.getParentHash()))
.thenReturn(Optional.of(mock(BlockHeader.class)));
var resp = resp(mockPayload(mockHeader, Collections.emptyList()));
var resp = resp(mockEnginePayload(mockHeader, Collections.emptyList()));
assertValidResponse(mockHeader, resp);
}
@ -196,7 +197,7 @@ public abstract class AbstractEngineNewPayloadTest {
lenient()
.when(blockchain.getBlockHeader(mockHeader.getParentHash()))
.thenReturn(Optional.of(mock(BlockHeader.class)));
var resp = resp(mockPayload(mockHeader, Collections.emptyList()));
var resp = resp(mockEnginePayload(mockHeader, Collections.emptyList()));
EnginePayloadStatusResult res = fromSuccessResp(resp);
assertThat(res.getLatestValidHash().get()).isEqualTo(mockHash);
@ -219,7 +220,7 @@ public abstract class AbstractEngineNewPayloadTest {
.thenReturn(true);
}
var resp = resp(mockPayload(mockHeader, Collections.emptyList()));
var resp = resp(mockEnginePayload(mockHeader, Collections.emptyList()));
EnginePayloadStatusResult res = fromSuccessResp(resp);
assertThat(res.getLatestValidHash()).isEmpty();
@ -236,7 +237,7 @@ public abstract class AbstractEngineNewPayloadTest {
when(blockchain.getBlockByHash(any())).thenReturn(Optional.of(mockBlock));
var resp = resp(mockPayload(mockHeader, Collections.emptyList()));
var resp = resp(mockEnginePayload(mockHeader, Collections.emptyList()));
assertValidResponse(mockHeader, resp);
}
@ -251,7 +252,7 @@ public abstract class AbstractEngineNewPayloadTest {
when(mergeCoordinator.getLatestValidHashOfBadBlock(mockHeader.getHash()))
.thenReturn(Optional.of(latestValidHash));
var resp = resp(mockPayload(mockHeader, Collections.emptyList()));
var resp = resp(mockEnginePayload(mockHeader, Collections.emptyList()));
EnginePayloadStatusResult res = fromSuccessResp(resp);
assertThat(res.getLatestValidHash()).isEqualTo(Optional.of(latestValidHash));
@ -269,7 +270,7 @@ public abstract class AbstractEngineNewPayloadTest {
lenient()
.when(blockchain.getBlockHeader(mockHeader.getParentHash()))
.thenReturn(Optional.of(mock(BlockHeader.class)));
var resp = resp(mockPayload(mockHeader, Collections.emptyList()));
var resp = resp(mockEnginePayload(mockHeader, Collections.emptyList()));
fromErrorResp(resp);
verify(engineCallListener, times(1)).executionEngineCalled();
@ -288,7 +289,7 @@ public abstract class AbstractEngineNewPayloadTest {
lenient()
.when(blockchain.getBlockHeader(mockHeader.getParentHash()))
.thenReturn(Optional.of(mock(BlockHeader.class)));
var resp = resp(mockPayload(mockHeader, Collections.emptyList()));
var resp = resp(mockEnginePayload(mockHeader, Collections.emptyList()));
verify(engineCallListener, times(1)).executionEngineCalled();
verify(mergeCoordinator, times(0)).addBadBlock(any(), any());
@ -311,7 +312,7 @@ public abstract class AbstractEngineNewPayloadTest {
}
when(mergeCoordinator.rememberBlock(any())).thenThrow(new MerkleTrieException("missing leaf"));
var resp = resp(mockPayload(mockHeader, Collections.emptyList()));
var resp = resp(mockEnginePayload(mockHeader, Collections.emptyList()));
verify(engineCallListener, times(1)).executionEngineCalled();
verify(mergeCoordinator, never()).addBadBlock(any(), any());
@ -332,7 +333,7 @@ public abstract class AbstractEngineNewPayloadTest {
.when(mergeCoordinator.latestValidAncestorDescendsFromTerminal(any(BlockHeader.class)))
.thenReturn(true);
lenient().when(mockHeader.getHash()).thenReturn(Hash.fromHexStringLenient("0x1337"));
var resp = resp(mockPayload(mockHeader, Collections.emptyList()));
var resp = resp(mockEnginePayload(mockHeader, Collections.emptyList()));
EnginePayloadStatusResult res = fromSuccessResp(resp);
assertThat(res.getStatusAsString()).isEqualTo(getExpectedInvalidBlockHashStatus().name());
@ -345,7 +346,7 @@ public abstract class AbstractEngineNewPayloadTest {
BlockHeader paramHeader = spy(realHeader);
when(paramHeader.getHash()).thenReturn(Hash.fromHexStringLenient("0x1337"));
var resp = resp(mockPayload(paramHeader, Collections.emptyList()));
var resp = resp(mockEnginePayload(paramHeader, Collections.emptyList()));
EnginePayloadStatusResult res = fromSuccessResp(resp);
assertThat(res.getLatestValidHash()).isEmpty();
@ -359,7 +360,7 @@ public abstract class AbstractEngineNewPayloadTest {
when(mergeCoordinator.getLatestValidAncestor(any(Hash.class)))
.thenReturn(Optional.of(mockHash));
var resp = resp(mockPayload(mockHeader, List.of("0xDEAD", "0xBEEF")));
var resp = resp(mockEnginePayload(mockHeader, List.of("0xDEAD", "0xBEEF")));
EnginePayloadStatusResult res = fromSuccessResp(resp);
assertThat(res.getLatestValidHash().get()).isEqualTo(mockHash);
@ -372,7 +373,7 @@ public abstract class AbstractEngineNewPayloadTest {
public void shouldRespondWithSyncingDuringForwardSync() {
BlockHeader mockHeader = createBlockHeader(Optional.empty(), Optional.empty());
when(mergeContext.isSyncing()).thenReturn(Boolean.TRUE);
var resp = resp(mockPayload(mockHeader, Collections.emptyList()));
var resp = resp(mockEnginePayload(mockHeader, Collections.emptyList()));
EnginePayloadStatusResult res = fromSuccessResp(resp);
assertThat(res.getError()).isNull();
@ -386,7 +387,7 @@ public abstract class AbstractEngineNewPayloadTest {
BlockHeader mockHeader = createBlockHeader(Optional.empty(), Optional.empty());
when(mergeCoordinator.appendNewPayloadToSync(any()))
.thenReturn(CompletableFuture.completedFuture(null));
var resp = resp(mockPayload(mockHeader, Collections.emptyList()));
var resp = resp(mockEnginePayload(mockHeader, Collections.emptyList()));
EnginePayloadStatusResult res = fromSuccessResp(resp);
assertThat(res.getLatestValidHash()).isEmpty();
@ -402,7 +403,7 @@ public abstract class AbstractEngineNewPayloadTest {
when(paramHeader.getHash()).thenReturn(Hash.fromHexStringLenient("0x1337"));
when(paramHeader.getExtraData().toHexString()).thenReturn(null);
var resp = resp(mockPayload(paramHeader, Collections.emptyList()));
var resp = resp(mockEnginePayload(paramHeader, Collections.emptyList()));
EnginePayloadStatusResult res = fromSuccessResp(resp);
assertThat(res.getLatestValidHash()).isEmpty();
@ -415,7 +416,7 @@ public abstract class AbstractEngineNewPayloadTest {
public void shouldReturnInvalidWhenBadBlock() {
when(mergeCoordinator.isBadBlock(any(Hash.class))).thenReturn(true);
BlockHeader mockHeader = createBlockHeader(Optional.empty(), Optional.empty());
var resp = resp(mockPayload(mockHeader, Collections.emptyList()));
var resp = resp(mockEnginePayload(mockHeader, Collections.emptyList()));
when(protocolSpec.getWithdrawalsValidator())
.thenReturn(new WithdrawalsValidator.AllowedWithdrawals());
EnginePayloadStatusResult res = fromSuccessResp(resp);
@ -436,27 +437,31 @@ public abstract class AbstractEngineNewPayloadTest {
lenient()
.when(blockchain.getBlockHeader(mockHeader.getParentHash()))
.thenReturn(Optional.of(mock(BlockHeader.class)));
var resp = resp(mockPayload(mockHeader, Collections.emptyList()));
var resp = resp(mockEnginePayload(mockHeader, Collections.emptyList()));
assertValidResponse(mockHeader, resp);
}
protected JsonRpcResponse resp(final EnginePayloadParameter payload) {
Object[] params =
maybeParentBeaconBlockRoot
.map(bytes32 -> new Object[] {payload, null, bytes32.toHexString()})
.orElseGet(() -> new Object[] {payload});
return method.response(
new JsonRpcRequestContext(
new JsonRpcRequest("2.0", this.method.getName(), new Object[] {payload})));
new JsonRpcRequestContext(new JsonRpcRequest("2.0", this.method.getName(), params)));
}
protected EnginePayloadParameter mockPayload(final BlockHeader header, final List<String> txs) {
return mockPayload(header, txs, null, null, null);
protected EnginePayloadParameter mockEnginePayload(
final BlockHeader header, final List<String> txs) {
return mockEnginePayload(header, txs, null, null, null);
}
protected EnginePayloadParameter mockPayload(
protected EnginePayloadParameter mockEnginePayload(
final BlockHeader header,
final List<String> txs,
final List<WithdrawalParameter> withdrawals,
final List<DepositParameter> deposits) {
return mockPayload(
return mockEnginePayload(
header,
txs,
withdrawals,
@ -464,7 +469,7 @@ public abstract class AbstractEngineNewPayloadTest {
List.of(VersionedHash.DEFAULT_VERSIONED_HASH.toBytes()));
}
protected EnginePayloadParameter mockPayload(
protected EnginePayloadParameter mockEnginePayload(
final BlockHeader header,
final List<String> txs,
final List<WithdrawalParameter> withdrawals,
@ -550,6 +555,7 @@ public abstract class AbstractEngineNewPayloadTest {
.timestamp(parentBlockHeader.getTimestamp() + 1)
.withdrawalsRoot(maybeWithdrawals.map(BodyValidation::withdrawalsRoot).orElse(null))
.depositsRoot(maybeDeposits.map(BodyValidation::depositsRoot).orElse(null))
.parentBeaconBlockRoot(maybeParentBeaconBlockRoot)
.buildHeader();
return mockHeader;
}

@ -253,6 +253,7 @@ public class EngineExchangeTransitionConfigurationTest {
null,
null,
null,
null,
new BlockHeaderFunctions() {
@Override
public Hash hash(final BlockHeader header) {

@ -44,6 +44,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Optional;
import org.apache.tuweni.bytes.Bytes32;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ -60,6 +61,7 @@ public class EngineNewPayloadEIP6110Test extends EngineNewPayloadV3Test {
@Override
public void before() {
super.before();
maybeParentBeaconBlockRoot = Optional.of(Bytes32.ZERO);
this.method =
new EngineNewPayloadV3(
vertx,
@ -96,7 +98,7 @@ public class EngineNewPayloadEIP6110Test extends EngineNewPayloadV3Test {
.thenReturn(Optional.of(mockHeader.getHash()));
when(mergeCoordinator.latestValidAncestorDescendsFromTerminal(mockHeader)).thenReturn(true);
var resp = resp(mockPayload(mockHeader, Collections.emptyList(), null, deposits, null));
var resp = resp(mockEnginePayload(mockHeader, Collections.emptyList(), null, deposits, null));
assertValidResponse(mockHeader, resp);
}
@ -110,7 +112,7 @@ public class EngineNewPayloadEIP6110Test extends EngineNewPayloadV3Test {
var resp =
resp(
mockPayload(
mockEnginePayload(
createBlockHeader(Optional.empty(), Optional.empty()),
Collections.emptyList(),
null,
@ -136,7 +138,7 @@ public class EngineNewPayloadEIP6110Test extends EngineNewPayloadV3Test {
when(mergeCoordinator.getLatestValidAncestor(mockHeader))
.thenReturn(Optional.of(mockHeader.getHash()));
when(mergeCoordinator.latestValidAncestorDescendsFromTerminal(mockHeader)).thenReturn(true);
var resp = resp(mockPayload(mockHeader, Collections.emptyList(), null, depositsParam));
var resp = resp(mockEnginePayload(mockHeader, Collections.emptyList(), null, depositsParam));
assertValidResponse(mockHeader, resp);
}
@ -150,7 +152,7 @@ public class EngineNewPayloadEIP6110Test extends EngineNewPayloadV3Test {
var resp =
resp(
mockPayload(
mockEnginePayload(
createBlockHeader(Optional.empty(), Optional.of(Collections.emptyList())),
Collections.emptyList(),
null,
@ -183,6 +185,8 @@ public class EngineNewPayloadEIP6110Test extends EngineNewPayloadV3Test {
.excessBlobGas(BlobGas.ZERO)
.blobGasUsed(100L)
.depositsRoot(maybeDeposits.map(BodyValidation::depositsRoot).orElse(null))
.parentBeaconBlockRoot(
maybeParentBeaconBlockRoot.isPresent() ? maybeParentBeaconBlockRoot : null)
.buildHeader();
return mockHeader;
}

@ -74,7 +74,7 @@ public class EngineNewPayloadV1Test extends AbstractEngineNewPayloadTest {
when(mergeCoordinator.latestValidAncestorDescendsFromTerminal(any(BlockHeader.class)))
.thenReturn(false);
var resp = resp(mockPayload(mockHeader, Collections.emptyList()));
var resp = resp(mockEnginePayload(mockHeader, Collections.emptyList()));
EnginePayloadStatusResult res = fromSuccessResp(resp);
assertThat(res.getLatestValidHash()).isEqualTo(Optional.of(Hash.ZERO));

@ -84,7 +84,7 @@ public class EngineNewPayloadV2Test extends AbstractEngineNewPayloadTest {
lenient()
.when(blockchain.getBlockHeader(mockHeader.getParentHash()))
.thenReturn(Optional.of(mock(BlockHeader.class)));
var resp = resp(mockPayload(mockHeader, Collections.emptyList(), withdrawalsParam, null));
var resp = resp(mockEnginePayload(mockHeader, Collections.emptyList(), withdrawalsParam, null));
assertValidResponse(mockHeader, resp);
}
@ -102,7 +102,8 @@ public class EngineNewPayloadV2Test extends AbstractEngineNewPayloadTest {
lenient()
.when(blockchain.getBlockHeader(mockHeader.getParentHash()))
.thenReturn(Optional.of(mock(BlockHeader.class)));
var resp = resp(mockPayload(mockHeader, Collections.emptyList(), withdrawals, null, null));
var resp =
resp(mockEnginePayload(mockHeader, Collections.emptyList(), withdrawals, null, null));
assertValidResponse(mockHeader, resp);
}
@ -116,7 +117,7 @@ public class EngineNewPayloadV2Test extends AbstractEngineNewPayloadTest {
var resp =
resp(
mockPayload(
mockEnginePayload(
createBlockHeader(Optional.of(Collections.emptyList()), Optional.empty()),
Collections.emptyList(),
withdrawals,
@ -136,7 +137,7 @@ public class EngineNewPayloadV2Test extends AbstractEngineNewPayloadTest {
var resp =
resp(
mockPayload(
mockEnginePayload(
createBlockHeader(Optional.empty(), Optional.empty()),
Collections.emptyList(),
withdrawals,

@ -39,6 +39,7 @@ import java.util.List;
import java.util.Optional;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ -59,6 +60,7 @@ public class EngineNewPayloadV3Test extends EngineNewPayloadV2Test {
@Override
public void before() {
super.before();
maybeParentBeaconBlockRoot = Optional.of(Bytes32.ZERO);
this.method =
new EngineNewPayloadV3(
vertx,
@ -85,7 +87,11 @@ public class EngineNewPayloadV3Test extends EngineNewPayloadV2Test {
new JsonRpcRequest(
"2.0",
RpcMethod.ENGINE_NEW_PAYLOAD_V3.getMethodName(),
new Object[] {payload, List.of(shortHash.toHexString())})));
new Object[] {
payload,
List.of(shortHash.toHexString()),
"0x0000000000000000000000000000000000000000000000000000000000000000"
})));
EnginePayloadStatusResult res = fromSuccessResp(badParam);
assertThat(res.getStatusAsString()).isEqualTo(INVALID.name());
assertThat(res.getError()).isEqualTo("Invalid versionedHash");
@ -100,7 +106,7 @@ public class EngineNewPayloadV3Test extends EngineNewPayloadV2Test {
.baseFeePerGas(Wei.ONE)
.timestamp(super.cancunHardfork.milestone())
.buildHeader();
// protocolContext.getBlockchain().getBlockHeader(blockParam.getParentHash());
when(blockchain.getBlockHeader(parentBlockHeader.getBlockHash()))
.thenReturn(Optional.of(parentBlockHeader));
when(protocolContext.getBlockchain()).thenReturn(blockchain);
@ -114,6 +120,8 @@ public class EngineNewPayloadV3Test extends EngineNewPayloadV2Test {
.depositsRoot(maybeDeposits.map(BodyValidation::depositsRoot).orElse(null))
.excessBlobGas(BlobGas.ZERO)
.blobGasUsed(0L)
.parentBeaconBlockRoot(
maybeParentBeaconBlockRoot.isPresent() ? maybeParentBeaconBlockRoot : null)
.buildHeader();
return mockHeader;
}

@ -99,12 +99,14 @@ public class EnginePayloadAttributesParameterTest {
private EnginePayloadAttributesParameter parameterWithdrawalsOmitted() {
return new EnginePayloadAttributesParameter(
TIMESTAMP, PREV_RANDAO, SUGGESTED_FEE_RECIPIENT_ADDRESS, null);
TIMESTAMP, PREV_RANDAO, SUGGESTED_FEE_RECIPIENT_ADDRESS, null, null);
}
private EnginePayloadAttributesParameter parameterWithdrawalsPresent() {
final List<WithdrawalParameter> withdrawals = List.of(WITHDRAWAL_PARAM_1, WITHDRAWAL_PARAM_2);
return new EnginePayloadAttributesParameter(
TIMESTAMP, PREV_RANDAO, SUGGESTED_FEE_RECIPIENT_ADDRESS, withdrawals);
TIMESTAMP, PREV_RANDAO, SUGGESTED_FEE_RECIPIENT_ADDRESS, withdrawals, null);
}
// TODO: add a parent beacon block root test here
}

@ -117,6 +117,7 @@ public class BlockchainQueriesLogCacheTest {
null,
null,
null,
null,
new MainnetBlockHeaderFunctions());
testHash = fakeHeader.getHash();
final BlockBody fakeBody = new BlockBody(Collections.emptyList(), Collections.emptyList());

@ -109,6 +109,7 @@ public class TransactionLogBloomCacherTest {
null,
null,
null,
null,
new MainnetBlockHeaderFunctions());
testHash = fakeHeader.getHash();
when(blockchain.getBlockHeader(anyLong())).thenReturn(Optional.of(fakeHeader));
@ -283,6 +284,7 @@ public class TransactionLogBloomCacherTest {
null,
null,
null,
null,
new MainnetBlockHeaderFunctions());
testHash = fakeHeader.getHash();
when(blockchain.getBlockHeader(number)).thenReturn(Optional.of(fakeHeader));

@ -42,6 +42,7 @@ import org.hyperledger.besu.ethereum.mainnet.BodyValidation;
import org.hyperledger.besu.ethereum.mainnet.DepositsValidator;
import org.hyperledger.besu.ethereum.mainnet.DifficultyCalculator;
import org.hyperledger.besu.ethereum.mainnet.MainnetTransactionProcessor;
import org.hyperledger.besu.ethereum.mainnet.ParentBeaconBlockRootHelper;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec;
import org.hyperledger.besu.ethereum.mainnet.ScheduleBasedBlockHeaderFunctions;
@ -152,7 +153,13 @@ public abstract class AbstractBlockCreator implements AsyncBlockCreator {
final Optional<List<BlockHeader>> maybeOmmers,
final long timestamp) {
return createBlock(
maybeTransactions, maybeOmmers, Optional.empty(), Optional.empty(), timestamp, true);
maybeTransactions,
maybeOmmers,
Optional.empty(),
Optional.empty(),
Optional.empty(),
timestamp,
true);
}
protected BlockCreationResult createBlock(
@ -160,6 +167,7 @@ public abstract class AbstractBlockCreator implements AsyncBlockCreator {
final Optional<List<BlockHeader>> maybeOmmers,
final Optional<List<Withdrawal>> maybeWithdrawals,
final Optional<Bytes32> maybePrevRandao,
final Optional<Bytes32> maybeParentBeaconBlockRoot,
final long timestamp,
boolean rewardCoinbase) {
@ -168,7 +176,8 @@ public abstract class AbstractBlockCreator implements AsyncBlockCreator {
protocolSchedule.getForNextBlockHeader(parentHeader, timestamp);
final ProcessableBlockHeader processableBlockHeader =
createPendingBlockHeader(timestamp, maybePrevRandao, newProtocolSpec);
createPendingBlockHeader(
timestamp, maybePrevRandao, maybeParentBeaconBlockRoot, newProtocolSpec);
final Address miningBeneficiary =
miningBeneficiaryCalculator.getMiningBeneficiary(processableBlockHeader.getNumber());
Wei blobGasPrice =
@ -180,6 +189,11 @@ public abstract class AbstractBlockCreator implements AsyncBlockCreator {
final List<BlockHeader> ommers = maybeOmmers.orElse(selectOmmers());
if (maybeParentBeaconBlockRoot.isPresent()) {
ParentBeaconBlockRootHelper.storeParentBeaconBlockRoot(
disposableWorldState.updater(), timestamp, maybeParentBeaconBlockRoot.get());
}
throwIfStopped();
final TransactionSelectionResults transactionResults =
selectTransactions(
@ -373,6 +387,7 @@ public abstract class AbstractBlockCreator implements AsyncBlockCreator {
private ProcessableBlockHeader createPendingBlockHeader(
final long timestamp,
final Optional<Bytes32> maybePrevRandao,
final Optional<Bytes32> maybeParentBeaconBlockRoot,
final ProtocolSpec protocolSpec) {
final long newBlockNumber = parentHeader.getNumber() + 1;
long gasLimit =
@ -401,6 +416,7 @@ public abstract class AbstractBlockCreator implements AsyncBlockCreator {
.orElse(null);
final Bytes32 prevRandao = maybePrevRandao.orElse(null);
final Bytes32 parentBeaconBlockRoot = maybeParentBeaconBlockRoot.orElse(null);
return BlockHeaderBuilder.create()
.parentHash(parentHeader.getHash())
.coinbase(coinbase)
@ -410,6 +426,7 @@ public abstract class AbstractBlockCreator implements AsyncBlockCreator {
.timestamp(timestamp)
.baseFee(baseFee)
.prevRandao(prevRandao)
.parentBeaconBlockRoot(parentBeaconBlockRoot)
.buildProcessableBlockHeader();
}

@ -146,6 +146,7 @@ abstract class AbstractBlockCreatorTest {
Optional.empty(),
Optional.of(emptyList()),
Optional.empty(),
Optional.empty(),
1L,
false);
@ -165,6 +166,7 @@ abstract class AbstractBlockCreatorTest {
Optional.empty(),
Optional.of(emptyList()),
Optional.empty(),
Optional.empty(),
1L,
false);
@ -182,6 +184,7 @@ abstract class AbstractBlockCreatorTest {
Optional.empty(),
Optional.of(emptyList()),
Optional.empty(),
Optional.empty(),
1L,
false);
@ -214,7 +217,13 @@ abstract class AbstractBlockCreatorTest {
final AbstractBlockCreator blockCreator = blockCreatorWithWithdrawalsProcessor();
final BlockCreationResult blockCreationResult =
blockCreator.createBlock(
Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), 1L, false);
Optional.empty(),
Optional.empty(),
Optional.empty(),
Optional.empty(),
Optional.empty(),
1L,
false);
verify(withdrawalsProcessor, never()).processWithdrawals(any(), any());
assertThat(blockCreationResult.getBlock().getHeader().getWithdrawalsRoot()).isEmpty();
assertThat(blockCreationResult.getBlock().getBody().getWithdrawals()).isEmpty();
@ -225,7 +234,13 @@ abstract class AbstractBlockCreatorTest {
final AbstractBlockCreator blockCreator = blockCreatorWithoutWithdrawalsProcessor();
final BlockCreationResult blockCreationResult =
blockCreator.createBlock(
Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), 1L, false);
Optional.empty(),
Optional.empty(),
Optional.empty(),
Optional.empty(),
Optional.empty(),
1L,
false);
verify(withdrawalsProcessor, never()).processWithdrawals(any(), any());
assertThat(blockCreationResult.getBlock().getHeader().getWithdrawalsRoot()).isEmpty();
assertThat(blockCreationResult.getBlock().getBody().getWithdrawals()).isEmpty();
@ -242,6 +257,7 @@ abstract class AbstractBlockCreatorTest {
Optional.empty(),
Optional.of(withdrawals),
Optional.empty(),
Optional.empty(),
1L,
false);
@ -263,6 +279,7 @@ abstract class AbstractBlockCreatorTest {
Optional.empty(),
Optional.of(withdrawals),
Optional.empty(),
Optional.empty(),
1L,
false);
verify(withdrawalsProcessor, never()).processWithdrawals(any(), any());
@ -295,6 +312,7 @@ abstract class AbstractBlockCreatorTest {
Optional.empty(),
Optional.empty(),
Optional.empty(),
Optional.empty(),
1L,
false);
long blobGasUsage = blockCreationResult.getBlock().getHeader().getGasUsed();

@ -53,6 +53,7 @@ import java.util.stream.Stream;
import com.google.common.base.MoreObjects;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.apache.tuweni.units.bigints.UInt256;
public final class GenesisState {
@ -170,6 +171,8 @@ public final class GenesisState {
.withdrawalsRoot(isShanghaiAtGenesis(genesis) ? Hash.EMPTY_TRIE_HASH : null)
.blobGasUsed(isCancunAtGenesis(genesis) ? parseBlobGasUsed(genesis) : null)
.excessBlobGas(isCancunAtGenesis(genesis) ? parseExcessBlobGas(genesis) : null)
.parentBeaconBlockRoot(
(isCancunAtGenesis(genesis) ? parseParentBeaconBlockRoot(genesis) : null))
.depositsRoot(isExperimentalEipsTimeAtGenesis(genesis) ? Hash.EMPTY_TRIE_HASH : null)
.buildBlockHeader();
}
@ -232,6 +235,11 @@ public final class GenesisState {
return BlobGas.of(excessBlobGas);
}
private static Bytes32 parseParentBeaconBlockRoot(final GenesisConfigFile genesis) {
return withNiceErrorMessage(
"parentBeaconBlockRoot", genesis.getParentBeaconBlockRoot(), Bytes32::fromHexString);
}
private static long parseUnsignedLong(final String value) {
String v = value.toLowerCase(Locale.US);
if (v.startsWith("0x")) {

@ -23,7 +23,6 @@ import org.hyperledger.besu.ethereum.rlp.RLPOutput;
import org.hyperledger.besu.evm.log.LogsBloomFilter;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Supplier;
import com.google.common.base.Suppliers;
@ -44,54 +43,6 @@ public class BlockHeader extends SealableBlockHeader
private final Supplier<ParsedExtraData> parsedExtraData;
public BlockHeader(
final Hash parentHash,
final Hash ommersHash,
final Address coinbase,
final Hash stateRoot,
final Hash transactionsRoot,
final Hash receiptsRoot,
final LogsBloomFilter logsBloom,
final Difficulty difficulty,
final long number,
final long gasLimit,
final long gasUsed,
final long timestamp,
final Bytes extraData,
final Wei baseFee,
final Bytes32 mixHashOrPrevRandao,
final long nonce,
final Hash withdrawalsRoot,
final long blobGasUsed,
final BlobGas excessBlobGas,
final Hash depositsRoot,
final BlockHeaderFunctions blockHeaderFunctions,
final Optional<LogsBloomFilter> privateLogsBloom) {
super(
parentHash,
ommersHash,
coinbase,
stateRoot,
transactionsRoot,
receiptsRoot,
logsBloom,
difficulty,
number,
gasLimit,
gasUsed,
timestamp,
extraData,
baseFee,
mixHashOrPrevRandao,
withdrawalsRoot,
blobGasUsed,
excessBlobGas,
depositsRoot);
this.nonce = nonce;
this.hash = Suppliers.memoize(() -> blockHeaderFunctions.hash(this));
this.parsedExtraData = Suppliers.memoize(() -> blockHeaderFunctions.parseExtraData(this));
}
public BlockHeader(
final Hash parentHash,
final Hash ommersHash,
@ -112,6 +63,7 @@ public class BlockHeader extends SealableBlockHeader
final Hash withdrawalsRoot,
final Long blobGasUsed,
final BlobGas excessBlobGas,
final Bytes32 parentBeaconBlockRoot,
final Hash depositsRoot,
final BlockHeaderFunctions blockHeaderFunctions) {
super(
@ -133,6 +85,7 @@ public class BlockHeader extends SealableBlockHeader
withdrawalsRoot,
blobGasUsed,
excessBlobGas,
parentBeaconBlockRoot,
depositsRoot);
this.nonce = nonce;
this.hash = Suppliers.memoize(() -> blockHeaderFunctions.hash(this));
@ -220,6 +173,9 @@ public class BlockHeader extends SealableBlockHeader
out.writeLongScalar(blobGasUsed.get());
out.writeUInt64Scalar(excessBlobGas.get());
}
if (parentBeaconBlockRoot != null) {
out.writeBytes(parentBeaconBlockRoot);
}
if (depositsRoot != null) {
out.writeBytes(depositsRoot);
}
@ -252,6 +208,7 @@ public class BlockHeader extends SealableBlockHeader
final Long blobGasUsed = !input.isEndOfCurrentList() ? input.readLongScalar() : null;
final BlobGas excessBlobGas =
!input.isEndOfCurrentList() ? BlobGas.of(input.readLongScalar()) : null;
final Bytes32 parentBeaconBlockRoot = !input.isEndOfCurrentList() ? input.readBytes32() : null;
final Hash depositHashRoot =
!input.isEndOfCurrentList() ? Hash.wrap(input.readBytes32()) : null;
input.leaveList();
@ -275,6 +232,7 @@ public class BlockHeader extends SealableBlockHeader
withdrawalHashRoot,
blobGasUsed,
excessBlobGas,
parentBeaconBlockRoot,
depositHashRoot,
blockHeaderFunctions);
}
@ -322,6 +280,9 @@ public class BlockHeader extends SealableBlockHeader
}
blobGasUsed.ifPresent(aLong -> sb.append("blobGasUsed=").append(aLong).append(", "));
excessBlobGas.ifPresent(blobGas -> sb.append("excessBlobGas=").append(blobGas).append(", "));
if (parentBeaconBlockRoot != null) {
sb.append("parentBeaconBlockRoot=").append(parentBeaconBlockRoot).append(", ");
}
if (depositsRoot != null) {
sb.append("depositsRoot=").append(depositsRoot);
}
@ -354,6 +315,7 @@ public class BlockHeader extends SealableBlockHeader
.orElse(null),
pluginBlockHeader.getBlobGasUsed().map(Long::longValue).orElse(null),
pluginBlockHeader.getExcessBlobGas().map(BlobGas::fromQuantity).orElse(null),
pluginBlockHeader.getParentBeaconBlockRoot().orElse(null),
pluginBlockHeader
.getDepositsRoot()
.map(h -> Hash.fromHexString(h.toHexString()))

@ -75,6 +75,7 @@ public class BlockHeaderBuilder {
private Long blobGasUsed = null;
private BlobGas excessBlobGas = null;
private Bytes32 parentBeaconBlockRoot = null;
public static BlockHeaderBuilder create() {
return new BlockHeaderBuilder();
@ -122,6 +123,7 @@ public class BlockHeaderBuilder {
.withdrawalsRoot(header.getWithdrawalsRoot().orElse(null))
.blobGasUsed(header.getBlobGasUsed().orElse(null))
.excessBlobGas(header.getExcessBlobGas().orElse(null))
.parentBeaconBlockRoot(header.getParentBeaconBlockRoot().orElse(null))
.depositsRoot(header.getDepositsRoot().orElse(null));
}
@ -145,6 +147,7 @@ public class BlockHeaderBuilder {
.prevRandao(fromBuilder.mixHashOrPrevRandao)
.withdrawalsRoot(fromBuilder.withdrawalsRoot)
.excessBlobGas(fromBuilder.excessBlobGas)
.parentBeaconBlockRoot(fromBuilder.parentBeaconBlockRoot)
.depositsRoot(fromBuilder.depositsRoot)
.blockHeaderFunctions(fromBuilder.blockHeaderFunctions);
toBuilder.nonce = fromBuilder.nonce;
@ -174,6 +177,7 @@ public class BlockHeaderBuilder {
withdrawalsRoot,
blobGasUsed,
excessBlobGas,
parentBeaconBlockRoot,
depositsRoot,
blockHeaderFunctions);
}
@ -191,7 +195,8 @@ public class BlockHeaderBuilder {
baseFee,
mixHashOrPrevRandao,
blobGasUsed,
excessBlobGas);
excessBlobGas,
parentBeaconBlockRoot);
}
public SealableBlockHeader buildSealableBlockHeader() {
@ -216,6 +221,7 @@ public class BlockHeaderBuilder {
withdrawalsRoot,
blobGasUsed,
excessBlobGas,
parentBeaconBlockRoot,
depositsRoot);
}
@ -258,6 +264,7 @@ public class BlockHeaderBuilder {
processableBlockHeader.getPrevRandao().ifPresent(this::prevRandao);
processableBlockHeader.getBlobGasUsed().ifPresent(this::blobGasUsed);
processableBlockHeader.getExcessBlobGas().ifPresent(this::excessBlobGas);
processableBlockHeader.getParentBeaconBlockRoot().ifPresent(this::parentBeaconBlockRoot);
return this;
}
@ -281,6 +288,7 @@ public class BlockHeaderBuilder {
withdrawalsRoot(sealableBlockHeader.getWithdrawalsRoot().orElse(null));
sealableBlockHeader.getBlobGasUsed().ifPresent(this::blobGasUsed);
sealableBlockHeader.getExcessBlobGas().ifPresent(this::excessBlobGas);
sealableBlockHeader.getParentBeaconBlockRoot().ifPresent(this::parentBeaconBlockRoot);
depositsRoot(sealableBlockHeader.getDepositsRoot().orElse(null));
return this;
}
@ -411,4 +419,9 @@ public class BlockHeaderBuilder {
this.blobGasUsed = blobGasUsed;
return this;
}
public BlockHeaderBuilder parentBeaconBlockRoot(final Bytes32 parentBeaconBlockRoot) {
this.parentBeaconBlockRoot = parentBeaconBlockRoot;
return this;
}
}

@ -48,6 +48,8 @@ public class ProcessableBlockHeader implements BlockValues {
protected final Optional<Long> blobGasUsed;
// excessBlogGas is included for Cancun
protected final Optional<BlobGas> excessBlobGas;
// parentBeaconBlockRoot is included for Cancun
protected final Bytes32 parentBeaconBlockRoot;
protected ProcessableBlockHeader(
final Hash parentHash,
@ -59,7 +61,8 @@ public class ProcessableBlockHeader implements BlockValues {
final Wei baseFee,
final Bytes32 mixHashOrPrevRandao,
final Long blobGasUsed,
final BlobGas excessBlobGas) {
final BlobGas excessBlobGas,
final Bytes32 parentBeaconBlockRoot) {
this.parentHash = parentHash;
this.coinbase = coinbase;
this.difficulty = difficulty;
@ -70,6 +73,7 @@ public class ProcessableBlockHeader implements BlockValues {
this.mixHashOrPrevRandao = mixHashOrPrevRandao;
this.blobGasUsed = Optional.ofNullable(blobGasUsed);
this.excessBlobGas = Optional.ofNullable(excessBlobGas);
this.parentBeaconBlockRoot = parentBeaconBlockRoot;
}
/**
@ -176,6 +180,10 @@ public class ProcessableBlockHeader implements BlockValues {
return blobGasUsed;
}
public Optional<Bytes32> getParentBeaconBlockRoot() {
return Optional.ofNullable(parentBeaconBlockRoot);
}
public String toLogString() {
return getNumber() + " (time: " + getTimestamp() + ")";
}

@ -64,6 +64,7 @@ public class SealableBlockHeader extends ProcessableBlockHeader {
final Hash withdrawalsRoot,
final Long blobGasUsed,
final BlobGas excessBlobGas,
final Bytes32 parentBeaconBlockRoot,
final Hash depositsRoot) {
super(
parentHash,
@ -75,7 +76,8 @@ public class SealableBlockHeader extends ProcessableBlockHeader {
baseFee,
mixHashOrPrevRandao,
blobGasUsed,
excessBlobGas);
excessBlobGas,
parentBeaconBlockRoot);
this.ommersHash = ommersHash;
this.stateRoot = stateRoot;
this.transactionsRoot = transactionsRoot;

@ -104,12 +104,19 @@ public abstract class AbstractBlockProcessor implements BlockProcessor {
final ProtocolSpec protocolSpec = protocolSchedule.getByBlockHeader(blockHeader);
if (blockHeader.getParentBeaconBlockRoot().isPresent()) {
final WorldUpdater updater = worldState.updater();
ParentBeaconBlockRootHelper.storeParentBeaconBlockRoot(
updater, blockHeader.getTimestamp(), blockHeader.getParentBeaconBlockRoot().get());
}
for (final Transaction transaction : transactions) {
if (!hasAvailableBlockBudget(blockHeader, transaction, currentGasUsed)) {
return new BlockProcessingResult(Optional.empty(), "provided gas insufficient");
}
final WorldUpdater worldStateUpdater = worldState.updater();
final BlockHashLookup blockHashLookup = new CachingBlockHashLookup(blockHeader, blockchain);
final Address miningBeneficiary =
miningBeneficiaryCalculator.calculateBeneficiary(blockHeader);

@ -0,0 +1,60 @@
/*
* Copyright Hyperledger Besu Contributors.
*
* 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.mainnet;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.evm.account.MutableAccount;
import org.hyperledger.besu.evm.worldstate.WorldUpdater;
import org.apache.tuweni.bytes.Bytes32;
import org.apache.tuweni.units.bigints.UInt256;
/** A helper class to store the parent beacon block root. */
public class ParentBeaconBlockRootHelper {
// Modulus use to for the timestamp to store the root
public static final long HISTORICAL_ROOTS_MODULUS = 98304;
// Address of the system user, that is used to call the contract for storing the root
// public static final Address SYSTEM_ADDRESS =
// Address.fromHexString("0xfffffffffffffffffffffffffffffffffffffffe");
// The address of the contract that stores the roots
// public static final Address BEACON_ROOTS_ADDRESS =
// Address.fromHexString("0x89e64Be8700cC37EB34f9209c96466DEEDc0d8a6");
public static void storeParentBeaconBlockRoot(
final WorldUpdater worldUpdater, final long timestamp, final Bytes32 root) {
/*
pseudo code from EIP 4788:
timestamp_as_uint256 = to_uint256_be(block_header.timestamp)
parent_beacon_block_root = block_header.parent_beacon_block_root
sstore(HISTORY_STORAGE_ADDRESS, timestamp_index, timestamp_as_uint256)
sstore(HISTORY_STORAGE_ADDRESS, root_index, parent_beacon_block_root)
*/
final long timestampReduced = timestamp % HISTORICAL_ROOTS_MODULUS;
final long timestampExtended = timestampReduced + HISTORICAL_ROOTS_MODULUS;
final UInt256 timestampIndex = UInt256.valueOf(timestampReduced);
final UInt256 rootIndex = UInt256.valueOf(timestampExtended);
final MutableAccount account =
worldUpdater.getOrCreate(Address.PARENT_BEACON_BLOCK_ROOT_REGISTRY).getMutable();
account.setStorageValue(timestampIndex, UInt256.valueOf(timestamp));
account.setStorageValue(rootIndex, UInt256.fromBytes(root));
worldUpdater.commit();
}
}

@ -54,6 +54,7 @@ public class BlockHeaderTestFixture {
private BlockHeaderFunctions blockHeaderFunctions = new MainnetBlockHeaderFunctions();
private Optional<BlobGas> excessBlobGas = Optional.empty();
private Optional<Long> blobGasUsed = Optional.empty();
private Optional<Bytes32> parentBeaconBlockRoot = Optional.empty();
public BlockHeader buildHeader() {
final BlockHeaderBuilder builder = BlockHeaderBuilder.create();
@ -78,6 +79,7 @@ public class BlockHeaderTestFixture {
excessBlobGas.ifPresent(builder::excessBlobGas);
blobGasUsed.ifPresent(builder::blobGasUsed);
depositsRoot.ifPresent(builder::depositsRoot);
parentBeaconBlockRoot.ifPresent(builder::parentBeaconBlockRoot);
builder.blockHeaderFunctions(blockHeaderFunctions);
return builder.buildBlockHeader();
@ -193,4 +195,10 @@ public class BlockHeaderTestFixture {
this.blockHeaderFunctions = blockHeaderFunctions;
return this;
}
public BlockHeaderTestFixture parentBeaconBlockRoot(
final Optional<Bytes32> parentBeaconBlockRoot) {
this.parentBeaconBlockRoot = parentBeaconBlockRoot;
return this;
}
}

@ -22,6 +22,7 @@ import org.hyperledger.besu.plugin.data.BlockHeader;
import java.util.Optional;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
public class NonBesuBlockHeader implements BlockHeader {
@ -132,4 +133,9 @@ public class NonBesuBlockHeader implements BlockHeader {
public Optional<? extends Long> getBlobGasUsed() {
return Optional.empty();
}
@Override
public Optional<? extends Bytes32> getParentBeaconBlockRoot() {
return Optional.empty();
}
}

@ -98,6 +98,7 @@ public class LogRollingTests {
null, // blobGasUSed
null,
null,
null,
new MainnetBlockHeaderFunctions());
private static final BlockHeader headerTwo =
new BlockHeader(
@ -121,6 +122,7 @@ public class LogRollingTests {
null, // blobGasUsed
null,
null,
null,
new MainnetBlockHeaderFunctions());
@BeforeEach

@ -211,7 +211,7 @@ public final class GenesisStateTest {
assertThat(header.getHash())
.isEqualTo(
Hash.fromHexString(
"0xf48e1c6ee02ec8da09e8e5a0084e48c081ae26522d29e398db68d945cd5a6890"));
"0x87846b86c1026fa7d7be2da045716274231de1871065a320659c9b111287c688"));
assertThat(header.getGasLimit()).isEqualTo(0x2fefd8);
assertThat(header.getGasUsed()).isEqualTo(0);
assertThat(header.getNumber()).isEqualTo(0);

@ -392,6 +392,7 @@ public class MessageWrapperTest {
null,
null,
null,
null,
new MainnetBlockHeaderFunctions());
}
}

@ -60,6 +60,7 @@ public class ChainForTestCreator {
null,
null,
null,
null,
new MainnetBlockHeaderFunctions());
}
@ -86,6 +87,7 @@ public class ChainForTestCreator {
blockHeader.getWithdrawalsRoot().orElse(null),
blockHeader.getBlobGasUsed().orElse(null),
blockHeader.getExcessBlobGas().orElse(null),
blockHeader.getParentBeaconBlockRoot().orElse(null),
blockHeader.getDepositsRoot().orElse(null),
new MainnetBlockHeaderFunctions());
}
@ -136,6 +138,7 @@ public class ChainForTestCreator {
null,
null,
null,
null,
new MainnetBlockHeaderFunctions());
}
}

@ -48,6 +48,7 @@ import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
@JsonIgnoreProperties(ignoreUnknown = true)
public class BlockchainReferenceTestCaseSpec {
@ -164,10 +165,13 @@ public class BlockchainReferenceTestCaseSpec {
@JsonProperty("nonce") final String nonce,
@JsonProperty("withdrawalsRoot") final String withdrawalsRoot,
@JsonProperty("depositsRoot") final String depositsRoot,
@JsonProperty("dataGasUsed") final String dataGasUsed,
@JsonProperty("excessDataGas") final String excessDataGas,
@JsonProperty("dataGasUsed")
final String dataGasUsed, // TODO: remove once reference tests have been updated
@JsonProperty("excessDataGas")
final String excessDataGas, // TODO: remove once reference tests have been updated
@JsonProperty("blobGasUsed") final String blobGasUsed,
@JsonProperty("excessBlobGas") final String excessBlobGas,
@JsonProperty("parentBeaconBlockRoot") final String parentBeaconBlockRoot,
@JsonProperty("hash") final String hash) {
super(
Hash.fromHexString(parentHash), // parentHash
@ -197,6 +201,7 @@ public class BlockchainReferenceTestCaseSpec {
excessDataGas != null
? BlobGas.fromHexString(excessDataGas)
: excessBlobGas != null ? BlobGas.fromHexString(excessBlobGas) : null,
parentBeaconBlockRoot != null ? Bytes32.fromHexString(parentBeaconBlockRoot) : null,
depositsRoot != null ? Hash.fromHexString(depositsRoot) : null,
new BlockHeaderFunctions() {
@Override

@ -82,7 +82,7 @@ public class ReferenceTestEnv extends BlockHeader {
private final String parentBlobGasUsed;
private final Hash beaconRoot;
private final Bytes32 beaconRoot;
/**
* Public constructor.
@ -147,6 +147,7 @@ public class ReferenceTestEnv extends BlockHeader {
currentExcessBlobGas == null
? currentExcessDataGas == null ? null : BlobGas.fromHexString(currentExcessDataGas)
: BlobGas.fromHexString(currentExcessBlobGas),
beaconRoot == null ? null : Bytes32.fromHexString(beaconRoot),
null, // depositsRoot
new MainnetBlockHeaderFunctions());
this.parentDifficulty = parentDifficulty;

File diff suppressed because one or more lines are too long

@ -0,0 +1 @@
<< (9ee7c86c) {"jsonrpc":"2.0","id":12,"result":{"executionPayload":{"parentHash":"0x90c8b9e1e8e51584453f5516036bfe0031ebb595e47c5fd664bd56471ce618d6","feeRecipient":"0x0000000000000000000000000000000000000000","stateRoot":"0xfb58f52e7d31c6d3232b10a33799f7b5b8f7b883a8cbdd0c41cdefb207de4648","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0xe7f1a1fb5c08adeb1e62a01f58dda79626f9ab56bce81f8ffa587b3045e007bb","gasLimit":"0x2ffbd8","gasUsed":"0x0","timestamp":"0x1237","extraData":"0x","baseFeePerGas":"0x28482258","excessBlobGas":"0x0","parentBeaconBlockRoot":"0xe19f3a77f925a661b19f84ba74acddd84c121b4813b7470d91df7d3270667511","transactions":[],"withdrawals":[],"blockNumber":"0x3","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","blobGasUsed":"0x0","blockHash":"0x69607c0fc6af60144538ad4c3028d0ac86baa0cd45fbc1c080b2d5f095811c84"},"blockValue":"0x0","blobsBundle":{"commitments":[],"proofs":[],"blobs":[]}}}

@ -69,7 +69,7 @@ Calculated : ${currentHash}
tasks.register('checkAPIChanges', FileStateChecker) {
description = "Checks that the API for the Plugin-API project does not change without deliberate thought"
files = sourceSets.main.allJava.files
knownHash = 'jxYLp4SMEdixHCmHDjylVvl7W8smMCWispBMKPJlFXg='
knownHash = 'W/4RHhLwUOWYDqwT3oZVKdRAnu/CEFCtqNDOT9cdPNk='
}
check.dependsOn('checkAPIChanges')

@ -216,4 +216,12 @@ public interface BlockHeader {
*/
@Unstable
Optional<? extends Long> getBlobGasUsed();
/**
* The parent beacon block root of this header.
*
* @return The parent_beacon_block_root of this header.
*/
@Unstable
Optional<? extends Bytes32> getParentBeaconBlockRoot();
}

Loading…
Cancel
Save