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. 32
      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. 38
      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,
null, null,
null,
blockHeaderFunctions); blockHeaderFunctions);
} }
} }

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

@ -265,6 +265,18 @@ public class GenesisConfigFile {
return JsonUtil.getValueAsString(configRoot, "blobgasused", "0x0"); 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. * Gets coinbase.
* *

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

@ -246,7 +246,8 @@ public class MergeCoordinator implements MergeMiningCoordinator, BadChainListene
final Long timestamp, final Long timestamp,
final Bytes32 prevRandao, final Bytes32 prevRandao,
final Address feeRecipient, 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 // 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 // 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 // put the empty block in first
final Block emptyBlock = final Block emptyBlock =
mergeBlockCreator mergeBlockCreator
.createBlock(Optional.of(Collections.emptyList()), prevRandao, timestamp, withdrawals) .createBlock(
Optional.of(Collections.emptyList()),
prevRandao,
timestamp,
withdrawals,
parentBeaconBlockRoot)
.getBlock(); .getBlock();
BlockProcessingResult result = validateProposedBlock(emptyBlock); 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; return payloadIdentifier;
} }
@ -334,10 +346,13 @@ public class MergeCoordinator implements MergeMiningCoordinator, BadChainListene
final Bytes32 random, final Bytes32 random,
final PayloadIdentifier payloadIdentifier, final PayloadIdentifier payloadIdentifier,
final MergeBlockCreator mergeBlockCreator, final MergeBlockCreator mergeBlockCreator,
final Optional<List<Withdrawal>> withdrawals) { final Optional<List<Withdrawal>> withdrawals,
final Optional<Bytes32> parentBeaconBlockRoot) {
final Supplier<BlockCreationResult> blockCreator = final Supplier<BlockCreationResult> blockCreator =
() -> mergeBlockCreator.createBlock(Optional.empty(), random, timestamp, withdrawals); () ->
mergeBlockCreator.createBlock(
Optional.empty(), random, timestamp, withdrawals, parentBeaconBlockRoot);
LOG.debug( LOG.debug(
"Block creation started for payload id {}, remaining time is {}ms", "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 prevRandao the prev randao
* @param feeRecipient the fee recipient * @param feeRecipient the fee recipient
* @param withdrawals the optional list of withdrawals * @param withdrawals the optional list of withdrawals
* @param parentBeaconBlockRoot optional root hash of the parent beacon block
* @return the payload identifier * @return the payload identifier
*/ */
PayloadIdentifier preparePayload( PayloadIdentifier preparePayload(
@ -48,7 +49,8 @@ public interface MergeMiningCoordinator extends MiningCoordinator {
final Long timestamp, final Long timestamp,
final Bytes32 prevRandao, final Bytes32 prevRandao,
final Address feeRecipient, final Address feeRecipient,
final Optional<List<Withdrawal>> withdrawals); final Optional<List<Withdrawal>> withdrawals,
final Optional<Bytes32> parentBeaconBlockRoot);
@Override @Override
default boolean isCompatibleWithEngineApi() { default boolean isCompatibleWithEngineApi() {

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

@ -57,26 +57,30 @@ public class Address extends DelegatingBytes {
public static final Address ALTBN128_PAIRING = Address.precompiled(0x08); public static final Address ALTBN128_PAIRING = Address.precompiled(0x08);
/** The constant BLAKE2B_F_COMPRESSION. */ /** The constant BLAKE2B_F_COMPRESSION. */
public static final Address BLAKE2B_F_COMPRESSION = Address.precompiled(0x09); 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. */ /** 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. */ /** 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. */ /** 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. */ /** 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. */ /** 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. */ /** 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. */ /** 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. */ /** 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. */ /** The constant BLS12_MAP_FP2_TO_G2. */
public static final Address BLS12_MAP_FP2_TO_G2 = Address.precompiled(0x12); public static final Address BLS12_MAP_FP2_TO_G2 = Address.precompiled(0x14);
/** The constant KZG_POINT_EVAL. */
public static final Address KZG_POINT_EVAL = Address.precompiled(0x14);
/** The constant ZERO. */ /** The constant ZERO. */
public static final Address ZERO = Address.fromHexString("0x0"); public static final Address ZERO = Address.fromHexString("0x0");

@ -129,6 +129,7 @@ public class JsonRpcResponseUtils {
withdrawalsRoot, withdrawalsRoot,
null, // ToDo 4844: set with the value of blob_gas_used field 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 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, depositsRoot,
blockHeaderFunctions); blockHeaderFunctions);

@ -58,6 +58,7 @@ public enum RpcMethod {
ENGINE_NEW_PAYLOAD_V3("engine_newPayloadV3"), ENGINE_NEW_PAYLOAD_V3("engine_newPayloadV3"),
ENGINE_FORKCHOICE_UPDATED_V1("engine_forkchoiceUpdatedV1"), ENGINE_FORKCHOICE_UPDATED_V1("engine_forkchoiceUpdatedV1"),
ENGINE_FORKCHOICE_UPDATED_V2("engine_forkchoiceUpdatedV2"), ENGINE_FORKCHOICE_UPDATED_V2("engine_forkchoiceUpdatedV2"),
ENGINE_FORKCHOICE_UPDATED_V3("engine_forkchoiceUpdatedV3"),
ENGINE_EXCHANGE_TRANSITION_CONFIGURATION("engine_exchangeTransitionConfigurationV1"), ENGINE_EXCHANGE_TRANSITION_CONFIGURATION("engine_exchangeTransitionConfigurationV1"),
ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1("engine_getPayloadBodiesByHashV1"), ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1("engine_getPayloadBodiesByHashV1"),
ENGINE_GET_PAYLOAD_BODIES_BY_RANGE_V1("engine_getPayloadBodiesByRangeV1"), 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.BlockHeader;
import org.hyperledger.besu.ethereum.core.Withdrawal; import org.hyperledger.besu.ethereum.core.Withdrawal;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.ScheduledProtocolSpec;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -51,6 +52,7 @@ public abstract class AbstractEngineForkchoiceUpdated extends ExecutionEngineJso
private static final Logger LOG = LoggerFactory.getLogger(AbstractEngineForkchoiceUpdated.class); private static final Logger LOG = LoggerFactory.getLogger(AbstractEngineForkchoiceUpdated.class);
private final ProtocolSchedule protocolSchedule; private final ProtocolSchedule protocolSchedule;
private final MergeMiningCoordinator mergeCoordinator; private final MergeMiningCoordinator mergeCoordinator;
private final Long cancunTimestamp;
public AbstractEngineForkchoiceUpdated( public AbstractEngineForkchoiceUpdated(
final Vertx vertx, final Vertx vertx,
@ -61,6 +63,9 @@ public abstract class AbstractEngineForkchoiceUpdated extends ExecutionEngineJso
super(vertx, protocolContext, engineCallListener); super(vertx, protocolContext, engineCallListener);
this.protocolSchedule = protocolSchedule; this.protocolSchedule = protocolSchedule;
this.mergeCoordinator = mergeCoordinator; 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 @Override
@ -178,7 +183,8 @@ public abstract class AbstractEngineForkchoiceUpdated extends ExecutionEngineJso
payloadAttributes.getTimestamp(), payloadAttributes.getTimestamp(),
payloadAttributes.getPrevRandao(), payloadAttributes.getPrevRandao(),
payloadAttributes.getSuggestedFeeRecipient(), payloadAttributes.getSuggestedFeeRecipient(),
withdrawals)); withdrawals,
Optional.ofNullable(payloadAttributes.getParentBeaconBlockRoot())));
payloadId.ifPresent( payloadId.ifPresent(
pid -> pid ->
@ -204,12 +210,24 @@ public abstract class AbstractEngineForkchoiceUpdated extends ExecutionEngineJso
final Optional<List<Withdrawal>> maybeWithdrawals, final Optional<List<Withdrawal>> maybeWithdrawals,
final BlockHeader headBlockHeader) { final BlockHeader headBlockHeader) {
final boolean newTimestampGreaterThanHead = if (payloadAttributes.getTimestamp() <= headBlockHeader.getTimestamp()) {
payloadAttributes.getTimestamp() > headBlockHeader.getTimestamp(); LOG.warn(
return newTimestampGreaterThanHead "Payload attributes timestamp is smaller than timestamp of header in fork choice update");
&& getWithdrawalsValidator( return false;
protocolSchedule, headBlockHeader, payloadAttributes.getTimestamp()) }
.validateWithdrawals(maybeWithdrawals); 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)) {
return false;
}
return true;
} }
private JsonRpcResponse handleNonValidForkchoiceUpdate( private JsonRpcResponse handleNonValidForkchoiceUpdate(

@ -110,8 +110,14 @@ public abstract class AbstractEngineNewPayload extends ExecutionEngineJsonRpcMet
final Object reqId = requestContext.getRequest().getId(); final Object reqId = requestContext.getRequest().getId();
final ValidationResult<RpcErrorType> forkValidationResult = Optional<String> maybeParentBeaconBlockRootParam =
validateForkSupported(reqId, blockParam); requestContext.getOptionalParameter(2, String.class);
final Optional<Bytes32> maybeParentBeaconBlockRoot =
maybeParentBeaconBlockRootParam.map(Bytes32::fromHexString);
ValidationResult<RpcErrorType> forkValidationResult =
validateParamsAndForkSupported(
reqId, blockParam, maybeVersionedHashParam, maybeParentBeaconBlockRoot);
if (!forkValidationResult.isValid()) { if (!forkValidationResult.isValid()) {
return new JsonRpcErrorResponse(reqId, forkValidationResult); return new JsonRpcErrorResponse(reqId, forkValidationResult);
} }
@ -204,6 +210,7 @@ public abstract class AbstractEngineNewPayload extends ExecutionEngineJsonRpcMet
blockParam.getExcessBlobGas() == null blockParam.getExcessBlobGas() == null
? null ? null
: BlobGas.fromHexString(blockParam.getExcessBlobGas()), : BlobGas.fromHexString(blockParam.getExcessBlobGas()),
maybeParentBeaconBlockRoot.orElse(null),
maybeDeposits.map(BodyValidation::depositsRoot).orElse(null), maybeDeposits.map(BodyValidation::depositsRoot).orElse(null),
headerFunctions); headerFunctions);
@ -391,8 +398,11 @@ public abstract class AbstractEngineNewPayload extends ExecutionEngineJsonRpcMet
return INVALID; return INVALID;
} }
protected ValidationResult<RpcErrorType> validateForkSupported( protected ValidationResult<RpcErrorType> validateParamsAndForkSupported(
final Object id, final EnginePayloadParameter payloadParameter) { final Object id,
final EnginePayloadParameter payloadParameter,
final Optional<List<String>> maybeVersionedHashParam,
final Optional<Bytes32> parentBeaconBlockRoot) {
return ValidationResult.valid(); 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.api.jsonrpc.internal.response.RpcErrorType;
import org.hyperledger.besu.ethereum.eth.manager.EthPeers; import org.hyperledger.besu.ethereum.eth.manager.EthPeers;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.ScheduledProtocolSpec;
import org.hyperledger.besu.ethereum.mainnet.ValidationResult; import org.hyperledger.besu.ethereum.mainnet.ValidationResult;
import java.util.List;
import java.util.Optional;
import io.vertx.core.Vertx; import io.vertx.core.Vertx;
import org.apache.tuweni.bytes.Bytes32;
public class EngineNewPayloadV3 extends AbstractEngineNewPayload { public class EngineNewPayloadV3 extends AbstractEngineNewPayload {
private final ProtocolSchedule timestampSchedule; private final Long cancunTimestamp;
public EngineNewPayloadV3( public EngineNewPayloadV3(
final Vertx vertx, final Vertx vertx,
@ -38,7 +43,9 @@ public class EngineNewPayloadV3 extends AbstractEngineNewPayload {
final EngineCallListener engineCallListener) { final EngineCallListener engineCallListener) {
super( super(
vertx, timestampSchedule, protocolContext, mergeCoordinator, ethPeers, engineCallListener); 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 @Override
@ -47,19 +54,24 @@ public class EngineNewPayloadV3 extends AbstractEngineNewPayload {
} }
@Override @Override
protected ValidationResult<RpcErrorType> validateForkSupported( protected ValidationResult<RpcErrorType> validateParamsAndForkSupported(
final Object reqId, final EnginePayloadParameter payloadParameter) { final Object reqId,
var cancun = timestampSchedule.hardforkFor(s -> s.fork().name().equalsIgnoreCase("Cancun")); 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 return ValidationResult.invalid(RpcErrorType.INVALID_PARAMS, "Missing blob gas fields");
|| payloadParameter.getExcessBlobGas() == null) { } else if (maybeVersionedHashParam == null) {
return ValidationResult.invalid(RpcErrorType.INVALID_PARAMS, "Missing blob gas fields"); return ValidationResult.invalid(
} else { RpcErrorType.INVALID_PARAMS, "Missing versioned hashes field");
return ValidationResult.valid(); } else if (maybeParentBeaconBlockRoot.isEmpty()) {
} return ValidationResult.invalid(
} else { RpcErrorType.INVALID_PARAMS, "Missing parent beacon block root field");
}
if (payloadParameter.getTimestamp() < cancunTimestamp) {
return ValidationResult.invalid(RpcErrorType.UNSUPPORTED_FORK, "Fork not supported"); 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(), Optional.empty(),
Optional.empty(),
Optional.empty())); Optional.empty()));
final var requestId = requestContext.getRequest().getId(); final var requestId = requestContext.getRequest().getId();
@ -99,6 +100,7 @@ public class EnginePreparePayloadDebug extends ExecutionEngineJsonRpcMethod {
param.getTimestamp().orElse(parentHeader.getTimestamp() + 1L), param.getTimestamp().orElse(parentHeader.getTimestamp() + 1L),
param.getPrevRandao(), param.getPrevRandao(),
param.getFeeRecipient(), param.getFeeRecipient(),
Optional.of(withdrawals))); Optional.of(withdrawals),
param.getParentBeaconBlockRoot()));
} }
} }

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

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

@ -31,6 +31,7 @@ public class EnginePreparePayloadParameter {
private final Bytes32 prevRandao; private final Bytes32 prevRandao;
private final Optional<Long> timestamp; private final Optional<Long> timestamp;
final List<WithdrawalParameter> withdrawals; final List<WithdrawalParameter> withdrawals;
private final Optional<Bytes32> parentBeaconBlockRoot;
@JsonCreator @JsonCreator
public EnginePreparePayloadParameter( public EnginePreparePayloadParameter(
@ -38,12 +39,14 @@ public class EnginePreparePayloadParameter {
@JsonProperty("feeRecipient") final Optional<Address> feeRecipient, @JsonProperty("feeRecipient") final Optional<Address> feeRecipient,
@JsonProperty("timestamp") final Optional<UnsignedLongParameter> timestamp, @JsonProperty("timestamp") final Optional<UnsignedLongParameter> timestamp,
@JsonProperty("prevRandao") final Optional<String> prevRandao, @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.parentHash = parentHash;
this.feeRecipient = feeRecipient.orElse(Address.ZERO); this.feeRecipient = feeRecipient.orElse(Address.ZERO);
this.timestamp = timestamp.map(UnsignedLongParameter::getValue); this.timestamp = timestamp.map(UnsignedLongParameter::getValue);
this.prevRandao = Bytes32.fromHexStringLenient(prevRandao.orElse("deadbeef")); this.prevRandao = Bytes32.fromHexStringLenient(prevRandao.orElse("deadbeef"));
this.withdrawals = withdrawals.orElse(Collections.emptyList()); this.withdrawals = withdrawals.orElse(Collections.emptyList());
this.parentBeaconBlockRoot = parentBeaconBlockRoot;
} }
public Optional<Hash> getParentHash() { public Optional<Hash> getParentHash() {
@ -65,4 +68,8 @@ public class EnginePreparePayloadParameter {
public List<WithdrawalParameter> getWithdrawals() { public List<WithdrawalParameter> getWithdrawals() {
return withdrawals; 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.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import org.apache.tuweni.bytes.Bytes32;
@JsonInclude(JsonInclude.Include.NON_NULL) @JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({ @JsonPropertyOrder({
@ -86,6 +87,7 @@ public class BlockResult implements JsonRpcResult {
private final String blobGasUsed; private final String blobGasUsed;
private final String excessBlobGas; private final String excessBlobGas;
private final String parentBeaconBlockRoot;
public BlockResult( public BlockResult(
final BlockHeader header, final BlockHeader header,
@ -134,6 +136,8 @@ public class BlockResult implements JsonRpcResult {
this.blobGasUsed = header.getBlobGasUsed().map(Quantity::create).orElse(null); this.blobGasUsed = header.getBlobGasUsed().map(Quantity::create).orElse(null);
this.excessBlobGas = header.getExcessBlobGas().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") @JsonGetter(value = "number")
@ -266,4 +270,9 @@ public class BlockResult implements JsonRpcResult {
public String getExcessBlobGas() { public String getExcessBlobGas() {
return excessBlobGas; return excessBlobGas;
} }
@JsonGetter(value = "parentBeaconBlockRoot")
public String getParentBeaconBlockRoot() {
return parentBeaconBlockRoot;
}
} }

@ -81,10 +81,9 @@ public class EngineGetPayloadResultV3 {
private final String timestamp; private final String timestamp;
private final String extraData; private final String extraData;
private final String baseFeePerGas; private final String baseFeePerGas;
private final String excessBlobGas; private final String excessBlobGas;
private final String blobGasUsed; private final String blobGasUsed;
private final String parentBeaconBlockRoot;
protected final List<String> transactions; protected final List<String> transactions;
private final List<WithdrawalParameter> withdrawals; private final List<WithdrawalParameter> withdrawals;
@ -118,6 +117,8 @@ public class EngineGetPayloadResultV3 {
this.blobGasUsed = header.getBlobGasUsed().map(Quantity::create).orElse(Quantity.HEX_ZERO); this.blobGasUsed = header.getBlobGasUsed().map(Quantity::create).orElse(Quantity.HEX_ZERO);
this.excessBlobGas = this.excessBlobGas =
header.getExcessBlobGas().map(Quantity::create).orElse(Quantity.HEX_ZERO); header.getExcessBlobGas().map(Quantity::create).orElse(Quantity.HEX_ZERO);
this.parentBeaconBlockRoot =
header.getParentBeaconBlockRoot().map(Bytes32::toHexString).orElse(null);
} }
@JsonGetter(value = "blockNumber") @JsonGetter(value = "blockNumber")
@ -205,5 +206,10 @@ public class EngineGetPayloadResultV3 {
public String getBlobGasUseds() { public String getBlobGasUseds() {
return blobGasUsed; 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.EngineExchangeTransitionConfiguration;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineForkchoiceUpdatedV1; 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.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.EngineGetPayloadBodiesByHashV1;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineGetPayloadBodiesByRangeV1; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineGetPayloadBodiesByRangeV1;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineGetPayloadV1; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineGetPayloadV1;
@ -128,6 +129,12 @@ public class ExecutionEngineJsonRpcMethods extends ApiGroupJsonRpcMethods {
protocolContext, protocolContext,
mergeCoordinator.get(), mergeCoordinator.get(),
engineQosTimer), engineQosTimer),
new EngineForkchoiceUpdatedV3(
consensusEngineServer,
protocolSchedule,
protocolContext,
mergeCoordinator.get(),
engineQosTimer),
new EngineExchangeTransitionConfiguration( new EngineExchangeTransitionConfiguration(
consensusEngineServer, protocolContext, engineQosTimer), consensusEngineServer, protocolContext, engineQosTimer),
new EngineGetPayloadBodiesByHashV1( new EngineGetPayloadBodiesByHashV1(

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

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

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

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

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

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

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

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

@ -99,12 +99,14 @@ public class EnginePayloadAttributesParameterTest {
private EnginePayloadAttributesParameter parameterWithdrawalsOmitted() { private EnginePayloadAttributesParameter parameterWithdrawalsOmitted() {
return new EnginePayloadAttributesParameter( return new EnginePayloadAttributesParameter(
TIMESTAMP, PREV_RANDAO, SUGGESTED_FEE_RECIPIENT_ADDRESS, null); TIMESTAMP, PREV_RANDAO, SUGGESTED_FEE_RECIPIENT_ADDRESS, null, null);
} }
private EnginePayloadAttributesParameter parameterWithdrawalsPresent() { private EnginePayloadAttributesParameter parameterWithdrawalsPresent() {
final List<WithdrawalParameter> withdrawals = List.of(WITHDRAWAL_PARAM_1, WITHDRAWAL_PARAM_2); final List<WithdrawalParameter> withdrawals = List.of(WITHDRAWAL_PARAM_1, WITHDRAWAL_PARAM_2);
return new EnginePayloadAttributesParameter( 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,
null, null,
null,
new MainnetBlockHeaderFunctions()); new MainnetBlockHeaderFunctions());
testHash = fakeHeader.getHash(); testHash = fakeHeader.getHash();
final BlockBody fakeBody = new BlockBody(Collections.emptyList(), Collections.emptyList()); final BlockBody fakeBody = new BlockBody(Collections.emptyList(), Collections.emptyList());

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

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

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

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

@ -75,6 +75,7 @@ public class BlockHeaderBuilder {
private Long blobGasUsed = null; private Long blobGasUsed = null;
private BlobGas excessBlobGas = null; private BlobGas excessBlobGas = null;
private Bytes32 parentBeaconBlockRoot = null;
public static BlockHeaderBuilder create() { public static BlockHeaderBuilder create() {
return new BlockHeaderBuilder(); return new BlockHeaderBuilder();
@ -122,6 +123,7 @@ public class BlockHeaderBuilder {
.withdrawalsRoot(header.getWithdrawalsRoot().orElse(null)) .withdrawalsRoot(header.getWithdrawalsRoot().orElse(null))
.blobGasUsed(header.getBlobGasUsed().orElse(null)) .blobGasUsed(header.getBlobGasUsed().orElse(null))
.excessBlobGas(header.getExcessBlobGas().orElse(null)) .excessBlobGas(header.getExcessBlobGas().orElse(null))
.parentBeaconBlockRoot(header.getParentBeaconBlockRoot().orElse(null))
.depositsRoot(header.getDepositsRoot().orElse(null)); .depositsRoot(header.getDepositsRoot().orElse(null));
} }
@ -145,6 +147,7 @@ public class BlockHeaderBuilder {
.prevRandao(fromBuilder.mixHashOrPrevRandao) .prevRandao(fromBuilder.mixHashOrPrevRandao)
.withdrawalsRoot(fromBuilder.withdrawalsRoot) .withdrawalsRoot(fromBuilder.withdrawalsRoot)
.excessBlobGas(fromBuilder.excessBlobGas) .excessBlobGas(fromBuilder.excessBlobGas)
.parentBeaconBlockRoot(fromBuilder.parentBeaconBlockRoot)
.depositsRoot(fromBuilder.depositsRoot) .depositsRoot(fromBuilder.depositsRoot)
.blockHeaderFunctions(fromBuilder.blockHeaderFunctions); .blockHeaderFunctions(fromBuilder.blockHeaderFunctions);
toBuilder.nonce = fromBuilder.nonce; toBuilder.nonce = fromBuilder.nonce;
@ -174,6 +177,7 @@ public class BlockHeaderBuilder {
withdrawalsRoot, withdrawalsRoot,
blobGasUsed, blobGasUsed,
excessBlobGas, excessBlobGas,
parentBeaconBlockRoot,
depositsRoot, depositsRoot,
blockHeaderFunctions); blockHeaderFunctions);
} }
@ -191,7 +195,8 @@ public class BlockHeaderBuilder {
baseFee, baseFee,
mixHashOrPrevRandao, mixHashOrPrevRandao,
blobGasUsed, blobGasUsed,
excessBlobGas); excessBlobGas,
parentBeaconBlockRoot);
} }
public SealableBlockHeader buildSealableBlockHeader() { public SealableBlockHeader buildSealableBlockHeader() {
@ -216,6 +221,7 @@ public class BlockHeaderBuilder {
withdrawalsRoot, withdrawalsRoot,
blobGasUsed, blobGasUsed,
excessBlobGas, excessBlobGas,
parentBeaconBlockRoot,
depositsRoot); depositsRoot);
} }
@ -258,6 +264,7 @@ public class BlockHeaderBuilder {
processableBlockHeader.getPrevRandao().ifPresent(this::prevRandao); processableBlockHeader.getPrevRandao().ifPresent(this::prevRandao);
processableBlockHeader.getBlobGasUsed().ifPresent(this::blobGasUsed); processableBlockHeader.getBlobGasUsed().ifPresent(this::blobGasUsed);
processableBlockHeader.getExcessBlobGas().ifPresent(this::excessBlobGas); processableBlockHeader.getExcessBlobGas().ifPresent(this::excessBlobGas);
processableBlockHeader.getParentBeaconBlockRoot().ifPresent(this::parentBeaconBlockRoot);
return this; return this;
} }
@ -281,6 +288,7 @@ public class BlockHeaderBuilder {
withdrawalsRoot(sealableBlockHeader.getWithdrawalsRoot().orElse(null)); withdrawalsRoot(sealableBlockHeader.getWithdrawalsRoot().orElse(null));
sealableBlockHeader.getBlobGasUsed().ifPresent(this::blobGasUsed); sealableBlockHeader.getBlobGasUsed().ifPresent(this::blobGasUsed);
sealableBlockHeader.getExcessBlobGas().ifPresent(this::excessBlobGas); sealableBlockHeader.getExcessBlobGas().ifPresent(this::excessBlobGas);
sealableBlockHeader.getParentBeaconBlockRoot().ifPresent(this::parentBeaconBlockRoot);
depositsRoot(sealableBlockHeader.getDepositsRoot().orElse(null)); depositsRoot(sealableBlockHeader.getDepositsRoot().orElse(null));
return this; return this;
} }
@ -411,4 +419,9 @@ public class BlockHeaderBuilder {
this.blobGasUsed = blobGasUsed; this.blobGasUsed = blobGasUsed;
return this; 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; protected final Optional<Long> blobGasUsed;
// excessBlogGas is included for Cancun // excessBlogGas is included for Cancun
protected final Optional<BlobGas> excessBlobGas; protected final Optional<BlobGas> excessBlobGas;
// parentBeaconBlockRoot is included for Cancun
protected final Bytes32 parentBeaconBlockRoot;
protected ProcessableBlockHeader( protected ProcessableBlockHeader(
final Hash parentHash, final Hash parentHash,
@ -59,7 +61,8 @@ public class ProcessableBlockHeader implements BlockValues {
final Wei baseFee, final Wei baseFee,
final Bytes32 mixHashOrPrevRandao, final Bytes32 mixHashOrPrevRandao,
final Long blobGasUsed, final Long blobGasUsed,
final BlobGas excessBlobGas) { final BlobGas excessBlobGas,
final Bytes32 parentBeaconBlockRoot) {
this.parentHash = parentHash; this.parentHash = parentHash;
this.coinbase = coinbase; this.coinbase = coinbase;
this.difficulty = difficulty; this.difficulty = difficulty;
@ -70,6 +73,7 @@ public class ProcessableBlockHeader implements BlockValues {
this.mixHashOrPrevRandao = mixHashOrPrevRandao; this.mixHashOrPrevRandao = mixHashOrPrevRandao;
this.blobGasUsed = Optional.ofNullable(blobGasUsed); this.blobGasUsed = Optional.ofNullable(blobGasUsed);
this.excessBlobGas = Optional.ofNullable(excessBlobGas); this.excessBlobGas = Optional.ofNullable(excessBlobGas);
this.parentBeaconBlockRoot = parentBeaconBlockRoot;
} }
/** /**
@ -176,6 +180,10 @@ public class ProcessableBlockHeader implements BlockValues {
return blobGasUsed; return blobGasUsed;
} }
public Optional<Bytes32> getParentBeaconBlockRoot() {
return Optional.ofNullable(parentBeaconBlockRoot);
}
public String toLogString() { public String toLogString() {
return getNumber() + " (time: " + getTimestamp() + ")"; return getNumber() + " (time: " + getTimestamp() + ")";
} }

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

@ -104,12 +104,19 @@ public abstract class AbstractBlockProcessor implements BlockProcessor {
final ProtocolSpec protocolSpec = protocolSchedule.getByBlockHeader(blockHeader); 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) { for (final Transaction transaction : transactions) {
if (!hasAvailableBlockBudget(blockHeader, transaction, currentGasUsed)) { if (!hasAvailableBlockBudget(blockHeader, transaction, currentGasUsed)) {
return new BlockProcessingResult(Optional.empty(), "provided gas insufficient"); return new BlockProcessingResult(Optional.empty(), "provided gas insufficient");
} }
final WorldUpdater worldStateUpdater = worldState.updater(); final WorldUpdater worldStateUpdater = worldState.updater();
final BlockHashLookup blockHashLookup = new CachingBlockHashLookup(blockHeader, blockchain); final BlockHashLookup blockHashLookup = new CachingBlockHashLookup(blockHeader, blockchain);
final Address miningBeneficiary = final Address miningBeneficiary =
miningBeneficiaryCalculator.calculateBeneficiary(blockHeader); 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 BlockHeaderFunctions blockHeaderFunctions = new MainnetBlockHeaderFunctions();
private Optional<BlobGas> excessBlobGas = Optional.empty(); private Optional<BlobGas> excessBlobGas = Optional.empty();
private Optional<Long> blobGasUsed = Optional.empty(); private Optional<Long> blobGasUsed = Optional.empty();
private Optional<Bytes32> parentBeaconBlockRoot = Optional.empty();
public BlockHeader buildHeader() { public BlockHeader buildHeader() {
final BlockHeaderBuilder builder = BlockHeaderBuilder.create(); final BlockHeaderBuilder builder = BlockHeaderBuilder.create();
@ -78,6 +79,7 @@ public class BlockHeaderTestFixture {
excessBlobGas.ifPresent(builder::excessBlobGas); excessBlobGas.ifPresent(builder::excessBlobGas);
blobGasUsed.ifPresent(builder::blobGasUsed); blobGasUsed.ifPresent(builder::blobGasUsed);
depositsRoot.ifPresent(builder::depositsRoot); depositsRoot.ifPresent(builder::depositsRoot);
parentBeaconBlockRoot.ifPresent(builder::parentBeaconBlockRoot);
builder.blockHeaderFunctions(blockHeaderFunctions); builder.blockHeaderFunctions(blockHeaderFunctions);
return builder.buildBlockHeader(); return builder.buildBlockHeader();
@ -193,4 +195,10 @@ public class BlockHeaderTestFixture {
this.blockHeaderFunctions = blockHeaderFunctions; this.blockHeaderFunctions = blockHeaderFunctions;
return this; 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 java.util.Optional;
import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
public class NonBesuBlockHeader implements BlockHeader { public class NonBesuBlockHeader implements BlockHeader {
@ -132,4 +133,9 @@ public class NonBesuBlockHeader implements BlockHeader {
public Optional<? extends Long> getBlobGasUsed() { public Optional<? extends Long> getBlobGasUsed() {
return Optional.empty(); return Optional.empty();
} }
@Override
public Optional<? extends Bytes32> getParentBeaconBlockRoot() {
return Optional.empty();
}
} }

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

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

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

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

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

@ -82,7 +82,7 @@ public class ReferenceTestEnv extends BlockHeader {
private final String parentBlobGasUsed; private final String parentBlobGasUsed;
private final Hash beaconRoot; private final Bytes32 beaconRoot;
/** /**
* Public constructor. * Public constructor.
@ -147,6 +147,7 @@ public class ReferenceTestEnv extends BlockHeader {
currentExcessBlobGas == null currentExcessBlobGas == null
? currentExcessDataGas == null ? null : BlobGas.fromHexString(currentExcessDataGas) ? currentExcessDataGas == null ? null : BlobGas.fromHexString(currentExcessDataGas)
: BlobGas.fromHexString(currentExcessBlobGas), : BlobGas.fromHexString(currentExcessBlobGas),
beaconRoot == null ? null : Bytes32.fromHexString(beaconRoot),
null, // depositsRoot null, // depositsRoot
new MainnetBlockHeaderFunctions()); new MainnetBlockHeaderFunctions());
this.parentDifficulty = parentDifficulty; 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) { tasks.register('checkAPIChanges', FileStateChecker) {
description = "Checks that the API for the Plugin-API project does not change without deliberate thought" description = "Checks that the API for the Plugin-API project does not change without deliberate thought"
files = sourceSets.main.allJava.files files = sourceSets.main.allJava.files
knownHash = 'jxYLp4SMEdixHCmHDjylVvl7W8smMCWispBMKPJlFXg=' knownHash = 'W/4RHhLwUOWYDqwT3oZVKdRAnu/CEFCtqNDOT9cdPNk='
} }
check.dependsOn('checkAPIChanges') check.dependsOn('checkAPIChanges')

@ -216,4 +216,12 @@ public interface BlockHeader {
*/ */
@Unstable @Unstable
Optional<? extends Long> getBlobGasUsed(); 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