Consolidated EIP-4844 (#5724)

* Implements EIP-4844.

* introduces a Hardfork class to the protocol schedule system
* new Engine APIs required for CL to work on 4844
* new DataGas type for tracking block cost for 4844 data
* new VersionedHash type to reflect that a versioned hash is not quite a pure sha256
* incorporates wrapped jc-kzg library for KZG point evaluations
* New transaction type, and domain objects for constituent parts to represent the Blobs, KZGCommitments, and Proofs used for 4844
* RLP encoders and decoders to support new transaction type
* gas pricing calculators for the new type of gas
* plugin-api version was changed

Signed-off-by: Justin Florentine <justin+github@florentine.us>
Co-authored-by: Jiri Peinlich <jiri.peinlich@gmail.com>
Co-authored-by: Jason Frame <jason.frame@consensys.net>
Co-authored-by: garyschulte <garyschulte@gmail.com>
Co-authored-by: Fabio Di Fabio <fabio.difabio@consensys.net>
Co-authored-by: Sally MacFarlane <macfarla.github@gmail.com>
Co-authored-by: Gabriel Fukushima <gabrielfukushima@gmail.com>
Co-authored-by: Gabriel-Trintinalia <gabriel.trintinalia@consensys.net>
Co-authored-by: Stefan <stefan.pingel@consensys.net>
Co-authored-by: spencer-tb <spencer@spencertaylorbrown.uk>

* junit5 updates

Signed-off-by: Justin Florentine <justin+github@florentine.us>

* update t8n test

Cancun gas claculator was inheriting from london, should have been shanghai.

Signed-off-by: Danno Ferrin <danno.ferrin@swirldslabs.com>

---------

Signed-off-by: Justin Florentine <justin+github@florentine.us>
Signed-off-by: Danno Ferrin <danno.ferrin@swirldslabs.com>
Co-authored-by: Jiri Peinlich <jiri.peinlich@gmail.com>
Co-authored-by: Jason Frame <jason.frame@consensys.net>
Co-authored-by: garyschulte <garyschulte@gmail.com>
Co-authored-by: Fabio Di Fabio <fabio.difabio@consensys.net>
Co-authored-by: Sally MacFarlane <macfarla.github@gmail.com>
Co-authored-by: Gabriel Fukushima <gabrielfukushima@gmail.com>
Co-authored-by: Gabriel-Trintinalia <gabriel.trintinalia@consensys.net>
Co-authored-by: Stefan <stefan.pingel@consensys.net>
Co-authored-by: spencer-tb <spencer@spencertaylorbrown.uk>
Co-authored-by: Danno Ferrin <danno.ferrin@swirldslabs.com>
pull/5727/head
Justin Florentine 1 year ago committed by GitHub
parent d923b8eb8f
commit 1c099e8970
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
      acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/ExecutionEngineEip6110AcceptanceTest.java
  3. 4078
      acceptance-tests/tests/src/test/resources/jsonrpc/engine/cancun/genesis.json
  4. 33
      acceptance-tests/tests/src/test/resources/jsonrpc/engine/cancun/test-cases/01_shanghai_prepare_payload.json
  5. 36
      acceptance-tests/tests/src/test/resources/jsonrpc/engine/cancun/test-cases/02_shanghai_getPayloadV2.json
  6. 38
      acceptance-tests/tests/src/test/resources/jsonrpc/engine/cancun/test-cases/03_shanghai_newPayloadV2.json
  7. 39
      acceptance-tests/tests/src/test/resources/jsonrpc/engine/cancun/test-cases/04_shanghai_newPayloadV3.json
  8. 28
      acceptance-tests/tests/src/test/resources/jsonrpc/engine/cancun/test-cases/05_cancun_forkchoiceUpdatedV2.json
  9. 33
      acceptance-tests/tests/src/test/resources/jsonrpc/engine/cancun/test-cases/06_cancun_forkchoiceUpdatedV2.json
  10. 42
      acceptance-tests/tests/src/test/resources/jsonrpc/engine/cancun/test-cases/07_cancun_getPayloadV3.json
  11. 39
      acceptance-tests/tests/src/test/resources/jsonrpc/engine/cancun/test-cases/08_cancun_newPayloadV3.json
  12. 2
      acceptance-tests/tests/src/test/resources/jsonrpc/engine/eip6110/genesis.json
  13. 2
      acceptance-tests/tests/src/test/resources/jsonrpc/engine/eip6110/test-cases/06_eip6110_get_payload.json
  14. 7
      besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java
  15. 7
      besu/src/main/java/org/hyperledger/besu/services/TraceServiceImpl.java
  16. 12
      besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java
  17. 2
      besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java
  18. 8194
      besu/src/test/resources/trusted_setup.txt
  19. 18
      config/src/main/java/org/hyperledger/besu/config/GenesisConfigFile.java
  20. 8320
      config/src/main/resources/kzg-trusted-setups/mainnet.txt
  21. 8
      consensus/common/src/main/java/org/hyperledger/besu/consensus/common/CombinedProtocolScheduleFactory.java
  22. 5
      consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftProtocolSchedule.java
  23. 2
      consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/IbftExtraDataRLPEncoderTest.java
  24. 7
      consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/TransitionProtocolSchedule.java
  25. 64
      datatypes/src/main/java/org/hyperledger/besu/datatypes/Blob.java
  26. 88
      datatypes/src/main/java/org/hyperledger/besu/datatypes/BlobsWithCommitments.java
  27. 20
      datatypes/src/main/java/org/hyperledger/besu/datatypes/DataGas.java
  28. 7
      datatypes/src/main/java/org/hyperledger/besu/datatypes/Hash.java
  29. 63
      datatypes/src/main/java/org/hyperledger/besu/datatypes/KZGCommitment.java
  30. 63
      datatypes/src/main/java/org/hyperledger/besu/datatypes/KZGProof.java
  31. 42
      datatypes/src/main/java/org/hyperledger/besu/datatypes/Sha256Hash.java
  32. 5
      datatypes/src/main/java/org/hyperledger/besu/datatypes/TransactionType.java
  33. 107
      datatypes/src/main/java/org/hyperledger/besu/datatypes/VersionedHash.java
  34. 40
      datatypes/src/test/java/org/hyperledger/besu/datatypes/BlobsWithCommitmentsTest.java
  35. 34
      datatypes/src/test/java/org/hyperledger/besu/datatypes/VersionedHashTest.java
  36. 6
      ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcResponseUtils.java
  37. 12
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/TransactionAdapter.java
  38. 2
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java
  39. 5
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/JsonRpcRequest.java
  40. 5
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/JsonRpcRequestContext.java
  41. 3
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetMinerDataByBlockHash.java
  42. 9
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetTransactionReceipt.java
  43. 8
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/ExecuteTransactionStep.java
  44. 150
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineNewPayload.java
  45. 91
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV3.java
  46. 19
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV1.java
  47. 19
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV2.java
  48. 65
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV3.java
  49. 21
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EnginePayloadParameter.java
  50. 24
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/JsonRpcParameter.java
  51. 1
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/UnsignedLongParameter.java
  52. 10
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/BlockReplay.java
  53. 5
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/TransactionTracer.java
  54. 97
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlobsBundleV1.java
  55. 16
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlockResult.java
  56. 20
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlockResultFactory.java
  57. 202
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineGetPayloadResultV3.java
  58. 7
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/Quantity.java
  59. 24
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/TransactionCompleteResult.java
  60. 12
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/TransactionPendingResult.java
  61. 21
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/TransactionReceiptResult.java
  62. 5
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/ApiGroupJsonRpcMethods.java
  63. 2
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/EthJsonRpcMethods.java
  64. 125
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/ExecutionEngineJsonRpcMethods.java
  65. 54
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/BlockchainQueries.java
  66. 24
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/TransactionReceiptWithMetadata.java
  67. 2
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGasPriceTest.java
  68. 121
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetTransactionReceiptTest.java
  69. 30
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineGetPayloadTest.java
  70. 265
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineNewPayloadTest.java
  71. 1
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineExchangeTransitionConfigurationTest.java
  72. 169
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV3Test.java
  73. 180
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadEIP6110Test.java
  74. 17
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV1Test.java
  75. 119
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV2Test.java
  76. 119
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV3Test.java
  77. 7
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/TransactionTracerTest.java
  78. 40
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlobsBundleV1Test.java
  79. 1
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/BlockchainQueriesLogCacheTest.java
  80. 2
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/cache/TransactionLogBloomCacherTest.java
  81. 35
      ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java
  82. 56
      ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreatorTest.java
  83. 2
      ethereum/core/build.gradle
  84. 4
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/GasLimitCalculator.java
  85. 35
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/GenesisState.java
  86. 16
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Block.java
  87. 21
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeader.java
  88. 12
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeaderBuilder.java
  89. 9
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/ProcessableBlockHeader.java
  90. 2
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/SealableBlockHeader.java
  91. 143
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Transaction.java
  92. 108
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/BlobTransactionDecoder.java
  93. 87
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/BlobTransactionEncoder.java
  94. 4
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/TransactionDecoder.java
  95. 12
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/TransactionEncoder.java
  96. 36
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/feemarket/TransactionPriceCalculator.java
  97. 24
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessor.java
  98. 2
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/CancunTargetingGasLimitCalculator.java
  99. 28
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/DefaultProtocolSchedule.java
  100. 7
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetBlockHeaderValidator.java
  101. Some files were not shown because too many files have changed in this diff Show More

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

@ -20,10 +20,12 @@ import java.net.URISyntaxException;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
@Ignore("EIP-6110 is not yet implemented")
public class ExecutionEngineEip6110AcceptanceTest extends AbstractJsonRpcTest {
private static final String GENESIS_FILE = "/jsonrpc/engine/eip6110/genesis.json";
private static final String TEST_CASE_PATH = "/jsonrpc/engine/eip6110/test-cases/";

@ -0,0 +1,33 @@
{
"request": {
"jsonrpc": "2.0",
"id": 1,
"method": "engine_forkchoiceUpdatedV2",
"params": [
{
"headBlockHash": "0xd76266aeddab03ffe29e2cf11a808d2a86f9b8582a9f4a48372f9a7795a95ba4",
"safeBlockHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"finalizedBlockHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
},
{
"timestamp": "0x1235",
"prevRandao": "0x6c9619f4c25184d07b2369f87ffdf9b3786f05ed3bc12c4e086b8850cd9fd606",
"suggestedFeeRecipient": "0x0000000000000000000000000000000000000000",
"withdrawals": []
}
]
},
"response": {
"jsonrpc": "2.0",
"id": 1,
"result": {
"payloadStatus": {
"status": "VALID",
"latestValidHash": "0xd76266aeddab03ffe29e2cf11a808d2a86f9b8582a9f4a48372f9a7795a95ba4",
"validationError": null
},
"payloadId": "0x006221426d1aefcc"
}
},
"statusCode": 200
}

@ -0,0 +1,36 @@
{
"request": {
"jsonrpc": "2.0",
"id": 2,
"method": "engine_getPayloadV2",
"params": [
"0x006221426d1aefcc"
]
},
"response": {
"jsonrpc": "2.0",
"id": 2,
"result": {
"executionPayload": {
"parentHash": "0xd76266aeddab03ffe29e2cf11a808d2a86f9b8582a9f4a48372f9a7795a95ba4",
"feeRecipient": "0x0000000000000000000000000000000000000000",
"stateRoot": "0x7f5cfe1375a61009a22d24512d18035bc8f855129452fa9c6a6be2ef4e9da7db",
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"prevRandao": "0x6c9619f4c25184d07b2369f87ffdf9b3786f05ed3bc12c4e086b8850cd9fd606",
"gasLimit": "0x2ff3d8",
"gasUsed": "0x0",
"timestamp": "0x1235",
"extraData": "0x",
"baseFeePerGas": "0x342770c0",
"transactions": [],
"withdrawals": [],
"deposits": null,
"blockNumber": "0x1",
"receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"blockHash": "0x50c02dc77082fe2060b600cba0e6ce5a491af6d5323b2a90a7bc6359dd18e97b"
},
"blockValue": "0x0"
}
},
"statusCode": 200
}

@ -0,0 +1,38 @@
{
"request": {
"jsonrpc": "2.0",
"id": 3,
"method": "engine_newPayloadV2",
"params": [
{
"parentHash": "0xd76266aeddab03ffe29e2cf11a808d2a86f9b8582a9f4a48372f9a7795a95ba4",
"feeRecipient": "0x0000000000000000000000000000000000000000",
"stateRoot": "0x7f5cfe1375a61009a22d24512d18035bc8f855129452fa9c6a6be2ef4e9da7db",
"receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"prevRandao": "0x6c9619f4c25184d07b2369f87ffdf9b3786f05ed3bc12c4e086b8850cd9fd606",
"blockNumber": "0x1",
"gasLimit": "0x2ff3d8",
"gasUsed": "0x0",
"timestamp": "0x1235",
"extraData": "0x",
"baseFeePerGas": "0x342770c0",
"blockHash": "0x50c02dc77082fe2060b600cba0e6ce5a491af6d5323b2a90a7bc6359dd18e97b",
"transactions": [],
"withdrawals": [],
"dataGasUsed": null,
"excessDataGas": null
}
]
},
"response": {
"jsonrpc": "2.0",
"id": 3,
"result": {
"status": "VALID",
"latestValidHash": "0x50c02dc77082fe2060b600cba0e6ce5a491af6d5323b2a90a7bc6359dd18e97b",
"validationError": null
}
},
"statusCode": 200
}

@ -0,0 +1,39 @@
{
"request": {
"jsonrpc": "2.0",
"id": 4,
"method": "engine_newPayloadV3",
"params": [
{
"parentHash": "0xd76266aeddab03ffe29e2cf11a808d2a86f9b8582a9f4a48372f9a7795a95ba4",
"feeRecipient": "0x0000000000000000000000000000000000000000",
"stateRoot": "0x7f5cfe1375a61009a22d24512d18035bc8f855129452fa9c6a6be2ef4e9da7db",
"receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"prevRandao": "0x6c9619f4c25184d07b2369f87ffdf9b3786f05ed3bc12c4e086b8850cd9fd606",
"blockNumber": "0x1",
"gasLimit": "0x2ff3d8",
"gasUsed": "0x0",
"timestamp": "0x1235",
"extraData": "0x",
"baseFeePerGas": "0x342770c0",
"blockHash": "0x50c02dc77082fe2060b600cba0e6ce5a491af6d5323b2a90a7bc6359dd18e97b",
"transactions": [],
"withdrawals": [],
"dataGasUsed": null,
"excessDataGas": null
},
null
]
},
"response": {
"jsonrpc": "2.0",
"id": 4,
"result": {
"status": "VALID",
"latestValidHash": "0x50c02dc77082fe2060b600cba0e6ce5a491af6d5323b2a90a7bc6359dd18e97b",
"validationError": null
}
},
"statusCode": 200
}

@ -0,0 +1,28 @@
{
"request": {
"jsonrpc": "2.0",
"id": 5,
"method": "engine_forkchoiceUpdatedV2",
"params": [
{
"headBlockHash": "0x50c02dc77082fe2060b600cba0e6ce5a491af6d5323b2a90a7bc6359dd18e97b",
"safeBlockHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"finalizedBlockHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
},
null
]
},
"response": {
"jsonrpc": "2.0",
"id": 5,
"result": {
"payloadStatus": {
"status": "VALID",
"latestValidHash": "0x50c02dc77082fe2060b600cba0e6ce5a491af6d5323b2a90a7bc6359dd18e97b",
"validationError": null
},
"payloadId": null
}
},
"statusCode": 200
}

@ -0,0 +1,33 @@
{
"request": {
"jsonrpc": "2.0",
"id": 6,
"method": "engine_forkchoiceUpdatedV2",
"params": [
{
"headBlockHash": "0x50c02dc77082fe2060b600cba0e6ce5a491af6d5323b2a90a7bc6359dd18e97b",
"safeBlockHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"finalizedBlockHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
},
{
"timestamp": "0x1236",
"prevRandao": "0x5079013331632e5f6db41eccd876e5659c014b8a0a0809794ad1bf64631e030d",
"suggestedFeeRecipient": "0x0000000000000000000000000000000000000000",
"withdrawals": []
}
]
},
"response": {
"jsonrpc": "2.0",
"id": 6,
"result": {
"payloadStatus": {
"status": "VALID",
"latestValidHash": "0x50c02dc77082fe2060b600cba0e6ce5a491af6d5323b2a90a7bc6359dd18e97b",
"validationError": null
},
"payloadId": "0x0062166c2eaa44c9"
}
},
"statusCode": 200
}

@ -0,0 +1,42 @@
{
"request": {
"jsonrpc": "2.0",
"id": 7,
"method": "engine_getPayloadV3",
"params": [
"0x0062166c2eaa44c9"
]
},
"response": {
"jsonrpc": "2.0",
"id": 7,
"result": {
"executionPayload": {
"parentHash": "0x50c02dc77082fe2060b600cba0e6ce5a491af6d5323b2a90a7bc6359dd18e97b",
"feeRecipient": "0x0000000000000000000000000000000000000000",
"stateRoot": "0x7f5cfe1375a61009a22d24512d18035bc8f855129452fa9c6a6be2ef4e9da7db",
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"prevRandao": "0x5079013331632e5f6db41eccd876e5659c014b8a0a0809794ad1bf64631e030d",
"gasLimit": "0x2ff7d8",
"gasUsed": "0x0",
"timestamp": "0x1236",
"extraData": "0x",
"baseFeePerGas": "0x2da282a8",
"excessDataGas": "0x0",
"transactions": [],
"withdrawals": [],
"blockNumber": "0x2",
"dataGasUsed": "0x0",
"receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"blockHash": "0xc33d43425366d661ef70df12faf8ccd66ed7d0c6718d16d14868ba49e6786927"
},
"blockValue": "0x0",
"blobsBundle": {
"commitments": [],
"proofs": [],
"blobs": []
}
}
},
"statusCode": 200
}

@ -0,0 +1,39 @@
{
"request": {
"jsonrpc": "2.0",
"id": 8,
"method": "engine_newPayloadV3",
"params": [
{
"parentHash": "0x50c02dc77082fe2060b600cba0e6ce5a491af6d5323b2a90a7bc6359dd18e97b",
"feeRecipient": "0x0000000000000000000000000000000000000000",
"stateRoot": "0x7f5cfe1375a61009a22d24512d18035bc8f855129452fa9c6a6be2ef4e9da7db",
"receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"prevRandao": "0x5079013331632e5f6db41eccd876e5659c014b8a0a0809794ad1bf64631e030d",
"blockNumber": "0x2",
"gasLimit": "0x2ff7d8",
"gasUsed": "0x0",
"timestamp": "0x1236",
"extraData": "0x",
"baseFeePerGas": "0x2da282a8",
"blockHash": "0xc33d43425366d661ef70df12faf8ccd66ed7d0c6718d16d14868ba49e6786927",
"transactions": [],
"withdrawals": [],
"dataGasUsed": "0x0",
"excessDataGas": "0x0"
},
[]
]
},
"response": {
"jsonrpc": "2.0",
"id": 8,
"result": {
"status": "VALID",
"latestValidHash": "0xc33d43425366d661ef70df12faf8ccd66ed7d0c6718d16d14868ba49e6786927",
"validationError": null
}
},
"statusCode": 200
}

@ -13,7 +13,7 @@
"berlinBlock":0,
"londonBlock":0,
"terminalTotalDifficulty":0,
"shanghaiTime":0,
"cancunTime":0,
"experimentalEipsTime":20,
"clique": {
"period": 5,

@ -39,7 +39,7 @@
],
"deposits" : [],
"blockNumber": "0x2",
"blockHash": "0x58ea3e01b03ac17c68ed3e3d724a021408273fac8a86f42cb30a26be8e93fbe9",
"blockHash": "0x4c4418c408aeadb4659d31d1c05108f26fabf713bb6f8cc487dba8424a725bf5",
"receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"
},
"blockValue": "0x0"

@ -1562,6 +1562,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
runner.awaitStop();
} catch (final Exception e) {
logger.error("Failed to start Besu", e);
throw new ParameterException(this.commandLine, e.getMessage(), e);
}
}
@ -1875,12 +1876,6 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
}
if (getActualGenesisConfigOptions().getCancunTime().isPresent()) {
// if custom genesis provided, then trusted setup file is mandatory
if (genesisFile != null && kzgTrustedSetupFile == null) {
throw new ParameterException(
this.commandLine,
"--kzg-trusted-setup is mandatory when providing a custom genesis that support data blobs");
}
if (kzgTrustedSetupFile != null) {
KZGPointEvalPrecompiledContract.init(kzgTrustedSetupFile);
} else {

@ -15,6 +15,7 @@
package org.hyperledger.besu.services;
import static com.google.common.base.Preconditions.checkArgument;
import static org.hyperledger.besu.ethereum.mainnet.feemarket.ExcessDataGasCalculator.calculateExcessDataGasForParent;
import org.hyperledger.besu.datatypes.DataGas;
import org.hyperledger.besu.datatypes.Hash;
@ -113,9 +114,11 @@ public class TraceServiceImpl implements TraceService {
final Wei dataGasPrice =
protocolSpec
.getFeeMarket()
.dataPrice(
.dataPricePerGas(
maybeParentHeader
.flatMap(BlockHeader::getExcessDataGas)
.map(
parent ->
calculateExcessDataGasForParent(protocolSpec, parent))
.orElse(DataGas.ZERO));
tracer.traceStartTransaction(transaction);

@ -5663,18 +5663,6 @@ public class BesuCommandTest extends CommandTestAbstract {
.contains("--kzg-trusted-setup can only be specified on networks with data blobs enabled");
}
@Test
public void kzgTrustedSetupFileIsMandatoryWithCustomGenesisFile()
throws IOException, URISyntaxException {
final Path genesisFileWithBlobs = createFakeGenesisFile(GENESIS_WITH_DATA_BLOBS_ENABLED);
parseCommand("--genesis-file", genesisFileWithBlobs.toString());
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8))
.contains(
"--kzg-trusted-setup is mandatory when providing a custom genesis that support data blobs");
}
@Test
public void kzgTrustedSetupFileLoadedWithCustomGenesisFile()
throws IOException, URISyntaxException {

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

File diff suppressed because it is too large Load Diff

@ -247,6 +247,24 @@ public class GenesisConfigFile {
return JsonUtil.getValueAsString(configRoot, "nonce", "0x0");
}
/**
* Gets excess data gas.
*
* @return the excess data gas
*/
public String getExcessDataGas() {
return JsonUtil.getValueAsString(configRoot, "excessdatagas", "0x0");
}
/**
* Gets data gas used.
*
* @return the data gas used
*/
public String getDataGasUsed() {
return JsonUtil.getValueAsString(configRoot, "datagasused", "0x0");
}
/**
* Gets coinbase.
*

File diff suppressed because it is too large Load Diff

@ -52,7 +52,9 @@ public class CombinedProtocolScheduleFactory {
Optional.ofNullable(forkSpecs.higher(spec)).map(ForkSpec::getBlock);
protocolSchedule.getScheduledProtocolSpecs().stream()
.filter(protocolSpecMatchesConsensusBlockRange(spec.getBlock(), endBlock))
.forEach(s -> combinedProtocolSchedule.putBlockNumberMilestone(s.milestone(), s.spec()));
.forEach(
s ->
combinedProtocolSchedule.putBlockNumberMilestone(s.fork().milestone(), s.spec()));
// When moving to a new consensus mechanism we want to use the last milestone but created by
// our consensus mechanism's BesuControllerBuilder so any additional rules are applied
@ -67,7 +69,7 @@ public class CombinedProtocolScheduleFactory {
private Predicate<ScheduledProtocolSpec> protocolSpecMatchesConsensusBlockRange(
final long startBlock, final Optional<Long> endBlock) {
return scheduledProtocolSpec ->
scheduledProtocolSpec.milestone() >= startBlock
&& endBlock.map(b -> scheduledProtocolSpec.milestone() < b).orElse(true);
scheduledProtocolSpec.fork().milestone() >= startBlock
&& endBlock.map(b -> scheduledProtocolSpec.fork().milestone() < b).orElse(true);
}
}

@ -49,11 +49,12 @@ public class BftProtocolSchedule extends DefaultProtocolSchedule {
checkArgument(
!protocolSpecs.isEmpty(), "At least 1 milestone must be provided to the protocol schedule");
checkArgument(
protocolSpecs.last().milestone() == 0, "There must be a milestone starting from block 0");
protocolSpecs.last().fork().milestone() == 0,
"There must be a milestone starting from block 0");
// protocolSpecs is sorted in descending block order, so the first one we find that's lower than
// the requested level will be the most appropriate spec
for (final ScheduledProtocolSpec s : protocolSpecs) {
if (number >= s.milestone()) {
if (number >= s.fork().milestone()) {
return s.spec();
}
}

@ -41,7 +41,7 @@ import com.google.common.collect.Lists;
import org.apache.tuweni.bytes.Bytes;
import org.junit.jupiter.api.Test;
public class IbftExtraDataEncoderTest {
public class IbftExtraDataRLPEncoderTest {
private static final Supplier<SignatureAlgorithm> SIGNATURE_ALGORITHM =
Suppliers.memoize(SignatureAlgorithmFactory::getInstance);

@ -203,6 +203,13 @@ public class TransitionProtocolSchedule implements ProtocolSchedule {
"Should not use TransitionProtocolSchedule wrapper class to create milestones");
}
@Override
public Optional<ScheduledProtocolSpec.Hardfork> hardforkFor(
final Predicate<ScheduledProtocolSpec> predicate) {
return this.transitionUtils.dispatchFunctionAccordingToMergeState(
schedule -> schedule.hardforkFor(predicate));
}
/**
* List milestones.
*

@ -0,0 +1,64 @@
/*
* 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.datatypes;
import org.hyperledger.besu.ethereum.rlp.RLPInput;
import org.hyperledger.besu.ethereum.rlp.RLPOutput;
import org.apache.tuweni.bytes.Bytes;
/** Arbitrary data for use in the KZG scheme. */
public class Blob {
final Bytes data;
/**
* Create a new Blob.
*
* @param data that represents the blob.
*/
public Blob(final Bytes data) {
this.data = data;
}
/**
* Read a Blob from an RLPInput.
*
* @param input to read from.
* @return the Blob.
*/
public static Blob readFrom(final RLPInput input) {
final Bytes bytes = input.readBytes();
return new Blob(bytes);
}
/**
* Write the Blob to an RLPOutput.
*
* @param out to write to.
*/
public void writeTo(final RLPOutput out) {
out.writeBytes(data);
}
/**
* Get the data of the Blob.
*
* @return the data.
*/
public Bytes getData() {
return data;
}
}

@ -0,0 +1,88 @@
/*
* 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.datatypes;
import java.security.InvalidParameterException;
import java.util.List;
/** A class to hold the blobs, commitments, proofs and versioned hashes for a set of blobs. */
public class BlobsWithCommitments {
private final List<KZGCommitment> kzgCommitments;
private final List<Blob> blobs;
private final List<KZGProof> kzgProofs;
private final List<VersionedHash> versionedHashes;
/**
* A class to hold the blobs, commitments and proofs for a set of blobs.
*
* @param kzgCommitments commitments for the blobs
* @param blobs list of blobs to be committed to
* @param kzgProofs proofs for the commitments
* @param versionedHashes hashes of the commitments
*/
public BlobsWithCommitments(
final List<KZGCommitment> kzgCommitments,
final List<Blob> blobs,
final List<KZGProof> kzgProofs,
final List<VersionedHash> versionedHashes) {
if (blobs.size() != kzgCommitments.size()
|| blobs.size() != kzgProofs.size()
|| kzgCommitments.size() != versionedHashes.size()) {
throw new InvalidParameterException(
"There must be an equal number of blobs, commitments and proofs");
}
this.kzgCommitments = kzgCommitments;
this.blobs = blobs;
this.kzgProofs = kzgProofs;
this.versionedHashes = versionedHashes;
}
/**
* Get the blobs.
*
* @return the blobs
*/
public List<Blob> getBlobs() {
return blobs;
}
/**
* Get the commitments.
*
* @return the commitments
*/
public List<KZGCommitment> getKzgCommitments() {
return kzgCommitments;
}
/**
* Get the proofs.
*
* @return the proofs
*/
public List<KZGProof> getKzgProofs() {
return kzgProofs;
}
/**
* Get the hashes.
*
* @return the hashes
*/
public List<VersionedHash> getVersionedHashes() {
return versionedHashes;
}
}

@ -17,11 +17,11 @@ package org.hyperledger.besu.datatypes;
import java.math.BigInteger;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.units.bigints.BaseUInt256Value;
import org.apache.tuweni.units.bigints.UInt256;
import org.apache.tuweni.units.bigints.BaseUInt64Value;
import org.apache.tuweni.units.bigints.UInt64;
/** A particular quantity of DataGas */
public final class DataGas extends BaseUInt256Value<DataGas> implements Quantity {
public final class DataGas extends BaseUInt64Value<DataGas> implements Quantity {
/** The constant ZERO. */
public static final DataGas ZERO = of(0);
@ -30,27 +30,27 @@ public final class DataGas extends BaseUInt256Value<DataGas> implements Quantity
public static final DataGas ONE = of(1);
/** The constant MAX_DATA_GAS. */
public static final DataGas MAX_DATA_GAS = of(UInt256.MAX_VALUE);
public static final DataGas MAX_DATA_GAS = of(UInt64.MAX_VALUE);
/**
* Instantiates a new DataGas.
*
* @param value the value
*/
DataGas(final UInt256 value) {
DataGas(final UInt64 value) {
super(value, DataGas::new);
}
private DataGas(final long v) {
this(UInt256.valueOf(v));
this(UInt64.valueOf(v));
}
private DataGas(final BigInteger v) {
this(UInt256.valueOf(v));
this(UInt64.valueOf(v));
}
private DataGas(final String hexString) {
this(UInt256.fromHexString(hexString));
this(UInt64.fromHexString(hexString));
}
/**
@ -79,7 +79,7 @@ public final class DataGas extends BaseUInt256Value<DataGas> implements Quantity
* @param value the value
* @return the data gas
*/
public static DataGas of(final UInt256 value) {
public static DataGas of(final UInt64 value) {
return new DataGas(value);
}
@ -100,7 +100,7 @@ public final class DataGas extends BaseUInt256Value<DataGas> implements Quantity
* @return the data gas
*/
public static DataGas wrap(final Bytes value) {
return new DataGas(UInt256.fromBytes(value));
return new DataGas(UInt64.fromBytes(value));
}
/**

@ -47,7 +47,12 @@ public class Hash extends DelegatingBytes32 {
*/
public static final Hash EMPTY = hash(Bytes.EMPTY);
private Hash(final Bytes32 bytes) {
/**
* Instantiates a new Hash.
*
* @param bytes raw bytes
*/
protected Hash(final Bytes32 bytes) {
super(bytes);
}

@ -0,0 +1,63 @@
/*
* 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.datatypes;
import org.hyperledger.besu.ethereum.rlp.RLPInput;
import org.hyperledger.besu.ethereum.rlp.RLPOutput;
import org.apache.tuweni.bytes.Bytes;
/** This class contains the data for a KZG commitment. */
public class KZGCommitment {
final Bytes data;
/**
* Constructor for a KZG commitment.
*
* @param data The data for the KZG commitment.
*/
public KZGCommitment(final Bytes data) {
this.data = data;
}
/**
* Reads a KZG commitment from the RLP input.
*
* @param input The RLP input.
* @return The KZG commitment.
*/
public static KZGCommitment readFrom(final RLPInput input) {
final Bytes bytes = input.readBytes();
return new KZGCommitment(bytes);
}
/**
* Writes the KZG commitment to the RLP output.
*
* @param out The RLP output.
*/
public void writeTo(final RLPOutput out) {
out.writeBytes(data);
}
/**
* Gets the data for the KZG commitment.
*
* @return The data for the KZG commitment.
*/
public Bytes getData() {
return data;
}
}

@ -0,0 +1,63 @@
/*
* 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.datatypes;
import org.hyperledger.besu.ethereum.rlp.RLPInput;
import org.hyperledger.besu.ethereum.rlp.RLPOutput;
import org.apache.tuweni.bytes.Bytes;
/** This class contains the data for a KZG proof for a KZG commitment. */
public class KZGProof {
final Bytes data;
/**
* Constructor for a KZG proof.
*
* @param data The data for the KZG proof.
*/
public KZGProof(final Bytes data) {
this.data = data;
}
/**
* Reads a KZG proof from the RLP input.
*
* @param input The RLP input.
* @return The KZG proof.
*/
public static KZGProof readFrom(final RLPInput input) {
final Bytes bytes = input.readBytes();
return new KZGProof(bytes);
}
/**
* Writes the KZG proof to the RLP output.
*
* @param out The RLP output.
*/
public void writeTo(final RLPOutput out) {
out.writeBytes(data);
}
/**
* Gets the data for the KZG proof.
*
* @return The data for the KZG proof.
*/
public Bytes getData() {
return data;
}
}

@ -0,0 +1,42 @@
/*
* 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.datatypes;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
/** A Sha256Hash is a Hash that has been generated using the SHA-256 algorithm. */
public class Sha256Hash extends Hash {
/**
* Construct a Sha256Hash from a Bytes32 value.
*
* @param bytes raw bytes of the hash
*/
private Sha256Hash(final Bytes32 bytes) {
super(bytes);
}
/**
* Construct a Sha256Hash from a Bytes value.
*
* @param value The value to hash.
* @return The Sha256Hash of the value.
*/
public static Hash sha256(final Bytes value) {
return new Sha256Hash(org.hyperledger.besu.crypto.Hash.sha256(value));
}
}

@ -69,7 +69,10 @@ public enum TransactionType {
public static TransactionType of(final int serializedTypeValue) {
return Arrays.stream(
new TransactionType[] {
TransactionType.FRONTIER, TransactionType.ACCESS_LIST, TransactionType.EIP1559
TransactionType.FRONTIER,
TransactionType.ACCESS_LIST,
TransactionType.EIP1559,
TransactionType.BLOB
})
.filter(transactionType -> transactionType.typeValue == serializedTypeValue)
.findFirst()

@ -0,0 +1,107 @@
/*
* 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.datatypes;
import java.util.Objects;
import com.fasterxml.jackson.annotation.JsonValue;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
/**
* A VersionedHash is a Hash that forfeits its most significant byte to indicate the hashing
* algorithm which was used.
*/
public class VersionedHash {
/**
* The versionedHash value. The first byte is the version id, the remainder is the subsequent
* bytes of the hash.
*/
Bytes32 hashish;
/** The version id for sha256 hashes. */
public static final byte SHA256_VERSION_ID = 1;
/** A default versioned hash, nonsensical but valid. */
public static VersionedHash DEFAULT_VERSIONED_HASH =
new VersionedHash(SHA256_VERSION_ID, Hash.ZERO);
/**
* Construct a VersionedHash from a Bytes32 value.
*
* @param versionId The version id of the hash. 01 for sha256.
* @param hash The hash value being versioned.
*/
public VersionedHash(final byte versionId, final Hash hash) {
if (versionId != SHA256_VERSION_ID) {
throw new IllegalArgumentException("Only supported hash version is 0x01, sha256 hash.");
}
this.hashish =
Bytes32.wrap(
Bytes.concatenate(Bytes.of(SHA256_VERSION_ID), hash.slice(1, hash.size() - 1)));
}
/**
* Parse a VersionedHash from a Bytes32 value.
*
* @param typedHash raw versioned hash bytes to parse.
*/
public VersionedHash(final Bytes32 typedHash) {
byte versionId = typedHash.get(0);
if (versionId != SHA256_VERSION_ID) {
throw new IllegalArgumentException("Only supported hash version is 0x01, sha256 hash.");
}
this.hashish = typedHash;
}
/**
* Convert it to raw bytes.
*
* @return The hash value.
*/
public Bytes32 toBytes() {
return this.hashish;
}
/**
* The version id of the hash.
*
* @return the version id.
*/
public byte getVersionId() {
return this.hashish.get(0);
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
VersionedHash that = (VersionedHash) o;
return getVersionId() == that.getVersionId() && Objects.equals(this.toBytes(), that.toBytes());
}
@Override
public int hashCode() {
return Objects.hash(getVersionId(), hashish);
}
@JsonValue
@Override
public String toString() {
return this.toBytes().toHexString();
}
}

@ -0,0 +1,40 @@
/*
* 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.datatypes;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.security.InvalidParameterException;
import java.util.List;
import org.apache.tuweni.bytes.Bytes;
import org.junit.jupiter.api.Test;
public class BlobsWithCommitmentsTest {
@Test
public void blobsWithCommitmentsMustHaveSameNumberOfElements() {
String actualMessage =
assertThrows(
InvalidParameterException.class,
() ->
new BlobsWithCommitments(
List.of(new KZGCommitment(Bytes.of(1))), List.of(), List.of(), List.of()))
.getMessage();
final String expectedMessage = "There must be an equal number of blobs, commitments and proofs";
assertThat(actualMessage).isEqualTo(expectedMessage);
}
}

@ -0,0 +1,34 @@
/*
* 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.datatypes;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.apache.tuweni.bytes.Bytes32;
import org.junit.jupiter.api.Test;
class VersionedHashTest {
@Test
public void throwsOnUnsupportedHashType() {
assertThrows(IllegalArgumentException.class, () -> new VersionedHash((byte) 0, Hash.ZERO));
}
@Test
public void throwsOnParsingUnsupportedHashType() {
assertThrows(IllegalArgumentException.class, () -> new VersionedHash(Bytes32.ZERO));
}
}

@ -127,6 +127,7 @@ public class JsonRpcResponseUtils {
mixHash,
nonce,
withdrawalsRoot,
null, // ToDo 4844: set with the value of data_gas_used field
null, // ToDo 4844: set with the value of excess_data_gas field
depositsRoot,
blockHeaderFunctions);
@ -194,7 +195,6 @@ public class JsonRpcResponseUtils {
.byteValueExact()))
.payload(bytes(input))
.sender(address(fromAddress))
.v(bigInteger(v))
.build();
return new TransactionCompleteResult(
@ -224,10 +224,6 @@ public class JsonRpcResponseUtils {
return prefixedHex.startsWith("0x") ? prefixedHex.substring(2) : prefixedHex;
}
private BigInteger bigInteger(final String hex) {
return hex == null ? null : new BigInteger(removeHexPrefix(hex), HEX_RADIX);
}
private Wei wei(final String hex) {
return Wei.fromHexString(hex);
}

@ -17,12 +17,14 @@ package org.hyperledger.besu.ethereum.api.graphql.internal.pojoadapter;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.api.graphql.GraphQLContextType;
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.api.query.TransactionReceiptWithMetadata;
import org.hyperledger.besu.ethereum.api.query.TransactionWithMetadata;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.LogWithMetadata;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput;
import java.util.ArrayList;
@ -46,12 +48,15 @@ public class TransactionAdapter extends AdapterBase {
final DataFetchingEnvironment environment) {
if (transactionReceiptWithMetadata == null) {
final BlockchainQueries query = getBlockchainQueries(environment);
final ProtocolSchedule protocolSchedule =
environment.getGraphQlContext().get(GraphQLContextType.PROTOCOL_SCHEDULE);
final Transaction transaction = transactionWithMetadata.getTransaction();
if (transaction == null) {
transactionReceiptWithMetadata = Optional.empty();
} else {
transactionReceiptWithMetadata =
query.transactionReceiptByTransactionHash(transaction.getHash());
query.transactionReceiptByTransactionHash(transaction.getHash(), protocolSchedule);
}
}
return transactionReceiptWithMetadata;
@ -187,6 +192,9 @@ public class TransactionAdapter extends AdapterBase {
public List<LogAdapter> getLogs(final DataFetchingEnvironment environment) {
final BlockchainQueries query = getBlockchainQueries(environment);
final ProtocolSchedule protocolSchedule =
environment.getGraphQlContext().get(GraphQLContextType.PROTOCOL_SCHEDULE);
final Hash hash = transactionWithMetadata.getTransaction().getHash();
final Optional<BlockHeader> maybeBlockHeader =
@ -201,7 +209,7 @@ public class TransactionAdapter extends AdapterBase {
}
final Optional<TransactionReceiptWithMetadata> maybeTransactionReceiptWithMetadata =
query.transactionReceiptByTransactionHash(hash);
query.transactionReceiptByTransactionHash(hash, protocolSchedule);
final List<LogAdapter> results = new ArrayList<>();
if (maybeTransactionReceiptWithMetadata.isPresent()) {
final List<LogWithMetadata> logs =

@ -52,8 +52,10 @@ public enum RpcMethod {
DEBUG_GET_RAW_TRANSACTION("debug_getRawTransaction"),
ENGINE_GET_PAYLOAD_V1("engine_getPayloadV1"),
ENGINE_GET_PAYLOAD_V2("engine_getPayloadV2"),
ENGINE_GET_PAYLOAD_V3("engine_getPayloadV3"),
ENGINE_NEW_PAYLOAD_V1("engine_newPayloadV1"),
ENGINE_NEW_PAYLOAD_V2("engine_newPayloadV2"),
ENGINE_NEW_PAYLOAD_V3("engine_newPayloadV3"),
ENGINE_FORKCHOICE_UPDATED_V1("engine_forkchoiceUpdatedV1"),
ENGINE_FORKCHOICE_UPDATED_V2("engine_forkchoiceUpdatedV2"),
ENGINE_EXCHANGE_TRANSITION_CONFIGURATION("engine_exchangeTransitionConfigurationV1"),

@ -18,6 +18,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonR
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
@ -137,6 +138,10 @@ public class JsonRpcRequest {
return parameterAccessor.optional(params, index, paramClass);
}
public <T> Optional<List<T>> getOptionalList(final int index, final Class<T> paramClass) {
return parameterAccessor.optionalList(params, index, paramClass);
}
@Override
public String toString() {
return "JsonRpcRequest{"

@ -14,6 +14,7 @@
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Supplier;
@ -68,6 +69,10 @@ public class JsonRpcRequestContext {
return jsonRpcRequest.getOptionalParameter(index, paramClass);
}
public <T> Optional<List<T>> getOptionalList(final int index, final Class<T> listOf) {
return jsonRpcRequest.getOptionalList(index, listOf);
}
@Override
public boolean equals(final Object o) {
if (this == o) {

@ -86,7 +86,8 @@ public class EthGetMinerDataByBlockHash implements JsonRpcMethod {
.map(
t ->
blockchainQueries
.transactionReceiptByTransactionHash(t.getTransaction().getHash())
.transactionReceiptByTransactionHash(
t.getTransaction().getHash(), protocolSchedule)
.map(
receipt ->
receipt

@ -24,14 +24,19 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.TransactionRec
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.TransactionReceiptStatusResult;
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.api.query.TransactionReceiptWithMetadata;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.TransactionReceiptType;
public class EthGetTransactionReceipt implements JsonRpcMethod {
private final BlockchainQueries blockchainQueries;
public EthGetTransactionReceipt(final BlockchainQueries blockchainQueries) {
private final ProtocolSchedule protocolSchedule;
public EthGetTransactionReceipt(
final BlockchainQueries blockchainQueries, final ProtocolSchedule protocolSchedule) {
this.blockchainQueries = blockchainQueries;
this.protocolSchedule = protocolSchedule;
}
@Override
@ -44,7 +49,7 @@ public class EthGetTransactionReceipt implements JsonRpcMethod {
final Hash hash = requestContext.getRequiredParameter(0, Hash.class);
final TransactionReceiptResult result =
blockchainQueries
.transactionReceiptByTransactionHash(hash)
.transactionReceiptByTransactionHash(hash, protocolSchedule)
.map(this::getResult)
.orElse(null);
return new JsonRpcSuccessResponse(requestContext.getRequest().getId(), result);

@ -14,6 +14,8 @@
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods;
import static org.hyperledger.besu.ethereum.mainnet.feemarket.ExcessDataGasCalculator.calculateExcessDataGasForParent;
import org.hyperledger.besu.datatypes.DataGas;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.TransactionTrace;
@ -89,8 +91,10 @@ public class ExecuteTransactionStep implements Function<TransactionTrace, Transa
final Wei dataGasPrice =
protocolSpec
.getFeeMarket()
.dataPrice(
maybeParentHeader.flatMap(BlockHeader::getExcessDataGas).orElse(DataGas.ZERO));
.dataPricePerGas(
maybeParentHeader
.map(parent -> calculateExcessDataGasForParent(protocolSpec, parent))
.orElse(DataGas.ZERO));
final BlockHashLookup blockHashLookup = new CachingBlockHashLookup(header, blockchain);
result =
transactionProcessor.processTransaction(

@ -25,7 +25,9 @@ import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType.INVALID_PARAMS;
import org.hyperledger.besu.consensus.merge.blockcreation.MergeMiningCoordinator;
import org.hyperledger.besu.datatypes.DataGas;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.VersionedHash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.BlockProcessingResult;
import org.hyperledger.besu.ethereum.ProtocolContext;
@ -52,10 +54,14 @@ import org.hyperledger.besu.ethereum.eth.manager.EthPeers;
import org.hyperledger.besu.ethereum.mainnet.BodyValidation;
import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec;
import org.hyperledger.besu.ethereum.mainnet.ValidationResult;
import org.hyperledger.besu.ethereum.mainnet.feemarket.ExcessDataGasCalculator;
import org.hyperledger.besu.ethereum.rlp.RLPException;
import org.hyperledger.besu.ethereum.trie.MerkleTrieException;
import org.hyperledger.besu.plugin.services.exception.StorageException;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -65,6 +71,7 @@ import java.util.stream.Collectors;
import io.vertx.core.Vertx;
import io.vertx.core.json.Json;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -97,13 +104,32 @@ public abstract class AbstractEngineNewPayload extends ExecutionEngineJsonRpcMet
final EnginePayloadParameter blockParam =
requestContext.getRequiredParameter(0, EnginePayloadParameter.class);
Optional<List<String>> maybeVersionedHashParam =
requestContext.getOptionalList(1, String.class);
Object reqId = requestContext.getRequest().getId();
Optional<List<VersionedHash>> maybeVersionedHashes;
try {
maybeVersionedHashes = extractVersionedHashes(maybeVersionedHashParam);
} catch (RuntimeException ex) {
return respondWithInvalid(reqId, blockParam, null, INVALID, "Invalid versionedHash");
}
final Optional<BlockHeader> maybeParentHeader =
protocolContext.getBlockchain().getBlockHeader(blockParam.getParentHash());
LOG.atTrace()
.setMessage("blockparam: {}")
.addArgument(() -> Json.encodePrettily(blockParam))
.log();
/*
ValidationResult<JsonRpcError> forkValidationResult = validateForkSupported(reqId, blockParam);
if (!forkValidationResult.isValid()) {
return new JsonRpcErrorResponse(reqId, forkValidationResult.getInvalidReason());
}
*/
final Optional<List<Withdrawal>> maybeWithdrawals =
Optional.ofNullable(blockParam.getWithdrawals())
.map(ws -> ws.stream().map(WithdrawalParameter::toWithdrawal).collect(toList()));
@ -172,7 +198,10 @@ public abstract class AbstractEngineNewPayload extends ExecutionEngineJsonRpcMet
blockParam.getPrevRandao(),
0,
maybeWithdrawals.map(BodyValidation::withdrawalsRoot).orElse(null),
null,
blockParam.getDataGasUsed() == null ? null : blockParam.getDataGasUsed(),
blockParam.getExcessDataGas() == null
? null
: DataGas.fromHexString(blockParam.getExcessDataGas()),
maybeDeposits.map(BodyValidation::depositsRoot).orElse(null),
headerFunctions);
@ -184,8 +213,30 @@ public abstract class AbstractEngineNewPayload extends ExecutionEngineJsonRpcMet
"Computed block hash %s does not match block hash parameter %s",
newBlockHeader.getBlockHash(), blockParam.getBlockHash());
LOG.debug(errorMessage);
return respondWithInvalid(reqId, blockParam, null, getInvalidBlockHashStatus(), errorMessage);
return respondWithInvalid(
reqId,
blockParam,
mergeCoordinator.getLatestValidAncestor(blockParam.getParentHash()).orElse(null),
getInvalidBlockHashStatus(),
errorMessage);
}
ValidationResult<RpcErrorType> blobValidationResult =
validateBlobs(
transactions,
newBlockHeader,
maybeParentHeader,
maybeVersionedHashes,
protocolSchedule.getByBlockHeader(newBlockHeader));
if (!blobValidationResult.isValid()) {
return respondWithInvalid(
reqId,
blockParam,
null,
getInvalidBlockHashStatus(),
blobValidationResult.getErrorMessage());
}
// do we already have this payload
if (protocolContext.getBlockchain().getBlockByHash(newBlockHeader.getBlockHash()).isPresent()) {
LOG.debug("block already present");
@ -202,8 +253,6 @@ public abstract class AbstractEngineNewPayload extends ExecutionEngineJsonRpcMet
"Block already present in bad block manager.");
}
final Optional<BlockHeader> maybeParentHeader =
protocolContext.getBlockchain().getBlockHeader(blockParam.getParentHash());
if (maybeParentHeader.isPresent()
&& (blockParam.getTimestamp() <= maybeParentHeader.get().getTimestamp())) {
return respondWithInvalid(
@ -225,7 +274,6 @@ public abstract class AbstractEngineNewPayload extends ExecutionEngineJsonRpcMet
.addArgument(block::toLogString)
.log();
mergeCoordinator.appendNewPayloadToSync(block);
return respondWith(reqId, blockParam, null, SYNCING);
}
@ -341,6 +389,98 @@ public abstract class AbstractEngineNewPayload extends ExecutionEngineJsonRpcMet
return INVALID;
}
protected ValidationResult<RpcErrorType> validateForkSupported(
final Object id, final EnginePayloadParameter payloadParameter) {
return ValidationResult.valid();
}
protected ValidationResult<RpcErrorType> validateBlobs(
final List<Transaction> transactions,
final BlockHeader header,
final Optional<BlockHeader> maybeParentHeader,
final Optional<List<VersionedHash>> maybeVersionedHashes,
final ProtocolSpec protocolSpec) {
var blobTransactions =
transactions.stream().filter(transaction -> transaction.getType().supportsBlob()).toList();
final List<VersionedHash> transactionVersionedHashes = new ArrayList<>();
for (Transaction transaction : blobTransactions) {
var versionedHashes = transaction.getVersionedHashes();
// blob transactions must have at least one blob
if (versionedHashes.isEmpty()) {
return ValidationResult.invalid(
RpcErrorType.INVALID_PARAMS, "There must be at least one blob");
}
transactionVersionedHashes.addAll(versionedHashes.get());
}
if (maybeVersionedHashes.isEmpty() && !transactionVersionedHashes.isEmpty()) {
return ValidationResult.invalid(
RpcErrorType.INVALID_PARAMS, "Payload must contain versioned hashes for transactions");
}
// Validate versionedHashesParam
if (maybeVersionedHashes.isPresent()
&& !maybeVersionedHashes.get().equals(transactionVersionedHashes)) {
return ValidationResult.invalid(
RpcErrorType.INVALID_PARAMS,
"Versioned hashes from blob transactions do not match expected values");
}
// Validate excessDataGas
if (maybeParentHeader.isPresent()) {
if (!validateExcessDataGas(header, maybeParentHeader.get(), protocolSpec)) {
return ValidationResult.invalid(
RpcErrorType.INVALID_PARAMS,
"Payload excessDataGas does not match calculated excessDataGas");
}
}
// Validate dataGasUsed
if (header.getDataGasUsed().isPresent() && maybeVersionedHashes.isPresent()) {
if (!validateDataGasUsed(header, maybeVersionedHashes.get(), protocolSpec)) {
return ValidationResult.invalid(
RpcErrorType.INVALID_PARAMS,
"Payload DataGasUsed does not match calculated DataGasUsed");
}
}
return ValidationResult.valid();
}
private boolean validateExcessDataGas(
final BlockHeader header, final BlockHeader parentHeader, final ProtocolSpec protocolSpec) {
DataGas calculatedDataGas =
ExcessDataGasCalculator.calculateExcessDataGasForParent(protocolSpec, parentHeader);
return header.getExcessDataGas().orElse(DataGas.ZERO).equals(calculatedDataGas);
}
private boolean validateDataGasUsed(
final BlockHeader header,
final List<VersionedHash> maybeVersionedHashes,
final ProtocolSpec protocolSpec) {
var calculatedDataGas =
protocolSpec.getGasCalculator().dataGasCost(maybeVersionedHashes.size());
return header.getDataGasUsed().orElse(0L).equals(calculatedDataGas);
}
private Optional<List<VersionedHash>> extractVersionedHashes(
final Optional<List<String>> maybeVersionedHashParam) {
return maybeVersionedHashParam.map(
versionedHashes ->
versionedHashes.stream()
.map(Bytes32::fromHexString)
.map(
hash -> {
try {
return new VersionedHash(hash);
} catch (InvalidParameterException e) {
throw new RuntimeException(e);
}
})
.collect(Collectors.toList()));
}
private void logImportedBlockInfo(final Block block, final double timeInS) {
final StringBuilder message = new StringBuilder();
message.append("Imported #%,d / %d tx");

@ -0,0 +1,91 @@
/*
* 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.consensus.merge.blockcreation.PayloadIdentifier;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlockResultFactory;
import org.hyperledger.besu.ethereum.core.BlockWithReceipts;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.ScheduledProtocolSpec;
import java.util.Optional;
import io.vertx.core.Vertx;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class EngineGetPayloadV3 extends AbstractEngineGetPayload {
private static final Logger LOG = LoggerFactory.getLogger(EngineGetPayloadV3.class);
private final Optional<ScheduledProtocolSpec.Hardfork> shanghai;
private final Optional<ScheduledProtocolSpec.Hardfork> cancun;
public EngineGetPayloadV3(
final Vertx vertx,
final ProtocolContext protocolContext,
final MergeMiningCoordinator mergeMiningCoordinator,
final BlockResultFactory blockResultFactory,
final EngineCallListener engineCallListener,
final ProtocolSchedule schedule) {
super(vertx, protocolContext, mergeMiningCoordinator, blockResultFactory, engineCallListener);
this.shanghai = schedule.hardforkFor(s -> s.fork().name().equalsIgnoreCase("Shanghai"));
this.cancun = schedule.hardforkFor(s -> s.fork().name().equalsIgnoreCase("Cancun"));
}
@Override
public String getName() {
return RpcMethod.ENGINE_GET_PAYLOAD_V3.getMethodName();
}
@Override
protected JsonRpcResponse createResponse(
final JsonRpcRequestContext request,
final PayloadIdentifier payloadId,
final BlockWithReceipts blockWithReceipts) {
try {
long builtAt = blockWithReceipts.getHeader().getTimestamp();
if (this.shanghai.isPresent() && builtAt < this.shanghai.get().milestone()) {
return new JsonRpcSuccessResponse(
request.getRequest().getId(),
blockResultFactory.payloadTransactionCompleteV1(blockWithReceipts.getBlock()));
} else if (this.shanghai.isPresent()
&& builtAt >= this.shanghai.get().milestone()
&& this.cancun.isPresent()
&& builtAt < this.cancun.get().milestone()) {
return new JsonRpcSuccessResponse(
request.getRequest().getId(),
blockResultFactory.payloadTransactionCompleteV2(blockWithReceipts));
} else {
return new JsonRpcSuccessResponse(
request.getRequest().getId(),
blockResultFactory.payloadTransactionCompleteV3(blockWithReceipts));
}
} catch (ClassCastException e) {
LOG.error("configuration error, can't call V3 endpoint with non-default protocol schedule");
return new JsonRpcErrorResponse(request.getRequest().getId(), RpcErrorType.INTERNAL_ERROR);
}
}
}

@ -17,10 +17,19 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod.EngineStatus.INVALID_BLOCK_HASH;
import org.hyperledger.besu.consensus.merge.blockcreation.MergeMiningCoordinator;
import org.hyperledger.besu.datatypes.VersionedHash;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.eth.manager.EthPeers;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec;
import org.hyperledger.besu.ethereum.mainnet.ValidationResult;
import java.util.List;
import java.util.Optional;
import io.vertx.core.Vertx;
@ -50,4 +59,14 @@ public class EngineNewPayloadV1 extends AbstractEngineNewPayload {
protected EngineStatus getInvalidBlockHashStatus() {
return INVALID_BLOCK_HASH;
}
@Override
protected ValidationResult<RpcErrorType> validateBlobs(
final List<Transaction> transactions,
final BlockHeader header,
final Optional<BlockHeader> maybeParentHeader,
final Optional<List<VersionedHash>> maybeVersionedHashParam,
final ProtocolSpec protocolSpec) {
return ValidationResult.valid();
}
}

@ -15,10 +15,19 @@
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine;
import org.hyperledger.besu.consensus.merge.blockcreation.MergeMiningCoordinator;
import org.hyperledger.besu.datatypes.VersionedHash;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.eth.manager.EthPeers;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec;
import org.hyperledger.besu.ethereum.mainnet.ValidationResult;
import java.util.List;
import java.util.Optional;
import io.vertx.core.Vertx;
@ -38,4 +47,14 @@ public class EngineNewPayloadV2 extends AbstractEngineNewPayload {
public String getName() {
return RpcMethod.ENGINE_NEW_PAYLOAD_V2.getMethodName();
}
@Override
protected ValidationResult<RpcErrorType> validateBlobs(
final List<Transaction> transactions,
final BlockHeader header,
final Optional<BlockHeader> maybeParentHeader,
final Optional<List<VersionedHash>> maybeVersionedHashParam,
final ProtocolSpec protocolSpec) {
return ValidationResult.valid();
}
}

@ -0,0 +1,65 @@
/*
* 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.api.jsonrpc.internal.parameters.EnginePayloadParameter;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType;
import org.hyperledger.besu.ethereum.eth.manager.EthPeers;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.ValidationResult;
import io.vertx.core.Vertx;
public class EngineNewPayloadV3 extends AbstractEngineNewPayload {
private final ProtocolSchedule timestampSchedule;
public EngineNewPayloadV3(
final Vertx vertx,
final ProtocolSchedule timestampSchedule,
final ProtocolContext protocolContext,
final MergeMiningCoordinator mergeCoordinator,
final EthPeers ethPeers,
final EngineCallListener engineCallListener) {
super(
vertx, timestampSchedule, protocolContext, mergeCoordinator, ethPeers, engineCallListener);
this.timestampSchedule = timestampSchedule;
}
@Override
public String getName() {
return RpcMethod.ENGINE_NEW_PAYLOAD_V3.getMethodName();
}
@Override
protected ValidationResult<RpcErrorType> validateForkSupported(
final Object reqId, final EnginePayloadParameter payloadParameter) {
var cancun = timestampSchedule.hardforkFor(s -> s.fork().name().equalsIgnoreCase("Cancun"));
if (cancun.isPresent() && payloadParameter.getTimestamp() >= cancun.get().milestone()) {
if (payloadParameter.getDataGasUsed() == null
|| payloadParameter.getExcessDataGas() == null) {
return ValidationResult.invalid(RpcErrorType.INVALID_PARAMS, "Missing data gas fields");
} else {
return ValidationResult.valid();
}
} else {
return ValidationResult.invalid(RpcErrorType.INVALID_PARAMS, "Fork not supported");
}
}
}

@ -47,6 +47,9 @@ public class EnginePayloadParameter {
private final LogsBloomFilter logsBloom;
private final List<String> transactions;
private final List<WithdrawalParameter> withdrawals;
private final Long dataGasUsed;
private final String excessDataGas;
private final List<Bytes32> versionedHashes;
private final List<DepositParameter> deposits;
@JsonCreator
@ -66,6 +69,9 @@ public class EnginePayloadParameter {
@JsonProperty("prevRandao") final String prevRandao,
@JsonProperty("transactions") final List<String> transactions,
@JsonProperty("withdrawals") final List<WithdrawalParameter> withdrawals,
@JsonProperty("dataGasUsed") final UnsignedLongParameter dataGasUsed,
@JsonProperty("excessDataGas") final String excessDataGas,
@JsonProperty("versionedHashes") final List<Bytes32> versionedHashes,
@JsonProperty("deposits") final List<DepositParameter> deposits) {
this.blockHash = blockHash;
this.parentHash = parentHash;
@ -82,6 +88,9 @@ public class EnginePayloadParameter {
this.prevRandao = Bytes32.fromHexString(prevRandao);
this.transactions = transactions;
this.withdrawals = withdrawals;
this.dataGasUsed = dataGasUsed == null ? null : dataGasUsed.getValue();
this.excessDataGas = excessDataGas;
this.versionedHashes = versionedHashes;
this.deposits = deposits;
}
@ -145,7 +154,19 @@ public class EnginePayloadParameter {
return withdrawals;
}
public Long getDataGasUsed() {
return dataGasUsed;
}
public String getExcessDataGas() {
return excessDataGas;
}
public List<DepositParameter> getDeposits() {
return deposits;
}
public List<Bytes32> getVersionedHashes() {
return versionedHashes;
}
}

@ -16,9 +16,11 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters;
import java.util.List;
import java.util.Optional;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
@ -84,4 +86,26 @@ public class JsonRpcParameter {
return Optional.of(param);
}
public <T> Optional<List<T>> optionalList(
final Object[] params, final int index, final Class<T> listClass) {
if (params == null || params.length <= index || params[index] == null) {
return Optional.empty();
}
Object rawParam = params[index];
if (List.class.isAssignableFrom(rawParam.getClass())) {
try {
String listJson = mapper.writeValueAsString(rawParam);
List<T> returnedList = mapper.readValue(listJson, new TypeReference<List<T>>() {});
return Optional.of(returnedList);
} catch (JsonProcessingException e) {
throw new InvalidJsonRpcParameters(
String.format(
"Invalid json rpc parameter at index %d. Supplied value was: '%s' of type: '%s' - expected type: '%s'",
index, rawParam, rawParam.getClass().getName(), listClass.getName()),
e);
}
}
return Optional.empty();
}
}

@ -24,6 +24,7 @@ public class UnsignedLongParameter {
@JsonCreator
public UnsignedLongParameter(final String value) {
checkArgument(value != null);
this.value = Long.decode(value);
checkArgument(this.value >= 0);
}

@ -14,6 +14,8 @@
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor;
import static org.hyperledger.besu.ethereum.mainnet.feemarket.ExcessDataGasCalculator.calculateExcessDataGasForParent;
import org.hyperledger.besu.datatypes.DataGas;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.Wei;
@ -52,10 +54,10 @@ public class BlockReplay {
final Wei dataGasPrice =
protocolSpec
.getFeeMarket()
.dataPrice(
.dataPricePerGas(
blockchain
.getBlockHeader(header.getParentHash())
.flatMap(BlockHeader::getExcessDataGas)
.map(parent -> calculateExcessDataGasForParent(protocolSpec, parent))
.orElse(DataGas.ZERO));
final List<TransactionTrace> transactionTraces =
@ -86,10 +88,10 @@ public class BlockReplay {
final Wei dataGasPrice =
protocolSpec
.getFeeMarket()
.dataPrice(
.dataPricePerGas(
blockchain
.getBlockHeader(header.getParentHash())
.flatMap(BlockHeader::getExcessDataGas)
.map(parent -> calculateExcessDataGasForParent(protocolSpec, parent))
.orElse(DataGas.ZERO));
for (final Transaction transaction : body.getTransactions()) {

@ -15,6 +15,7 @@
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor;
import static java.util.function.Predicate.isEqual;
import static org.hyperledger.besu.ethereum.mainnet.feemarket.ExcessDataGasCalculator.calculateExcessDataGasForParent;
import org.hyperledger.besu.datatypes.DataGas;
import org.hyperledger.besu.datatypes.Hash;
@ -118,10 +119,10 @@ public class TransactionTracer {
final Wei dataGasPrice =
protocolSpec
.getFeeMarket()
.dataPrice(
.dataPricePerGas(
blockchain
.getBlockHeader(header.getParentHash())
.flatMap(BlockHeader::getExcessDataGas)
.map(parent -> calculateExcessDataGasForParent(protocolSpec, parent))
.orElse(DataGas.ZERO));
for (int i = 0; i < body.getTransactions().size(); i++) {
((StackedUpdater<?, ?>) stackedUpdater).markTransactionBoundary();

@ -0,0 +1,97 @@
/*
* 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.results;
import org.hyperledger.besu.datatypes.Blob;
import org.hyperledger.besu.datatypes.BlobsWithCommitments;
import org.hyperledger.besu.datatypes.KZGCommitment;
import org.hyperledger.besu.datatypes.KZGProof;
import org.hyperledger.besu.ethereum.core.Transaction;
import java.security.InvalidParameterException;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import org.apache.tuweni.bytes.Bytes;
@JsonPropertyOrder({"commitments", "proofs", "blobs"})
public class BlobsBundleV1 {
private final List<String> commitments;
private final List<String> proofs;
private final List<String> blobs;
public BlobsBundleV1(final List<Transaction> transactions) {
final List<BlobsWithCommitments> blobsWithCommitments =
transactions.stream()
.map(Transaction::getBlobsWithCommitments)
.filter(Optional::isPresent)
.map(Optional::get)
.toList();
this.commitments =
blobsWithCommitments.stream()
.flatMap(b -> b.getKzgCommitments().stream())
.map(KZGCommitment::getData)
.map(Bytes::toString)
.collect(Collectors.toList());
this.proofs =
blobsWithCommitments.stream()
.flatMap(b -> b.getKzgProofs().stream())
.map(KZGProof::getData)
.map(Bytes::toString)
.collect(Collectors.toList());
this.blobs =
blobsWithCommitments.stream()
.flatMap(b -> b.getBlobs().stream())
.map(Blob::getData)
.map(Bytes::toString)
.collect(Collectors.toList());
}
public BlobsBundleV1(
final List<String> commitments, final List<String> proofs, final List<String> blobs) {
if (blobs.size() != commitments.size() || blobs.size() != proofs.size()) {
throw new InvalidParameterException(
"There must be an equal number of blobs, commitments and proofs");
}
this.commitments = commitments;
this.proofs = proofs;
this.blobs = blobs;
}
@JsonGetter("commitments")
public List<String> getCommitments() {
return commitments;
}
@JsonGetter("proofs")
public List<String> getProofs() {
return proofs;
}
@JsonGetter("blobs")
public List<String> getBlobs() {
return blobs;
}
}

@ -84,6 +84,9 @@ public class BlockResult implements JsonRpcResult {
private final String withdrawalsRoot;
private final List<WithdrawalParameter> withdrawals;
private final String dataGasUsed;
private final String excessDataGas;
public BlockResult(
final BlockHeader header,
final List<TransactionResult> transactions,
@ -128,6 +131,9 @@ public class BlockResult implements JsonRpcResult {
withdrawals
.map(w -> w.stream().map(WithdrawalParameter::fromWithdrawal).collect(toList()))
.orElse(null);
this.dataGasUsed = header.getDataGasUsed().map(Quantity::create).orElse(null);
this.excessDataGas = header.getExcessDataGas().map(Quantity::create).orElse(null);
}
@JsonGetter(value = "number")
@ -250,4 +256,14 @@ public class BlockResult implements JsonRpcResult {
public List<WithdrawalParameter> getWithdrawals() {
return withdrawals;
}
@JsonGetter(value = "dataGasUsed")
public String getDataGasUsed() {
return dataGasUsed;
}
@JsonGetter(value = "excessDataGas")
public String getExcessDataGas() {
return excessDataGas;
}
}

@ -128,6 +128,26 @@ public class BlockResultFactory {
return new EngineGetPayloadBodiesResultV1(payloadBodies);
}
public EngineGetPayloadResultV3 payloadTransactionCompleteV3(
final BlockWithReceipts blockWithReceipts) {
final List<String> txs =
blockWithReceipts.getBlock().getBody().getTransactions().stream()
.map(TransactionEncoder::encodeOpaqueBytes)
.map(Bytes::toHexString)
.collect(Collectors.toList());
final Wei blockValue = new BlockValueCalculator().calculateBlockValue(blockWithReceipts);
final BlobsBundleV1 blobsBundleV1 =
new BlobsBundleV1(blockWithReceipts.getBlock().getBody().getTransactions());
return new EngineGetPayloadResultV3(
blockWithReceipts.getHeader(),
txs,
blockWithReceipts.getBlock().getBody().getWithdrawals(),
Quantity.create(blockValue),
blobsBundleV1);
}
public BlockResult transactionHash(final BlockWithMetadata<Hash, Hash> blockWithMetadata) {
return transactionHash(blockWithMetadata, false);
}

@ -0,0 +1,202 @@
/*
* 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.results;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.WithdrawalParameter;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.Withdrawal;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import org.apache.tuweni.bytes.Bytes32;
@JsonPropertyOrder({"executionPayload", "blockValue", "blobsBundle"})
public class EngineGetPayloadResultV3 {
protected final PayloadResult executionPayload;
private final String blockValue;
private final BlobsBundleV1 blobsBundle;
public EngineGetPayloadResultV3(
final BlockHeader header,
final List<String> transactions,
final Optional<List<Withdrawal>> withdrawals,
final String blockValue,
final BlobsBundleV1 blobsBundle) {
this.executionPayload = new PayloadResult(header, transactions, withdrawals);
this.blockValue = blockValue;
this.blobsBundle = blobsBundle;
}
@JsonGetter(value = "executionPayload")
public PayloadResult getExecutionPayload() {
return executionPayload;
}
@JsonGetter(value = "blockValue")
public String getBlockValue() {
return blockValue;
}
@JsonGetter(value = "blobsBundle")
public BlobsBundleV1 getBlobsBundle() {
return blobsBundle;
}
public static class PayloadResult {
protected final String blockHash;
private final String parentHash;
private final String feeRecipient;
private final String stateRoot;
private final String receiptsRoot;
private final String logsBloom;
private final String prevRandao;
private final String blockNumber;
private final String gasLimit;
private final String gasUsed;
private final String timestamp;
private final String extraData;
private final String baseFeePerGas;
private final String excessDataGas;
private final String dataGasUsed;
protected final List<String> transactions;
private final List<WithdrawalParameter> withdrawals;
public PayloadResult(
final BlockHeader header,
final List<String> transactions,
final Optional<List<Withdrawal>> withdrawals) {
this.blockNumber = Quantity.create(header.getNumber());
this.blockHash = header.getHash().toString();
this.parentHash = header.getParentHash().toString();
this.logsBloom = header.getLogsBloom().toString();
this.stateRoot = header.getStateRoot().toString();
this.receiptsRoot = header.getReceiptsRoot().toString();
this.extraData = header.getExtraData().toString();
this.baseFeePerGas = header.getBaseFee().map(Quantity::create).orElse(null);
this.gasLimit = Quantity.create(header.getGasLimit());
this.gasUsed = Quantity.create(header.getGasUsed());
this.timestamp = Quantity.create(header.getTimestamp());
this.transactions = transactions;
this.feeRecipient = header.getCoinbase().toString();
this.prevRandao = header.getPrevRandao().map(Bytes32::toHexString).orElse(null);
this.withdrawals =
withdrawals
.map(
ws ->
ws.stream()
.map(WithdrawalParameter::fromWithdrawal)
.collect(Collectors.toList()))
.orElse(null);
this.dataGasUsed = header.getDataGasUsed().map(Quantity::create).orElse(Quantity.HEX_ZERO);
this.excessDataGas =
header.getExcessDataGas().map(Quantity::create).orElse(Quantity.HEX_ZERO);
}
@JsonGetter(value = "blockNumber")
public String getNumber() {
return blockNumber;
}
@JsonGetter(value = "blockHash")
public String getHash() {
return blockHash;
}
@JsonGetter(value = "parentHash")
public String getParentHash() {
return parentHash;
}
@JsonGetter(value = "logsBloom")
public String getLogsBloom() {
return logsBloom;
}
@JsonGetter(value = "prevRandao")
public String getPrevRandao() {
return prevRandao;
}
@JsonGetter(value = "stateRoot")
public String getStateRoot() {
return stateRoot;
}
@JsonGetter(value = "receiptsRoot")
public String getReceiptRoot() {
return receiptsRoot;
}
@JsonGetter(value = "extraData")
public String getExtraData() {
return extraData;
}
@JsonGetter(value = "baseFeePerGas")
public String getBaseFeePerGas() {
return baseFeePerGas;
}
@JsonGetter(value = "gasLimit")
public String getGasLimit() {
return gasLimit;
}
@JsonGetter(value = "gasUsed")
public String getGasUsed() {
return gasUsed;
}
@JsonGetter(value = "timestamp")
public String getTimestamp() {
return timestamp;
}
@JsonGetter(value = "transactions")
public List<String> getTransactions() {
return transactions;
}
@JsonGetter(value = "withdrawals")
public List<WithdrawalParameter> getWithdrawals() {
return withdrawals;
}
@JsonGetter(value = "feeRecipient")
@JsonInclude(JsonInclude.Include.NON_NULL)
public String getFeeRecipient() {
return feeRecipient;
}
@JsonGetter(value = "excessDataGas")
public String getExcessDataGas() {
return excessDataGas;
}
@JsonGetter(value = "dataGasUsed")
public String getDataGasUseds() {
return dataGasUsed;
}
}
}

@ -21,6 +21,7 @@ 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.UInt256Value;
import org.apache.tuweni.units.bigints.UInt64Value;
/**
* Utility for formatting "quantity" fields and results to be returned. Quantity fields are
@ -30,7 +31,7 @@ import org.apache.tuweni.units.bigints.UInt256Value;
public class Quantity {
private static final String HEX_PREFIX = "0x";
private static final String HEX_ZERO = "0x0";
public static final String HEX_ZERO = "0x0";
private Quantity() {}
@ -38,6 +39,10 @@ public class Quantity {
return uint256ToHex(value);
}
public static String create(final UInt64Value<?> value) {
return (value == null || value.isZero()) ? HEX_ZERO : value.toMinimalBytes().toShortHexString();
}
public static String create(final int value) {
return uint256ToHex(UInt256.valueOf(value));
}

@ -15,6 +15,7 @@
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.results;
import org.hyperledger.besu.datatypes.TransactionType;
import org.hyperledger.besu.datatypes.VersionedHash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.api.query.TransactionWithMetadata;
import org.hyperledger.besu.ethereum.core.Transaction;
@ -37,6 +38,7 @@ import org.apache.tuweni.bytes.Bytes;
"gasPrice",
"maxPriorityFeePerGas",
"maxFeePerGas",
"maxFeePerDataGas",
"hash",
"input",
"nonce",
@ -46,7 +48,8 @@ import org.apache.tuweni.bytes.Bytes;
"value",
"v",
"r",
"s"
"s",
"blobVersionedHashes"
})
public class TransactionCompleteResult implements TransactionResult {
@ -69,6 +72,9 @@ public class TransactionCompleteResult implements TransactionResult {
@JsonInclude(JsonInclude.Include.NON_NULL)
private final String maxFeePerGas;
@JsonInclude(JsonInclude.Include.NON_NULL)
private final String maxFeePerDataGas;
private final String hash;
private final String input;
private final String nonce;
@ -80,6 +86,9 @@ public class TransactionCompleteResult implements TransactionResult {
private final String r;
private final String s;
@JsonInclude(JsonInclude.Include.NON_NULL)
private final List<VersionedHash> versionedHashes;
public TransactionCompleteResult(final TransactionWithMetadata tx) {
final Transaction transaction = tx.getTransaction();
final TransactionType transactionType = transaction.getType();
@ -93,6 +102,8 @@ public class TransactionCompleteResult implements TransactionResult {
tx.getTransaction().getMaxPriorityFeePerGas().map(Wei::toShortHexString).orElse(null);
this.maxFeePerGas =
tx.getTransaction().getMaxFeePerGas().map(Wei::toShortHexString).orElse(null);
this.maxFeePerDataGas =
transaction.getMaxFeePerDataGas().map(Wei::toShortHexString).orElse(null);
this.gasPrice =
Quantity.create(
transaction
@ -111,6 +122,7 @@ public class TransactionCompleteResult implements TransactionResult {
this.v = Quantity.create(transaction.getV());
this.r = Quantity.create(transaction.getR());
this.s = Quantity.create(transaction.getS());
this.versionedHashes = transaction.getVersionedHashes().orElse(null);
}
@JsonGetter(value = "accessList")
@ -153,6 +165,11 @@ public class TransactionCompleteResult implements TransactionResult {
return maxFeePerGas;
}
@JsonGetter(value = "maxFeePerDataGas")
public String getMaxFeePerDataGas() {
return maxFeePerDataGas;
}
@JsonGetter(value = "gasPrice")
public String getGasPrice() {
return gasPrice;
@ -207,4 +224,9 @@ public class TransactionCompleteResult implements TransactionResult {
public String getS() {
return s;
}
@JsonGetter(value = "blobVersionedHashes")
public List<VersionedHash> getVersionedHashes() {
return versionedHashes;
}
}

@ -15,11 +15,11 @@
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.results;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.TransactionType;
import org.hyperledger.besu.datatypes.VersionedHash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput;
import org.hyperledger.besu.ethereum.core.encoding.TransactionEncoder;
import org.hyperledger.besu.evm.AccessListEntry;
import java.util.List;
@ -81,7 +81,7 @@ public class TransactionPendingResult implements TransactionResult {
private final String s;
@JsonInclude(JsonInclude.Include.NON_NULL)
private final List<Hash> versionedHashes;
private final List<VersionedHash> versionedHashes;
public TransactionPendingResult(final Transaction transaction) {
final TransactionType transactionType = transaction.getType();
@ -99,9 +99,7 @@ public class TransactionPendingResult implements TransactionResult {
this.input = transaction.getPayload().toString();
this.nonce = Quantity.create(transaction.getNonce());
this.publicKey = transaction.getPublicKey().orElse(null);
final BytesValueRLPOutput out = new BytesValueRLPOutput();
transaction.writeTo(out);
this.raw = out.encoded().toString();
this.raw = TransactionEncoder.encodeOpaqueBytes(transaction).toString();
this.to = transaction.getTo().map(Address::toHexString).orElse(null);
this.type =
transactionType.equals(TransactionType.FRONTIER)
@ -225,7 +223,7 @@ public class TransactionPendingResult implements TransactionResult {
}
@JsonGetter(value = "blobVersionedHashes")
public List<Hash> getVersionedHashes() {
public List<VersionedHash> getVersionedHashes() {
return versionedHashes;
}
}

@ -46,7 +46,9 @@ import org.apache.tuweni.bytes.Bytes;
"transactionHash",
"transactionIndex",
"revertReason",
"type"
"type",
"dataGasUsed",
"dataGasPrice"
})
public abstract class TransactionReceiptResult {
@ -67,6 +69,9 @@ public abstract class TransactionReceiptResult {
protected final TransactionReceipt receipt;
protected final String type;
private final String dataGasUsed;
private final String dataGasPrice;
protected TransactionReceiptResult(final TransactionReceiptWithMetadata receiptWithMetadata) {
final Transaction txn = receiptWithMetadata.getTransaction();
this.receipt = receiptWithMetadata.getReceipt();
@ -76,6 +81,8 @@ public abstract class TransactionReceiptResult {
this.cumulativeGasUsed = Quantity.create(receipt.getCumulativeGasUsed());
this.from = txn.getSender().toString();
this.gasUsed = Quantity.create(receiptWithMetadata.getGasUsed());
this.dataGasUsed = receiptWithMetadata.getDataGasUsed().map(Quantity::create).orElse(null);
this.dataGasPrice = receiptWithMetadata.getDataGasPrice().map(Quantity::create).orElse(null);
this.effectiveGasPrice =
Quantity.create(txn.getEffectiveGasPrice(receiptWithMetadata.getBaseFee()));
@ -127,6 +134,18 @@ public abstract class TransactionReceiptResult {
return gasUsed;
}
@JsonGetter(value = "dataGasUsed")
@JsonInclude(JsonInclude.Include.NON_NULL)
public String getDataGasUsed() {
return dataGasUsed;
}
@JsonGetter(value = "dataGasPrice")
@JsonInclude(JsonInclude.Include.NON_NULL)
public String getDataGasPrice() {
return dataGasPrice;
}
@JsonGetter(value = "effectiveGasPrice")
public String getEffectiveGasPrice() {
return effectiveGasPrice;

@ -19,6 +19,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@ -37,4 +38,8 @@ public abstract class ApiGroupJsonRpcMethods implements JsonRpcMethods {
return Arrays.stream(methods)
.collect(Collectors.toMap(JsonRpcMethod::getName, method -> method));
}
protected Map<String, JsonRpcMethod> mapOf(final List<JsonRpcMethod> methods) {
return methods.stream().collect(Collectors.toMap(JsonRpcMethod::getName, method -> method));
}
}

@ -142,7 +142,7 @@ public class EthJsonRpcMethods extends ApiGroupJsonRpcMethods {
new EthGetTransactionByBlockHashAndIndex(blockchainQueries),
new EthGetTransactionByBlockNumberAndIndex(blockchainQueries),
new EthGetTransactionCount(blockchainQueries, transactionPool),
new EthGetTransactionReceipt(blockchainQueries),
new EthGetTransactionReceipt(blockchainQueries, protocolSchedule),
new EthUninstallFilter(filterManager),
new EthGetFilterChanges(filterManager),
new EthGetFilterLogs(filterManager),

@ -26,8 +26,10 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineG
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.EngineGetPayloadV2;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineGetPayloadV3;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineNewPayloadV1;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineNewPayloadV2;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineNewPayloadV3;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EnginePreparePayloadDebug;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineQosTimer;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlockResultFactory;
@ -35,6 +37,9 @@ import org.hyperledger.besu.ethereum.blockcreation.MiningCoordinator;
import org.hyperledger.besu.ethereum.eth.manager.EthPeers;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@ -74,56 +79,78 @@ public class ExecutionEngineJsonRpcMethods extends ApiGroupJsonRpcMethods {
@Override
protected Map<String, JsonRpcMethod> create() {
final EngineQosTimer engineQosTimer = new EngineQosTimer(consensusEngineServer);
if (mergeCoordinator.isPresent()) {
return mapOf(
new EngineGetPayloadV1(
consensusEngineServer,
protocolContext,
mergeCoordinator.get(),
blockResultFactory,
engineQosTimer),
new EngineGetPayloadV2(
consensusEngineServer,
protocolContext,
mergeCoordinator.get(),
blockResultFactory,
engineQosTimer),
new EngineNewPayloadV1(
consensusEngineServer,
protocolSchedule,
protocolContext,
mergeCoordinator.get(),
ethPeers,
engineQosTimer),
new EngineNewPayloadV2(
consensusEngineServer,
protocolSchedule,
protocolContext,
mergeCoordinator.get(),
ethPeers,
engineQosTimer),
new EngineForkchoiceUpdatedV1(
consensusEngineServer,
protocolSchedule,
protocolContext,
mergeCoordinator.get(),
engineQosTimer),
new EngineForkchoiceUpdatedV2(
consensusEngineServer,
protocolSchedule,
protocolContext,
mergeCoordinator.get(),
engineQosTimer),
new EngineExchangeTransitionConfiguration(
consensusEngineServer, protocolContext, engineQosTimer),
new EngineGetPayloadBodiesByHashV1(
consensusEngineServer, protocolContext, blockResultFactory, engineQosTimer),
new EngineGetPayloadBodiesByRangeV1(
consensusEngineServer, protocolContext, blockResultFactory, engineQosTimer),
new EngineExchangeCapabilities(consensusEngineServer, protocolContext, engineQosTimer),
new EnginePreparePayloadDebug(
consensusEngineServer, protocolContext, engineQosTimer, mergeCoordinator.get()));
List<JsonRpcMethod> executionEngineApisSupported = new ArrayList<>();
executionEngineApisSupported.addAll(
Arrays.asList(
new EngineGetPayloadV1(
consensusEngineServer,
protocolContext,
mergeCoordinator.get(),
blockResultFactory,
engineQosTimer),
new EngineGetPayloadV2(
consensusEngineServer,
protocolContext,
mergeCoordinator.get(),
blockResultFactory,
engineQosTimer),
new EngineNewPayloadV1(
consensusEngineServer,
protocolSchedule,
protocolContext,
mergeCoordinator.get(),
ethPeers,
engineQosTimer),
new EngineNewPayloadV2(
consensusEngineServer,
protocolSchedule,
protocolContext,
mergeCoordinator.get(),
ethPeers,
engineQosTimer),
new EngineNewPayloadV3(
consensusEngineServer,
protocolSchedule,
protocolContext,
mergeCoordinator.get(),
ethPeers,
engineQosTimer),
new EngineForkchoiceUpdatedV1(
consensusEngineServer,
protocolSchedule,
protocolContext,
mergeCoordinator.get(),
engineQosTimer),
new EngineForkchoiceUpdatedV2(
consensusEngineServer,
protocolSchedule,
protocolContext,
mergeCoordinator.get(),
engineQosTimer),
new EngineExchangeTransitionConfiguration(
consensusEngineServer, protocolContext, engineQosTimer),
new EngineGetPayloadBodiesByHashV1(
consensusEngineServer, protocolContext, blockResultFactory, engineQosTimer),
new EngineGetPayloadBodiesByRangeV1(
consensusEngineServer, protocolContext, blockResultFactory, engineQosTimer),
new EngineExchangeCapabilities(
consensusEngineServer, protocolContext, engineQosTimer),
new EnginePreparePayloadDebug(
consensusEngineServer, protocolContext, engineQosTimer, mergeCoordinator.get())));
if (protocolSchedule.anyMatch(p -> p.spec().getName().equalsIgnoreCase("cancun"))) {
executionEngineApisSupported.add(
new EngineGetPayloadV3(
consensusEngineServer,
protocolContext,
mergeCoordinator.get(),
blockResultFactory,
engineQosTimer,
protocolSchedule));
}
return mapOf(executionEngineApisSupported);
} else {
return mapOf(
new EngineExchangeTransitionConfiguration(

@ -16,6 +16,7 @@ package org.hyperledger.besu.ethereum.api.query;
import static com.google.common.base.Preconditions.checkArgument;
import static org.hyperledger.besu.ethereum.api.query.cache.TransactionLogBloomCacher.BLOCKS_PER_BLOOM_CACHE;
import static org.hyperledger.besu.ethereum.mainnet.feemarket.ExcessDataGasCalculator.calculateExcessDataGasForParent;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
@ -33,6 +34,8 @@ import org.hyperledger.besu.ethereum.core.MutableWorldState;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.core.TransactionReceipt;
import org.hyperledger.besu.ethereum.eth.manager.EthScheduler;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec;
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
import org.hyperledger.besu.evm.account.Account;
import org.hyperledger.besu.evm.log.LogsBloomFilter;
@ -612,7 +615,7 @@ public class BlockchainQueries {
* @return The transaction receipt associated with the referenced transaction.
*/
public Optional<TransactionReceiptWithMetadata> transactionReceiptByTransactionHash(
final Hash transactionHash) {
final Hash transactionHash, final ProtocolSchedule protocolSchedule) {
final Optional<TransactionLocation> maybeLocation =
blockchain.getTransactionLocation(transactionHash);
if (maybeLocation.isEmpty()) {
@ -639,6 +642,12 @@ public class BlockchainQueries {
- transactionReceipts.get(location.getTransactionIndex() - 1).getCumulativeGasUsed();
}
Optional<Long> maybeDataGasUsed =
getDataGasUsed(transaction, protocolSchedule.getByBlockHeader(header));
Optional<Wei> maybeDataGasPrice =
getDataGasPrice(transaction, header, protocolSchedule.getByBlockHeader(header));
return Optional.of(
TransactionReceiptWithMetadata.create(
transactionReceipt,
@ -648,7 +657,48 @@ public class BlockchainQueries {
gasUsed,
header.getBaseFee(),
blockhash,
header.getNumber()));
header.getNumber(),
maybeDataGasUsed,
maybeDataGasPrice));
}
/**
* Calculates the data gas used for data in a transaction.
*
* @param transaction the transaction to calculate the gas for
* @param protocolSpec the protocol specification to use for gas calculation
* @return an Optional containing the data gas used for data if the transaction type supports
* blobs, otherwise returns an empty Optional
*/
private Optional<Long> getDataGasUsed(
final Transaction transaction, final ProtocolSpec protocolSpec) {
return transaction.getType().supportsBlob()
? Optional.of(protocolSpec.getGasCalculator().dataGasCost(transaction.getBlobCount()))
: Optional.empty();
}
/**
* Calculates the data gas price for data in a transaction.
*
* @param transaction the transaction to calculate the gas price for
* @param header the block header of the current block
* @param protocolSpec the protocol specification to use for gas price calculation
* @return an Optional containing the data gas price for data if the transaction type supports
* blobs, otherwise returns an empty Optional
*/
private Optional<Wei> getDataGasPrice(
final Transaction transaction, final BlockHeader header, final ProtocolSpec protocolSpec) {
if (transaction.getType().supportsBlob()) {
return blockchain
.getBlockHeader(header.getParentHash())
.map(
parentHeader ->
protocolSpec
.getFeeMarket()
.dataPricePerGas(
calculateExcessDataGasForParent(protocolSpec, parentHeader)));
}
return Optional.empty();
}
/**

@ -30,6 +30,8 @@ public class TransactionReceiptWithMetadata {
private final long blockNumber;
private final Hash blockHash;
private final Transaction transaction;
private final Optional<Long> dataGasUsed;
private final Optional<Wei> dataGasPrice;
private TransactionReceiptWithMetadata(
final TransactionReceipt receipt,
@ -39,7 +41,9 @@ public class TransactionReceiptWithMetadata {
final long gasUsed,
final Optional<Wei> baseFee,
final Hash blockHash,
final long blockNumber) {
final long blockNumber,
final Optional<Long> dataGasUsed,
final Optional<Wei> dataGasPrice) {
this.receipt = receipt;
this.transactionHash = transactionHash;
this.transactionIndex = transactionIndex;
@ -48,6 +52,8 @@ public class TransactionReceiptWithMetadata {
this.blockHash = blockHash;
this.blockNumber = blockNumber;
this.transaction = transaction;
this.dataGasUsed = dataGasUsed;
this.dataGasPrice = dataGasPrice;
}
public static TransactionReceiptWithMetadata create(
@ -58,7 +64,9 @@ public class TransactionReceiptWithMetadata {
final long gasUsed,
final Optional<Wei> baseFee,
final Hash blockHash,
final long blockNumber) {
final long blockNumber,
final Optional<Long> dataGasUsed,
final Optional<Wei> dataGasPrice) {
return new TransactionReceiptWithMetadata(
receipt,
transaction,
@ -67,7 +75,9 @@ public class TransactionReceiptWithMetadata {
gasUsed,
baseFee,
blockHash,
blockNumber);
blockNumber,
dataGasUsed,
dataGasPrice);
}
public TransactionReceipt getReceipt() {
@ -103,4 +113,12 @@ public class TransactionReceiptWithMetadata {
public Optional<Wei> getBaseFee() {
return baseFee;
}
public Optional<Long> getDataGasUsed() {
return dataGasUsed;
}
public Optional<Wei> getDataGasPrice() {
return dataGasPrice;
}
}

@ -164,6 +164,7 @@ public class EthGasPriceTest {
null,
null,
null,
null,
null),
new BlockBody(
List.of(
@ -204,6 +205,7 @@ public class EthGasPriceTest {
null,
null,
null,
null,
null),
new BlockBody(List.of(), List.of())));
}

@ -15,12 +15,15 @@
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.crypto.SECPSignature;
import org.hyperledger.besu.crypto.SignatureAlgorithmFactory;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.DataGas;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.TransactionType;
import org.hyperledger.besu.datatypes.Wei;
@ -32,6 +35,10 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.TransactionRec
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.TransactionReceiptStatusResult;
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.api.query.TransactionReceiptWithMetadata;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.chain.TransactionLocation;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.BlockBody;
import org.hyperledger.besu.ethereum.core.BlockDataGenerator;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture;
@ -40,13 +47,18 @@ import org.hyperledger.besu.ethereum.core.TransactionReceipt;
import org.hyperledger.besu.ethereum.mainnet.PoWHasher;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec;
import org.hyperledger.besu.ethereum.mainnet.feemarket.CancunFeeMarket;
import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket;
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
import org.hyperledger.besu.evm.gascalculator.CancunGasCalculator;
import java.math.BigInteger;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.apache.tuweni.units.bigints.UInt256s;
import org.junit.jupiter.api.Test;
@ -82,10 +94,28 @@ public class EthGetTransactionReceiptTest {
private final TransactionReceiptWithMetadata statusReceiptWithMetadata =
TransactionReceiptWithMetadata.create(
statusReceipt, transaction, hash, 1, 2, Optional.empty(), blockHash, 4);
statusReceipt,
transaction,
hash,
1,
2,
Optional.empty(),
blockHash,
4,
Optional.empty(),
Optional.empty());
private final TransactionReceiptWithMetadata rootReceiptWithMetaData =
TransactionReceiptWithMetadata.create(
rootReceipt, transaction, hash, 1, 2, Optional.empty(), blockHash, 4);
rootReceipt,
transaction,
hash,
1,
2,
Optional.empty(),
blockHash,
4,
Optional.empty(),
Optional.empty());
private final ProtocolSpec rootTransactionTypeSpec =
new ProtocolSpec(
@ -151,9 +181,12 @@ public class EthGetTransactionReceiptTest {
@SuppressWarnings("unchecked")
private final ProtocolSchedule protocolSchedule = mock(ProtocolSchedule.class);
private final BlockchainQueries blockchain = mock(BlockchainQueries.class);
private final Blockchain blockchain = mock(Blockchain.class);
private final BlockchainQueries blockchainQueries =
spy(new BlockchainQueries(blockchain, mock(WorldStateArchive.class)));
private final EthGetTransactionReceipt ethGetTransactionReceipt =
new EthGetTransactionReceipt(blockchain);
new EthGetTransactionReceipt(blockchainQueries, protocolSchedule);
private final String receiptString =
"0xcbef69eaf44af151aa66677ae4b8d8c343a09f667c873a3a6f4558fa4051fa5f";
private final Hash receiptHash =
@ -164,8 +197,8 @@ public class EthGetTransactionReceiptTest {
@Test
public void shouldCreateAStatusTransactionReceiptWhenStatusTypeProtocol() {
when(blockchain.headBlockNumber()).thenReturn(1L);
when(blockchain.transactionReceiptByTransactionHash(receiptHash))
when(blockchainQueries.headBlockNumber()).thenReturn(1L);
when(blockchainQueries.transactionReceiptByTransactionHash(receiptHash, protocolSchedule))
.thenReturn(Optional.of(statusReceiptWithMetadata));
when(protocolSchedule.getByBlockHeader(blockHeader(1))).thenReturn(statusTransactionTypeSpec);
@ -180,8 +213,8 @@ public class EthGetTransactionReceiptTest {
@Test
public void shouldCreateARootTransactionReceiptWhenRootTypeProtocol() {
when(blockchain.headBlockNumber()).thenReturn(1L);
when(blockchain.transactionReceiptByTransactionHash(receiptHash))
when(blockchainQueries.headBlockNumber()).thenReturn(1L);
when(blockchainQueries.transactionReceiptByTransactionHash(receiptHash, protocolSchedule))
.thenReturn(Optional.of(rootReceiptWithMetaData));
when(protocolSchedule.getByBlockHeader(blockHeader(1))).thenReturn(rootTransactionTypeSpec);
@ -195,14 +228,23 @@ public class EthGetTransactionReceiptTest {
@Test
public void shouldWorkFor1559Txs() {
when(blockchain.headBlockNumber()).thenReturn(1L);
when(blockchainQueries.headBlockNumber()).thenReturn(1L);
final Transaction transaction1559 =
new BlockDataGenerator().transaction(TransactionType.EIP1559);
final Wei baseFee = Wei.ONE;
final TransactionReceiptWithMetadata transactionReceiptWithMetadata =
TransactionReceiptWithMetadata.create(
statusReceipt, transaction1559, hash, 1, 2, Optional.of(baseFee), blockHash, 4);
when(blockchain.transactionReceiptByTransactionHash(receiptHash))
statusReceipt,
transaction1559,
hash,
1,
2,
Optional.of(baseFee),
blockHash,
4,
Optional.empty(),
Optional.empty());
when(blockchainQueries.transactionReceiptByTransactionHash(receiptHash, protocolSchedule))
.thenReturn(Optional.of(transactionReceiptWithMetadata));
when(protocolSchedule.getByBlockHeader(blockHeader(1))).thenReturn(rootTransactionTypeSpec);
@ -220,6 +262,63 @@ public class EthGetTransactionReceiptTest {
transaction1559.getMaxFeePerGas().get()));
}
/**
* Test case to verify that the TransactionReceiptStatusResult contains data gas used and data gas
* price when the transaction type is TransactionType#BLOB
*/
@Test
public void shouldContainDataGasUsedAndDataGasPriceWhenBlobTransaction() {
var hash = Hash.wrap(Bytes32.random());
mockBlockWithBlobTransaction(hash, 1L);
when(blockchain.getTxReceipts(hash)).thenReturn(Optional.of(List.of(statusReceipt)));
// Call the real method to get the transaction receipt by transaction hash
when(blockchainQueries.transactionReceiptByTransactionHash(receiptHash, protocolSchedule))
.thenCallRealMethod();
final JsonRpcSuccessResponse response =
(JsonRpcSuccessResponse) ethGetTransactionReceipt.response(request);
final TransactionReceiptStatusResult result =
(TransactionReceiptStatusResult) response.getResult();
assertThat(result.getType()).isEqualTo("0x3");
assertThat(result.getDataGasUsed()).isEqualTo("0x20000");
assertThat(result.getDataGasPrice()).isEqualTo("0x1");
}
private void mockBlockWithBlobTransaction(final Hash blockHash, final long blockNumber) {
Hash parentHash = Hash.wrap(Bytes32.random());
TransactionLocation transactionLocation = mock(TransactionLocation.class);
Block block = mock(Block.class);
BlockBody body = mock(BlockBody.class);
BlockHeader header = mock(BlockHeader.class);
BlockHeader parentHeader = mock(BlockHeader.class);
when(transactionLocation.getBlockHash()).thenReturn(blockHash);
when(transactionLocation.getTransactionIndex()).thenReturn(0);
when(header.getNumber()).thenReturn(blockNumber);
when(header.getBlockHash()).thenReturn(blockHash);
when(header.getParentHash()).thenReturn(parentHash);
when(blockchain.getBlockHeader(parentHash)).thenReturn(Optional.of(parentHeader));
when(block.getHeader()).thenReturn(header);
when(block.getBody()).thenReturn(body);
when(body.getTransactions())
.thenReturn(List.of(new BlockDataGenerator().transaction(TransactionType.BLOB)));
when(parentHeader.getExcessDataGas()).thenReturn(Optional.of(DataGas.of(1000)));
when(blockchain.getBlockByHash(blockHash)).thenReturn(Optional.of(block));
mockProtocolSpec(header);
when(blockchain.getTransactionLocation(receiptHash))
.thenReturn(Optional.of(transactionLocation));
}
private void mockProtocolSpec(final BlockHeader blockHeader) {
FeeMarket feeMarket = mock(CancunFeeMarket.class);
when(feeMarket.dataPricePerGas(any())).thenCallRealMethod();
ProtocolSpec spec = mock(ProtocolSpec.class);
when(spec.getFeeMarket()).thenReturn(feeMarket);
when(spec.getGasCalculator()).thenReturn(new CancunGasCalculator());
when(protocolSchedule.getByBlockHeader(blockHeader)).thenReturn(spec);
}
private BlockHeader blockHeader(final long number) {
return new BlockHeaderTestFixture().number(number).buildHeader();
}

@ -15,6 +15,7 @@
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ -22,6 +23,9 @@ import static org.mockito.Mockito.when;
import org.hyperledger.besu.consensus.merge.MergeContext;
import org.hyperledger.besu.consensus.merge.blockcreation.MergeMiningCoordinator;
import org.hyperledger.besu.consensus.merge.blockcreation.PayloadIdentifier;
import org.hyperledger.besu.crypto.KeyPair;
import org.hyperledger.besu.crypto.SignatureAlgorithm;
import org.hyperledger.besu.crypto.SignatureAlgorithmFactory;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.ProtocolContext;
@ -35,10 +39,14 @@ import org.hyperledger.besu.ethereum.core.BlockBody;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture;
import org.hyperledger.besu.ethereum.core.BlockWithReceipts;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.ScheduledProtocolSpec;
import java.util.Collections;
import java.util.Optional;
import java.util.function.Supplier;
import com.google.common.base.Suppliers;
import io.vertx.core.Vertx;
import org.apache.tuweni.bytes.Bytes32;
import org.junit.jupiter.api.BeforeEach;
@ -51,6 +59,10 @@ import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
public abstract class AbstractEngineGetPayloadTest {
private static final Supplier<SignatureAlgorithm> SIGNATURE_ALGORITHM =
Suppliers.memoize(SignatureAlgorithmFactory::getInstance);
protected static final KeyPair senderKeys = SIGNATURE_ALGORITHM.get().generateKeyPair();
@FunctionalInterface
interface MethodFactory {
AbstractEngineGetPayload create(
@ -68,7 +80,11 @@ public abstract class AbstractEngineGetPayloadTest {
this.methodFactory = methodFactory;
}
private static final Vertx vertx = Vertx.vertx();
public AbstractEngineGetPayloadTest() {
this.methodFactory = null;
}
protected static final Vertx vertx = Vertx.vertx();
protected static final BlockResultFactory factory = new BlockResultFactory();
protected static final PayloadIdentifier mockPid =
PayloadIdentifier.forPayloadParams(
@ -77,7 +93,7 @@ public abstract class AbstractEngineGetPayloadTest {
new BlockHeaderTestFixture().prevRandao(Bytes32.random()).buildHeader();
private static final Block mockBlock =
new Block(mockHeader, new BlockBody(Collections.emptyList(), Collections.emptyList()));
private static final BlockWithReceipts mockBlockWithReceipts =
protected static final BlockWithReceipts mockBlockWithReceipts =
new BlockWithReceipts(mockBlock, Collections.emptyList());
private static final Block mockBlockWithWithdrawals =
new Block(
@ -101,17 +117,23 @@ public abstract class AbstractEngineGetPayloadTest {
protected static final BlockWithReceipts mockBlockWithReceiptsAndDeposits =
new BlockWithReceipts(mockBlockWithDeposits, Collections.emptyList());
@Mock private ProtocolContext protocolContext;
@Mock protected ProtocolContext protocolContext;
@Mock protected MergeContext mergeContext;
@Mock private MergeMiningCoordinator mergeMiningCoordinator;
@Mock protected MergeMiningCoordinator mergeMiningCoordinator;
@Mock protected EngineCallListener engineCallListener;
@Mock protected ProtocolSchedule protocolSchedule;
protected static final long SHANGHAI_AT = 1337L;
@BeforeEach
public void before() {
when(mergeContext.retrieveBlockById(mockPid)).thenReturn(Optional.of(mockBlockWithReceipts));
when(protocolContext.safeConsensusContext(Mockito.any())).thenReturn(Optional.of(mergeContext));
when(protocolSchedule.hardforkFor(any()))
.thenReturn(Optional.of(new ScheduledProtocolSpec.Hardfork("shanghai", SHANGHAI_AT)));
this.method =
methodFactory.create(
vertx, protocolContext, mergeMiningCoordinator, factory, engineCallListener);

@ -19,10 +19,8 @@ import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.Executi
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod.EngineStatus.INVALID;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod.EngineStatus.SYNCING;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod.EngineStatus.VALID;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.DepositParameterTestFixture.DEPOSIT_PARAM_1;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.WithdrawalParameterTestFixture.WITHDRAWAL_PARAM_1;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType.INVALID_PARAMS;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@ -32,13 +30,13 @@ import static org.mockito.Mockito.when;
import org.hyperledger.besu.consensus.merge.MergeContext;
import org.hyperledger.besu.consensus.merge.blockcreation.MergeMiningCoordinator;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.DataGas;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.VersionedHash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.BlockProcessingOutputs;
import org.hyperledger.besu.ethereum.BlockProcessingResult;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod;
@ -75,9 +73,7 @@ import java.util.concurrent.CompletableFuture;
import io.vertx.core.Vertx;
import org.apache.tuweni.bytes.Bytes32;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
@ -87,6 +83,12 @@ import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
public abstract class AbstractEngineNewPayloadTest {
protected final long LONDON_TIMESTAMP = 0;
protected final long PARIS_TIMESTAMP = 10;
protected final long SHANGHAI_TIMESTAMP = 20;
protected final long CANCUN_TIMESTAMP = 30;
protected final long EXPERIMENTAL_TIMESTAMP = 40;
@FunctionalInterface
interface MethodFactory {
AbstractEngineNewPayload create(
@ -98,29 +100,25 @@ public abstract class AbstractEngineNewPayloadTest {
final EngineCallListener engineCallListener);
}
private final MethodFactory methodFactory;
// private final MethodFactory methodFactory;
protected AbstractEngineNewPayload method;
public AbstractEngineNewPayloadTest(final MethodFactory methodFactory) {
this.methodFactory = methodFactory;
}
public AbstractEngineNewPayloadTest() {}
private static final Vertx vertx = Vertx.vertx();
private static final Hash mockHash = Hash.hash(Bytes32.fromHexStringLenient("0x1337deadbeef"));
private static final Address depositContractAddress =
Address.fromHexString("0x00000000219ab540356cbb839cbe05303d7705fa");
protected static final Vertx vertx = Vertx.vertx();
protected static final Hash mockHash = Hash.hash(Bytes32.fromHexStringLenient("0x1337deadbeef"));
@Mock private ProtocolSpec protocolSpec;
@Mock private ProtocolSchedule protocolSchedule;
@Mock private ProtocolContext protocolContext;
@Mock protected ProtocolSpec protocolSpec;
@Mock protected ProtocolSchedule protocolSchedule;
@Mock protected ProtocolContext protocolContext;
@Mock private MergeContext mergeContext;
@Mock protected MergeContext mergeContext;
@Mock protected MergeMiningCoordinator mergeCoordinator;
@Mock protected MutableBlockchain blockchain;
@Mock private EthPeers ethPeers;
@Mock protected EthPeers ethPeers;
@Mock protected EngineCallListener engineCallListener;
@ -128,20 +126,14 @@ public abstract class AbstractEngineNewPayloadTest {
public void before() {
when(protocolContext.safeConsensusContext(Mockito.any())).thenReturn(Optional.of(mergeContext));
when(protocolContext.getBlockchain()).thenReturn(blockchain);
when(protocolSpec.getWithdrawalsValidator())
lenient()
.when(protocolSpec.getWithdrawalsValidator())
.thenReturn(new WithdrawalsValidator.ProhibitedWithdrawals());
when(protocolSpec.getDepositsValidator())
lenient()
.when(protocolSpec.getDepositsValidator())
.thenReturn(new DepositsValidator.ProhibitedDeposits());
when(protocolSchedule.getByBlockHeader(any())).thenReturn(protocolSpec);
when(ethPeers.peerCount()).thenReturn(1);
this.method =
methodFactory.create(
vertx,
protocolSchedule,
protocolContext,
mergeCoordinator,
ethPeers,
engineCallListener);
lenient().when(protocolSchedule.getByBlockHeader(any())).thenReturn(protocolSpec);
lenient().when(ethPeers.peerCount()).thenReturn(1);
}
@Test
@ -184,7 +176,8 @@ public abstract class AbstractEngineNewPayloadTest {
when(mergeCoordinator.getLatestValidAncestor(any(BlockHeader.class)))
.thenReturn(Optional.empty());
if (validateTerminalPoWBlock()) {
when(mergeCoordinator.latestValidAncestorDescendsFromTerminal(any(BlockHeader.class)))
lenient()
.when(mergeCoordinator.latestValidAncestorDescendsFromTerminal(any(BlockHeader.class)))
.thenReturn(true);
}
@ -269,7 +262,8 @@ public abstract class AbstractEngineNewPayloadTest {
when(mergeCoordinator.getLatestValidAncestor(any(BlockHeader.class)))
.thenReturn(Optional.of(mockHash));
if (validateTerminalPoWBlock()) {
when(mergeCoordinator.latestValidAncestorDescendsFromTerminal(any(BlockHeader.class)))
lenient()
.when(mergeCoordinator.latestValidAncestorDescendsFromTerminal(any(BlockHeader.class)))
.thenReturn(true);
}
when(mergeCoordinator.rememberBlock(any())).thenThrow(new MerkleTrieException("missing leaf"));
@ -285,7 +279,9 @@ public abstract class AbstractEngineNewPayloadTest {
@Test
public void shouldReturnInvalidBlockHashOnBadHashParameter() {
BlockHeader mockHeader = new BlockHeaderTestFixture().buildHeader();
lenient()
.when(mergeCoordinator.getLatestValidAncestor(mockHeader.getBlockHash()))
.thenReturn(Optional.empty());
var resp = resp(mockPayload(mockHeader, Collections.emptyList()));
EnginePayloadStatusResult res = fromSuccessResp(resp);
@ -372,14 +368,6 @@ public abstract class AbstractEngineNewPayloadTest {
verify(engineCallListener, times(1)).executionEngineCalled();
}
@Test
@Disabled
public void shouldRespondWithInvalidTerminalPowBlock() {
// TODO: implement this as part of https://github.com/hyperledger/besu/issues/3141
// mergeContext is a mock
// assertThat(mergeContext.getTerminalTotalDifficulty()).isNull();
}
@Test
public void shouldRespondWithInvalidIfExtraDataIsNull() {
BlockHeader realHeader = new BlockHeaderTestFixture().baseFeePerGas(Wei.ONE).buildHeader();
@ -409,146 +397,6 @@ public abstract class AbstractEngineNewPayloadTest {
verify(engineCallListener, times(1)).executionEngineCalled();
}
@Test
public void shouldReturnInvalidIfWithdrawalsIsNotNull_WhenWithdrawalsProhibited() {
final List<WithdrawalParameter> withdrawals = List.of();
when(protocolSpec.getWithdrawalsValidator())
.thenReturn(new WithdrawalsValidator.ProhibitedWithdrawals());
var resp =
resp(
mockPayload(
createBlockHeader(Optional.of(Collections.emptyList()), Optional.empty()),
Collections.emptyList(),
withdrawals,
null));
final JsonRpcError jsonRpcError = fromErrorResp(resp);
assertThat(jsonRpcError.getCode()).isEqualTo(INVALID_PARAMS.getCode());
verify(engineCallListener, times(1)).executionEngineCalled();
}
@Test
public void shouldReturnValidIfWithdrawalsIsNull_WhenWithdrawalsProhibited() {
final List<WithdrawalParameter> withdrawals = null;
when(protocolSpec.getWithdrawalsValidator())
.thenReturn(new WithdrawalsValidator.ProhibitedWithdrawals());
BlockHeader mockHeader =
setupValidPayload(
new BlockProcessingResult(Optional.of(new BlockProcessingOutputs(null, List.of()))),
Optional.empty(),
Optional.empty());
var resp = resp(mockPayload(mockHeader, Collections.emptyList(), withdrawals, null));
assertValidResponse(mockHeader, resp);
}
@Test
public void shouldReturnInvalidIfWithdrawalsIsNull_WhenWithdrawalsAllowed() {
final List<WithdrawalParameter> withdrawals = null;
when(protocolSpec.getWithdrawalsValidator())
.thenReturn(new WithdrawalsValidator.AllowedWithdrawals());
var resp =
resp(
mockPayload(
createBlockHeader(Optional.empty(), Optional.empty()),
Collections.emptyList(),
withdrawals,
null));
assertThat(fromErrorResp(resp).getCode()).isEqualTo(INVALID_PARAMS.getCode());
verify(engineCallListener, times(1)).executionEngineCalled();
}
@Test
public void shouldReturnValidIfWithdrawalsIsNotNull_WhenWithdrawalsAllowed() {
final List<WithdrawalParameter> withdrawalsParam = List.of(WITHDRAWAL_PARAM_1);
final List<Withdrawal> withdrawals = List.of(WITHDRAWAL_PARAM_1.toWithdrawal());
when(protocolSpec.getWithdrawalsValidator())
.thenReturn(new WithdrawalsValidator.AllowedWithdrawals());
BlockHeader mockHeader =
setupValidPayload(
new BlockProcessingResult(Optional.of(new BlockProcessingOutputs(null, List.of()))),
Optional.of(withdrawals),
Optional.empty());
var resp = resp(mockPayload(mockHeader, Collections.emptyList(), withdrawalsParam, null));
assertValidResponse(mockHeader, resp);
}
@Test
public void shouldReturnInvalidIfDepositsIsNotNull_WhenDepositsProhibited() {
final List<DepositParameter> deposits = List.of();
when(protocolSpec.getDepositsValidator())
.thenReturn(new DepositsValidator.ProhibitedDeposits());
var resp =
resp(
mockPayload(
createBlockHeader(Optional.empty(), Optional.of(Collections.emptyList())),
Collections.emptyList(),
null,
deposits));
final JsonRpcError jsonRpcError = fromErrorResp(resp);
assertThat(jsonRpcError.getCode()).isEqualTo(INVALID_PARAMS.getCode());
verify(engineCallListener, times(1)).executionEngineCalled();
}
@Test
public void shouldReturnValidIfDepositsIsNull_WhenDepositsProhibited() {
final List<DepositParameter> deposits = null;
when(protocolSpec.getDepositsValidator())
.thenReturn(new DepositsValidator.ProhibitedDeposits());
BlockHeader mockHeader =
setupValidPayload(
new BlockProcessingResult(Optional.of(new BlockProcessingOutputs(null, List.of()))),
Optional.empty(),
Optional.empty());
var resp = resp(mockPayload(mockHeader, Collections.emptyList(), null, deposits));
assertValidResponse(mockHeader, resp);
}
@Test
public void shouldReturnInvalidIfDepositsIsNull_WhenDepositsAllowed() {
final List<DepositParameter> deposits = null;
when(protocolSpec.getDepositsValidator())
.thenReturn(new DepositsValidator.AllowedDeposits(depositContractAddress));
var resp =
resp(
mockPayload(
createBlockHeader(Optional.empty(), Optional.empty()),
Collections.emptyList(),
null,
deposits));
assertThat(fromErrorResp(resp).getCode()).isEqualTo(INVALID_PARAMS.getCode());
verify(engineCallListener, times(1)).executionEngineCalled();
}
@Test
public void shouldReturnValidIfDepositsIsNotNull_WhenDepositsAllowed() {
final List<DepositParameter> depositsParam = List.of(DEPOSIT_PARAM_1);
final List<Deposit> deposits = List.of(DEPOSIT_PARAM_1.toDeposit());
when(protocolSpec.getDepositsValidator())
.thenReturn(new DepositsValidator.AllowedDeposits(depositContractAddress));
BlockHeader mockHeader =
setupValidPayload(
new BlockProcessingResult(Optional.of(new BlockProcessingOutputs(null, List.of()))),
Optional.empty(),
Optional.of(deposits));
var resp = resp(mockPayload(mockHeader, Collections.emptyList(), null, depositsParam));
assertValidResponse(mockHeader, resp);
}
@Test
public void shouldReturnValidIfProtocolScheduleIsEmpty() {
when(protocolSchedule.getByBlockHeader(any())).thenReturn(null);
@ -566,35 +414,32 @@ public abstract class AbstractEngineNewPayloadTest {
protected JsonRpcResponse resp(final EnginePayloadParameter payload) {
return method.response(
new JsonRpcRequestContext(
new JsonRpcRequest(
"2.0", RpcMethod.ENGINE_NEW_PAYLOAD_V2.getMethodName(), new Object[] {payload})));
new JsonRpcRequest("2.0", this.method.getName(), new Object[] {payload})));
}
protected EnginePayloadParameter mockPayload(final BlockHeader header, final List<String> txs) {
return new EnginePayloadParameter(
header.getHash(),
header.getParentHash(),
header.getCoinbase(),
header.getStateRoot(),
new UnsignedLongParameter(header.getNumber()),
header.getBaseFee().map(w -> w.toHexString()).orElse("0x0"),
new UnsignedLongParameter(header.getGasLimit()),
new UnsignedLongParameter(header.getGasUsed()),
new UnsignedLongParameter(header.getTimestamp()),
header.getExtraData() == null ? null : header.getExtraData().toHexString(),
header.getReceiptsRoot(),
header.getLogsBloom(),
header.getPrevRandao().map(Bytes32::toHexString).orElse("0x0"),
txs,
null,
null);
return mockPayload(header, txs, null, null, null);
}
private EnginePayloadParameter mockPayload(
protected EnginePayloadParameter mockPayload(
final BlockHeader header,
final List<String> txs,
final List<WithdrawalParameter> withdrawals,
final List<DepositParameter> deposits) {
return mockPayload(
header,
txs,
withdrawals,
deposits,
List.of(VersionedHash.DEFAULT_VERSIONED_HASH.toBytes()));
}
protected EnginePayloadParameter mockPayload(
final BlockHeader header,
final List<String> txs,
final List<WithdrawalParameter> withdrawals,
final List<DepositParameter> deposits,
final List<Bytes32> versionedHashes) {
return new EnginePayloadParameter(
header.getHash(),
header.getParentHash(),
@ -611,14 +456,17 @@ public abstract class AbstractEngineNewPayloadTest {
header.getPrevRandao().map(Bytes32::toHexString).orElse("0x0"),
txs,
withdrawals,
header.getDataGasUsed().map(UnsignedLongParameter::new).orElse(null),
header.getExcessDataGas().map(DataGas::toHexString).orElse(null),
versionedHashes,
deposits);
}
@NotNull
private BlockHeader setupValidPayload(
protected BlockHeader setupValidPayload(
final BlockProcessingResult value,
final Optional<List<Withdrawal>> maybeWithdrawals,
final Optional<List<Deposit>> maybeDeposits) {
BlockHeader mockHeader = createBlockHeader(maybeWithdrawals, maybeDeposits);
when(blockchain.getBlockByHash(mockHeader.getHash())).thenReturn(Optional.empty());
when(blockchain.getBlockHeader(mockHeader.getParentHash()))
@ -626,7 +474,8 @@ public abstract class AbstractEngineNewPayloadTest {
when(mergeCoordinator.getLatestValidAncestor(any(BlockHeader.class)))
.thenReturn(Optional.of(mockHash));
if (validateTerminalPoWBlock()) {
when(mergeCoordinator.latestValidAncestorDescendsFromTerminal(any(BlockHeader.class)))
lenient()
.when(mergeCoordinator.latestValidAncestorDescendsFromTerminal(any(BlockHeader.class)))
.thenReturn(true);
}
when(mergeCoordinator.rememberBlock(any())).thenReturn(value);
@ -650,7 +499,7 @@ public abstract class AbstractEngineNewPayloadTest {
.get();
}
private JsonRpcError fromErrorResp(final JsonRpcResponse resp) {
protected JsonRpcError fromErrorResp(final JsonRpcResponse resp) {
assertThat(resp.getType()).isEqualTo(JsonRpcResponseType.ERROR);
return Optional.of(resp)
.map(JsonRpcErrorResponse.class::cast)
@ -675,7 +524,7 @@ public abstract class AbstractEngineNewPayloadTest {
return mockHeader;
}
private void assertValidResponse(final BlockHeader mockHeader, final JsonRpcResponse resp) {
protected void assertValidResponse(final BlockHeader mockHeader, final JsonRpcResponse resp) {
EnginePayloadStatusResult res = fromSuccessResp(resp);
assertThat(res.getLatestValidHash().get()).isEqualTo(mockHeader.getHash());
assertThat(res.getStatusAsString()).isEqualTo(VALID.name());

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

@ -0,0 +1,169 @@
/*
* 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 static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.consensus.merge.blockcreation.PayloadIdentifier;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.BlobsWithCommitments;
import org.hyperledger.besu.datatypes.DataGas;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.TransactionType;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.EngineGetPayloadResultV3;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.Quantity;
import org.hyperledger.besu.ethereum.core.BlobTestFixture;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.BlockBody;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture;
import org.hyperledger.besu.ethereum.core.BlockWithReceipts;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.core.TransactionReceipt;
import org.hyperledger.besu.ethereum.core.TransactionTestFixture;
import org.hyperledger.besu.ethereum.mainnet.ScheduledProtocolSpec;
import java.math.BigInteger;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(
MockitoExtension.class) // mocks in parent class may not be used, throwing unnecessary stubbing
public class EngineGetPayloadV3Test extends AbstractEngineGetPayloadTest {
private static final long CANCUN_AT = 31337L;
public EngineGetPayloadV3Test() {
super();
}
@BeforeEach
@Override
public void before() {
lenient()
.when(mergeContext.retrieveBlockById(mockPid))
.thenReturn(Optional.of(mockBlockWithReceipts));
when(protocolContext.safeConsensusContext(Mockito.any())).thenReturn(Optional.of(mergeContext));
when(protocolSchedule.hardforkFor(any()))
.thenReturn(Optional.of(new ScheduledProtocolSpec.Hardfork("shanghai", SHANGHAI_AT)));
this.method =
new EngineGetPayloadV3(
vertx,
protocolContext,
mergeMiningCoordinator,
factory,
engineCallListener,
protocolSchedule);
}
@Override
@Test
public void shouldReturnExpectedMethodName() {
assertThat(method.getName()).isEqualTo("engine_getPayloadV3");
}
@Override
@Test
public void shouldReturnBlockForKnownPayloadId() {
BlockHeader cancunHeader =
new BlockHeaderTestFixture()
.prevRandao(Bytes32.random())
.timestamp(CANCUN_AT + 1)
.excessDataGas(DataGas.of(10L))
.buildHeader();
// should return withdrawals and excessGas for a post-cancun block
PayloadIdentifier postCancunPid =
PayloadIdentifier.forPayloadParams(
Hash.ZERO,
CANCUN_AT,
Bytes32.random(),
Address.fromHexString("0x42"),
Optional.empty());
BlobTestFixture blobTestFixture = new BlobTestFixture();
BlobsWithCommitments bwc = blobTestFixture.createBlobsWithCommitments(1);
Transaction blobTx =
new TransactionTestFixture()
.to(Optional.of(Address.fromHexString("0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF")))
.type(TransactionType.BLOB)
.chainId(Optional.of(BigInteger.ONE))
.maxFeePerGas(Optional.of(Wei.of(15)))
.maxFeePerDataGas(Optional.of(Wei.of(128)))
.maxPriorityFeePerGas(Optional.of(Wei.of(1)))
.blobsWithCommitments(Optional.of(bwc))
.versionedHashes(Optional.of(bwc.getVersionedHashes()))
.createTransaction(senderKeys);
TransactionReceipt blobReceipt = mock(TransactionReceipt.class);
when(blobReceipt.getCumulativeGasUsed()).thenReturn(100L);
BlockWithReceipts postCancunBlock =
new BlockWithReceipts(
new Block(
cancunHeader,
new BlockBody(
List.of(blobTx),
Collections.emptyList(),
Optional.of(Collections.emptyList()),
Optional.empty())),
List.of(blobReceipt));
when(mergeContext.retrieveBlockById(postCancunPid)).thenReturn(Optional.of(postCancunBlock));
final var resp = resp(RpcMethod.ENGINE_GET_PAYLOAD_V3.getMethodName(), postCancunPid);
assertThat(resp).isInstanceOf(JsonRpcSuccessResponse.class);
Optional.of(resp)
.map(JsonRpcSuccessResponse.class::cast)
.ifPresent(
r -> {
assertThat(r.getResult()).isInstanceOf(EngineGetPayloadResultV3.class);
final EngineGetPayloadResultV3 res = (EngineGetPayloadResultV3) r.getResult();
assertThat(res.getExecutionPayload().getWithdrawals()).isNotNull();
assertThat(res.getExecutionPayload().getHash())
.isEqualTo(cancunHeader.getHash().toString());
assertThat(res.getBlockValue()).isEqualTo(Quantity.create(0));
assertThat(res.getExecutionPayload().getPrevRandao())
.isEqualTo(cancunHeader.getPrevRandao().map(Bytes32::toString).orElse(""));
// excessDataGas: QUANTITY, 256 bits
String expectedQuantityOf10 = Bytes32.leftPad(Bytes.of(10)).toQuantityHexString();
assertThat(res.getExecutionPayload().getExcessDataGas()).isNotEmpty();
assertThat(res.getExecutionPayload().getExcessDataGas())
.isEqualTo(expectedQuantityOf10);
});
verify(engineCallListener, times(1)).executionEngineCalled();
}
@Override
protected String getMethodName() {
return RpcMethod.ENGINE_GET_PAYLOAD_V3.getMethodName();
}
}

@ -0,0 +1,180 @@
/*
* 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 static org.assertj.core.api.Assertions.assertThat;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.DepositParameterTestFixture.DEPOSIT_PARAM_1;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType.INVALID_PARAMS;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.DataGas;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.BlockProcessingOutputs;
import org.hyperledger.besu.ethereum.BlockProcessingResult;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.DepositParameter;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture;
import org.hyperledger.besu.ethereum.core.Deposit;
import org.hyperledger.besu.ethereum.core.Withdrawal;
import org.hyperledger.besu.ethereum.mainnet.BodyValidation;
import org.hyperledger.besu.ethereum.mainnet.DepositsValidator;
import org.hyperledger.besu.ethereum.mainnet.ScheduledProtocolSpec;
import org.hyperledger.besu.evm.gascalculator.CancunGasCalculator;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
public class EngineNewPayloadEIP6110Test extends EngineNewPayloadV3Test {
private static final Address depositContractAddress =
Address.fromHexString("0x00000000219ab540356cbb839cbe05303d7705fa");
public EngineNewPayloadEIP6110Test() {}
@BeforeEach
@Override
public void before() {
super.before();
this.method =
new EngineNewPayloadV3(
vertx,
protocolSchedule,
protocolContext,
mergeCoordinator,
ethPeers,
engineCallListener);
lenient()
.when(protocolSchedule.hardforkFor(any()))
.thenReturn(
Optional.of(new ScheduledProtocolSpec.Hardfork("Cancun", super.CANCUN_TIMESTAMP)));
lenient().when(protocolSpec.getGasCalculator()).thenReturn(new CancunGasCalculator());
}
@Override
public void shouldReturnExpectedMethodName() {
assertThat(method.getName()).isEqualTo("engine_newPayloadV3");
}
@Test
public void shouldReturnValidIfDepositsIsNull_WhenDepositsProhibited() {
final List<DepositParameter> deposits = null;
when(protocolSpec.getDepositsValidator())
.thenReturn(new DepositsValidator.ProhibitedDeposits());
BlockHeader mockHeader =
setupValidPayload(
new BlockProcessingResult(Optional.of(new BlockProcessingOutputs(null, List.of()))),
Optional.empty(),
Optional.empty());
var resp = resp(mockPayload(mockHeader, Collections.emptyList(), null, deposits, null));
assertValidResponse(mockHeader, resp);
}
@Test
public void shouldReturnInvalidIfDepositsIsNull_WhenDepositsAllowed() {
final List<DepositParameter> deposits = null;
lenient()
.when(protocolSpec.getDepositsValidator())
.thenReturn(new DepositsValidator.AllowedDeposits(depositContractAddress));
var resp =
resp(
mockPayload(
createBlockHeader(Optional.empty(), Optional.empty()),
Collections.emptyList(),
null,
deposits));
assertThat(fromErrorResp(resp).getCode()).isEqualTo(INVALID_PARAMS.getCode());
verify(engineCallListener, times(1)).executionEngineCalled();
}
@Test
public void shouldReturnValidIfDepositsIsNotNull_WhenDepositsAllowed() {
final List<DepositParameter> depositsParam = List.of(DEPOSIT_PARAM_1);
final List<Deposit> deposits = List.of(DEPOSIT_PARAM_1.toDeposit());
when(protocolSpec.getDepositsValidator())
.thenReturn(new DepositsValidator.AllowedDeposits(depositContractAddress));
BlockHeader mockHeader =
setupValidPayload(
new BlockProcessingResult(Optional.of(new BlockProcessingOutputs(null, List.of()))),
Optional.empty(),
Optional.of(deposits));
var resp = resp(mockPayload(mockHeader, Collections.emptyList(), null, depositsParam));
assertValidResponse(mockHeader, resp);
}
@Test
public void shouldReturnInvalidIfDepositsIsNotNull_WhenDepositsProhibited() {
final List<DepositParameter> deposits = List.of();
lenient()
.when(protocolSpec.getDepositsValidator())
.thenReturn(new DepositsValidator.ProhibitedDeposits());
var resp =
resp(
mockPayload(
createBlockHeader(Optional.empty(), Optional.of(Collections.emptyList())),
Collections.emptyList(),
null,
deposits));
final JsonRpcError jsonRpcError = fromErrorResp(resp);
assertThat(jsonRpcError.getCode()).isEqualTo(INVALID_PARAMS.getCode());
verify(engineCallListener, times(1)).executionEngineCalled();
}
@Override
protected BlockHeader createBlockHeader(
final Optional<List<Withdrawal>> maybeWithdrawals,
final Optional<List<Deposit>> maybeDeposits) {
BlockHeader parentBlockHeader =
new BlockHeaderTestFixture()
.baseFeePerGas(Wei.ONE)
.timestamp(super.EXPERIMENTAL_TIMESTAMP)
.excessDataGas(DataGas.ZERO)
.dataGasUsed(100L)
.buildHeader();
BlockHeader mockHeader =
new BlockHeaderTestFixture()
.baseFeePerGas(Wei.ONE)
.parentHash(parentBlockHeader.getParentHash())
.number(parentBlockHeader.getNumber() + 1)
.timestamp(parentBlockHeader.getTimestamp() + 1)
.withdrawalsRoot(maybeWithdrawals.map(BodyValidation::withdrawalsRoot).orElse(null))
.excessDataGas(DataGas.ZERO)
.dataGasUsed(100L)
.depositsRoot(maybeDeposits.map(BodyValidation::depositsRoot).orElse(null))
.buildHeader();
return mockHeader;
}
}

@ -32,6 +32,7 @@ import org.hyperledger.besu.ethereum.core.BlockHeader;
import java.util.Collections;
import java.util.Optional;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
@ -42,8 +43,20 @@ import org.mockito.quality.Strictness;
@MockitoSettings(strictness = Strictness.LENIENT)
public class EngineNewPayloadV1Test extends AbstractEngineNewPayloadTest {
public EngineNewPayloadV1Test() {
super(EngineNewPayloadV1::new);
public EngineNewPayloadV1Test() {}
@Override
@BeforeEach
public void before() {
super.before();
this.method =
new EngineNewPayloadV1(
vertx,
protocolSchedule,
protocolContext,
mergeCoordinator,
ethPeers,
engineCallListener);
}
@Override

@ -15,7 +15,28 @@
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod.EngineStatus.INVALID;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.WithdrawalParameterTestFixture.WITHDRAWAL_PARAM_1;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType.INVALID_PARAMS;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.ethereum.BlockProcessingOutputs;
import org.hyperledger.besu.ethereum.BlockProcessingResult;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.WithdrawalParameter;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.Withdrawal;
import org.hyperledger.besu.ethereum.mainnet.WithdrawalsValidator;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
@ -26,8 +47,20 @@ import org.mockito.quality.Strictness;
@MockitoSettings(strictness = Strictness.LENIENT)
public class EngineNewPayloadV2Test extends AbstractEngineNewPayloadTest {
public EngineNewPayloadV2Test() {
super(EngineNewPayloadV2::new);
public EngineNewPayloadV2Test() {}
@Override
@BeforeEach
public void before() {
super.before();
this.method =
new EngineNewPayloadV2(
vertx,
protocolSchedule,
protocolContext,
mergeCoordinator,
ethPeers,
engineCallListener);
}
@Override
@ -35,4 +68,86 @@ public class EngineNewPayloadV2Test extends AbstractEngineNewPayloadTest {
public void shouldReturnExpectedMethodName() {
assertThat(method.getName()).isEqualTo("engine_newPayloadV2");
}
@Test
public void shouldReturnValidIfWithdrawalsIsNotNull_WhenWithdrawalsAllowed() {
final List<WithdrawalParameter> withdrawalsParam = List.of(WITHDRAWAL_PARAM_1);
final List<Withdrawal> withdrawals = List.of(WITHDRAWAL_PARAM_1.toWithdrawal());
when(protocolSpec.getWithdrawalsValidator())
.thenReturn(new WithdrawalsValidator.AllowedWithdrawals());
BlockHeader mockHeader =
setupValidPayload(
new BlockProcessingResult(Optional.of(new BlockProcessingOutputs(null, List.of()))),
Optional.of(withdrawals),
Optional.empty());
var resp = resp(mockPayload(mockHeader, Collections.emptyList(), withdrawalsParam, null));
assertValidResponse(mockHeader, resp);
}
@Test
public void shouldReturnValidIfWithdrawalsIsNull_WhenWithdrawalsProhibited() {
final List<WithdrawalParameter> withdrawals = null;
when(protocolSpec.getWithdrawalsValidator())
.thenReturn(new WithdrawalsValidator.ProhibitedWithdrawals());
BlockHeader mockHeader =
setupValidPayload(
new BlockProcessingResult(Optional.of(new BlockProcessingOutputs(null, List.of()))),
Optional.empty(),
Optional.empty());
var resp = resp(mockPayload(mockHeader, Collections.emptyList(), withdrawals, null, null));
assertValidResponse(mockHeader, resp);
}
@Test
public void shouldReturnInvalidIfWithdrawalsIsNotNull_WhenWithdrawalsProhibited() {
final List<WithdrawalParameter> withdrawals = List.of();
lenient()
.when(protocolSpec.getWithdrawalsValidator())
.thenReturn(new WithdrawalsValidator.ProhibitedWithdrawals());
var resp =
resp(
mockPayload(
createBlockHeader(Optional.of(Collections.emptyList()), Optional.empty()),
Collections.emptyList(),
withdrawals,
null,
null));
final JsonRpcError jsonRpcError = fromErrorResp(resp);
assertThat(jsonRpcError.getCode()).isEqualTo(INVALID_PARAMS.getCode());
verify(engineCallListener, times(1)).executionEngineCalled();
}
@Test
public void shouldReturnInvalidIfWithdrawalsIsNull_WhenWithdrawalsAllowed() {
final List<WithdrawalParameter> withdrawals = null;
when(protocolSpec.getWithdrawalsValidator())
.thenReturn(new WithdrawalsValidator.AllowedWithdrawals());
var resp =
resp(
mockPayload(
createBlockHeader(Optional.empty(), Optional.empty()),
Collections.emptyList(),
withdrawals,
null));
assertThat(fromErrorResp(resp).getCode()).isEqualTo(INVALID_PARAMS.getCode());
verify(engineCallListener, times(1)).executionEngineCalled();
}
@Override
protected boolean validateTerminalPoWBlock() {
return false;
}
@Override
protected ExecutionEngineJsonRpcMethod.EngineStatus getExpectedInvalidBlockHashStatus() {
return INVALID;
}
}

@ -0,0 +1,119 @@
/*
* 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 static org.assertj.core.api.Assertions.assertThat;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod.EngineStatus.INVALID;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.EnginePayloadParameter;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.EnginePayloadStatusResult;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture;
import org.hyperledger.besu.ethereum.core.Deposit;
import org.hyperledger.besu.ethereum.core.Withdrawal;
import org.hyperledger.besu.ethereum.mainnet.BodyValidation;
import org.hyperledger.besu.ethereum.mainnet.ScheduledProtocolSpec;
import org.hyperledger.besu.evm.gascalculator.CancunGasCalculator;
import java.util.List;
import java.util.Optional;
import org.apache.tuweni.bytes.Bytes;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
public class EngineNewPayloadV3Test extends EngineNewPayloadV2Test {
public EngineNewPayloadV3Test() {}
@Override
@Test
public void shouldReturnExpectedMethodName() {
assertThat(method.getName()).isEqualTo("engine_newPayloadV3");
}
@BeforeEach
@Override
public void before() {
super.before();
this.method =
new EngineNewPayloadV3(
vertx,
protocolSchedule,
protocolContext,
mergeCoordinator,
ethPeers,
engineCallListener);
lenient().when(protocolSpec.getGasCalculator()).thenReturn(new CancunGasCalculator());
lenient()
.when(protocolSchedule.hardforkFor(any()))
.thenReturn(
Optional.of(new ScheduledProtocolSpec.Hardfork("Cancun", super.CANCUN_TIMESTAMP)));
}
@Test
public void shouldInvalidPayloadOnShortVersionedHash() {
Bytes shortHash = Bytes.fromHexString("0x" + "69".repeat(31));
EnginePayloadParameter payload = mock(EnginePayloadParameter.class);
JsonRpcResponse badParam =
method.response(
new JsonRpcRequestContext(
new JsonRpcRequest(
"2.0",
RpcMethod.ENGINE_NEW_PAYLOAD_V3.getMethodName(),
new Object[] {payload, List.of(shortHash.toHexString())})));
EnginePayloadStatusResult res = fromSuccessResp(badParam);
assertThat(res.getStatusAsString()).isEqualTo(INVALID.name());
assertThat(res.getError()).isEqualTo("Invalid versionedHash");
}
@Override
protected BlockHeader createBlockHeader(
final Optional<List<Withdrawal>> maybeWithdrawals,
final Optional<List<Deposit>> maybeDeposits) {
BlockHeader parentBlockHeader =
new BlockHeaderTestFixture()
.baseFeePerGas(Wei.ONE)
.timestamp(super.CANCUN_TIMESTAMP)
.buildHeader();
BlockHeader mockHeader =
new BlockHeaderTestFixture()
.baseFeePerGas(Wei.ONE)
.parentHash(parentBlockHeader.getParentHash())
.number(parentBlockHeader.getNumber() + 1)
.timestamp(parentBlockHeader.getTimestamp() + 1)
.withdrawalsRoot(maybeWithdrawals.map(BodyValidation::withdrawalsRoot).orElse(null))
.depositsRoot(maybeDeposits.map(BodyValidation::depositsRoot).orElse(null))
.buildHeader();
return mockHeader;
}
@Override
@Test
public void shouldReturnValidIfProtocolScheduleIsEmpty() {
// no longer the case, blob validation requires a protocol schedule
}
}

@ -16,7 +16,10 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ -35,6 +38,7 @@ import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec;
import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket;
import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult;
import org.hyperledger.besu.ethereum.vm.DebugOperationTracer;
import org.hyperledger.besu.evm.gascalculator.GasCalculator;
import org.hyperledger.besu.evm.tracing.StandardJsonTracer;
import org.hyperledger.besu.evm.worldstate.StackedUpdater;
import org.hyperledger.besu.evm.worldstate.WorldUpdater;
@ -80,6 +84,7 @@ public class TransactionTracerTest {
@Mock private DebugOperationTracer tracer;
@Mock private ProtocolSpec protocolSpec;
@Mock private GasCalculator gasCalculator;
@Mock private Tracer.TraceableState mutableWorldState;
@ -114,6 +119,8 @@ public class TransactionTracerTest {
when(protocolSpec.getFeeMarket()).thenReturn(FeeMarket.london(0L));
when(blockchain.getChainHeadHeader()).thenReturn(blockHeader);
when(protocolSpec.getBadBlocksManager()).thenReturn(new BadBlockManager());
when(protocolSpec.getGasCalculator()).thenReturn(gasCalculator);
lenient().when(gasCalculator.computeExcessDataGas(anyLong(), anyInt())).thenReturn(0L);
}
@Test

@ -0,0 +1,40 @@
/*
* 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.results;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.security.InvalidParameterException;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
public class BlobsBundleV1Test {
@Test
public void blobsBundleV1MustHaveSameNumberOfElements() {
String actualMessage =
assertThrows(
InvalidParameterException.class,
() -> new BlobsBundleV1(List.of(""), List.of(""), List.of()))
.getMessage();
final String expectedMessage = "There must be an equal number of blobs, commitments and proofs";
assertThat(actualMessage).isEqualTo(expectedMessage);
}
}

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

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

@ -14,6 +14,8 @@
*/
package org.hyperledger.besu.ethereum.blockcreation;
import static org.hyperledger.besu.ethereum.mainnet.feemarket.ExcessDataGasCalculator.calculateExcessDataGasForParent;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.DataGas;
import org.hyperledger.besu.datatypes.Hash;
@ -169,10 +171,10 @@ public abstract class AbstractBlockCreator implements AsyncBlockCreator {
createPendingBlockHeader(timestamp, maybePrevRandao, newProtocolSpec);
final Address miningBeneficiary =
miningBeneficiaryCalculator.getMiningBeneficiary(processableBlockHeader.getNumber());
final Wei dataGasPrice =
Wei dataGasPrice =
newProtocolSpec
.getFeeMarket()
.dataPrice(parentHeader.getExcessDataGas().orElse(DataGas.ZERO));
.dataPricePerGas(calculateExcessDataGasForParent(newProtocolSpec, parentHeader));
throwIfStopped();
@ -228,11 +230,11 @@ public abstract class AbstractBlockCreator implements AsyncBlockCreator {
throwIfStopped();
final DataGas newExcessDataGas = computeExcessDataGas(transactionResults, newProtocolSpec);
final GasUsage usage = computeExcessDataGas(transactionResults, newProtocolSpec);
throwIfStopped();
final SealableBlockHeader sealableBlockHeader =
BlockHeaderBuilder builder =
BlockHeaderBuilder.create()
.populateFrom(processableBlockHeader)
.ommersHash(BodyValidation.ommersHash(ommers))
@ -247,9 +249,12 @@ public abstract class AbstractBlockCreator implements AsyncBlockCreator {
withdrawalsCanBeProcessed
? BodyValidation.withdrawalsRoot(maybeWithdrawals.get())
: null)
.depositsRoot(maybeDeposits.map(BodyValidation::depositsRoot).orElse(null))
.excessDataGas(newExcessDataGas)
.buildSealableBlockHeader();
.depositsRoot(maybeDeposits.map(BodyValidation::depositsRoot).orElse(null));
if (usage != null) {
builder.dataGasUsed(usage.used.toLong()).excessDataGas(usage.excessDataGas);
}
final SealableBlockHeader sealableBlockHeader = builder.buildSealableBlockHeader();
final BlockHeader blockHeader = createFinalBlockHeader(sealableBlockHeader);
@ -280,8 +285,11 @@ public abstract class AbstractBlockCreator implements AsyncBlockCreator {
.toList();
}
private DataGas computeExcessDataGas(
TransactionSelectionResults transactionResults, ProtocolSpec newProtocolSpec) {
record GasUsage(DataGas excessDataGas, DataGas used) {}
;
private GasUsage computeExcessDataGas(
final TransactionSelectionResults transactionResults, final ProtocolSpec newProtocolSpec) {
if (newProtocolSpec.getFeeMarket().implementsDataFee()) {
final var gasCalculator = newProtocolSpec.getGasCalculator();
@ -292,9 +300,12 @@ public abstract class AbstractBlockCreator implements AsyncBlockCreator {
.sum();
// casting parent excess data gas to long since for the moment it should be well below that
// limit
return DataGas.of(
gasCalculator.computeExcessDataGas(
parentHeader.getExcessDataGas().map(DataGas::toLong).orElse(0L), newBlobsCount));
DataGas ecessDataGas =
DataGas.of(
gasCalculator.computeExcessDataGas(
parentHeader.getExcessDataGas().map(DataGas::toLong).orElse(0L), newBlobsCount));
DataGas used = DataGas.of(gasCalculator.dataGasCost(newBlobsCount));
return new GasUsage(ecessDataGas, used);
}
return null;
}

@ -27,15 +27,21 @@ import static org.mockito.Mockito.when;
import org.hyperledger.besu.config.GenesisConfigFile;
import org.hyperledger.besu.config.GenesisConfigOptions;
import org.hyperledger.besu.crypto.KeyPair;
import org.hyperledger.besu.crypto.SignatureAlgorithmFactory;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.BLSPublicKey;
import org.hyperledger.besu.datatypes.BLSSignature;
import org.hyperledger.besu.datatypes.BlobsWithCommitments;
import org.hyperledger.besu.datatypes.DataGas;
import org.hyperledger.besu.datatypes.GWei;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.TransactionType;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.blockcreation.BlockCreator.BlockCreationResult;
import org.hyperledger.besu.ethereum.chain.MutableBlockchain;
import org.hyperledger.besu.ethereum.core.BlobTestFixture;
import org.hyperledger.besu.ethereum.core.BlockDataGenerator;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.BlockHeaderBuilder;
@ -45,7 +51,9 @@ import org.hyperledger.besu.ethereum.core.ExecutionContextTestFixture;
import org.hyperledger.besu.ethereum.core.MiningParameters;
import org.hyperledger.besu.ethereum.core.PrivacyParameters;
import org.hyperledger.besu.ethereum.core.SealableBlockHeader;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.core.TransactionReceipt;
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.transactions.ImmutableTransactionPoolConfiguration;
@ -61,6 +69,7 @@ import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.ProtocolScheduleBuilder;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpecAdapters;
import org.hyperledger.besu.ethereum.mainnet.WithdrawalsProcessor;
import org.hyperledger.besu.ethereum.mainnet.feemarket.CancunFeeMarket;
import org.hyperledger.besu.evm.internal.EvmConfiguration;
import org.hyperledger.besu.evm.log.Log;
import org.hyperledger.besu.evm.log.LogTopic;
@ -76,6 +85,7 @@ import java.util.function.Supplier;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.apache.tuweni.units.bigints.UInt64;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
@ -260,6 +270,52 @@ abstract class AbstractBlockCreatorTest {
assertThat(blockCreationResult.getBlock().getBody().getWithdrawals()).isEmpty();
}
@Disabled
@Test
public void computesGasUsageFromIncludedTransactions() {
final KeyPair senderKeys = SignatureAlgorithmFactory.getInstance().generateKeyPair();
final AbstractBlockCreator blockCreator = blockCreatorWithDataGasSupport();
BlobTestFixture blobTestFixture = new BlobTestFixture();
BlobsWithCommitments bwc = blobTestFixture.createBlobsWithCommitments(6);
TransactionTestFixture ttf = new TransactionTestFixture();
Transaction fullOfBlobs =
ttf.to(Optional.of(Address.ZERO))
.type(TransactionType.BLOB)
.chainId(Optional.of(BigInteger.valueOf(42)))
.maxFeePerGas(Optional.of(Wei.of(15)))
.maxFeePerDataGas(Optional.of(Wei.of(128)))
.maxPriorityFeePerGas(Optional.of(Wei.of(1)))
.versionedHashes(Optional.of(bwc.getVersionedHashes()))
.createTransaction(senderKeys);
ttf.blobsWithCommitments(Optional.of(bwc));
final BlockCreationResult blockCreationResult =
blockCreator.createBlock(
Optional.of(List.of(fullOfBlobs)),
Optional.empty(),
Optional.empty(),
Optional.empty(),
1L,
false);
long dataGasUsage = blockCreationResult.getBlock().getHeader().getGasUsed();
assertThat(dataGasUsage).isNotZero();
DataGas excessDataGas = blockCreationResult.getBlock().getHeader().getExcessDataGas().get();
assertThat(excessDataGas).isNotNull();
}
private AbstractBlockCreator blockCreatorWithDataGasSupport() {
final ProtocolSpecAdapters protocolSpecAdapters =
ProtocolSpecAdapters.create(
0,
specBuilder -> {
specBuilder.feeMarket(new CancunFeeMarket(0, Optional.empty()));
specBuilder.isReplayProtectionSupported(true);
specBuilder.withdrawalsProcessor(withdrawalsProcessor);
return specBuilder;
});
return createBlockCreator(protocolSpecAdapters, EMPTY_DEPOSIT_CONTRACT_ADDRESS);
}
private AbstractBlockCreator blockCreatorWithWithdrawalsProcessor() {
final ProtocolSpecAdapters protocolSpecAdapters =
ProtocolSpecAdapters.create(

@ -60,6 +60,7 @@ dependencies {
implementation 'io.tmio:tuweni-rlp'
implementation 'org.hyperledger.besu:bls12-381'
implementation 'org.immutables:value-annotations'
implementation 'tech.pegasys:jc-kzg-4844'
implementation 'io.prometheus:simpleclient_guava'
@ -93,6 +94,7 @@ dependencies {
integrationTestImplementation 'org.junit.jupiter:junit-jupiter-api'
integrationTestImplementation 'org.mockito:mockito-core'
integrationTestImplementation 'org.testcontainers:testcontainers'
integrationTestImplementation 'io.tmio:tuweni-bytes'
integrationTestRuntimeOnly 'org.junit.jupiter:junit-jupiter'

@ -16,6 +16,8 @@ package org.hyperledger.besu.ethereum;
public interface GasLimitCalculator {
static final long DATA_GAS_LIMIT = 786432;
long nextGasLimit(long currentGasLimit, long targetGasLimit, long newBlockNumber);
static GasLimitCalculator constant() {
@ -23,6 +25,6 @@ public interface GasLimitCalculator {
}
default long currentDataGasLimit() {
return 0L;
return DATA_GAS_LIMIT;
}
}

@ -19,6 +19,7 @@ import static java.util.Collections.emptyList;
import org.hyperledger.besu.config.GenesisAllocation;
import org.hyperledger.besu.config.GenesisConfigFile;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.DataGas;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.core.Block;
@ -167,6 +168,8 @@ public final class GenesisState {
.blockHeaderFunctions(ScheduleBasedBlockHeaderFunctions.create(protocolSchedule))
.baseFee(genesis.getGenesisBaseFeePerGas().orElse(null))
.withdrawalsRoot(isShanghaiAtGenesis(genesis) ? Hash.EMPTY_TRIE_HASH : null)
.dataGasUsed(isCancunAtGenesis(genesis) ? parseDataGasUsed(genesis) : null)
.excessDataGas(isCancunAtGenesis(genesis) ? parseExcessDataGas(genesis) : null)
.depositsRoot(isExperimentalEipsTimeAtGenesis(genesis) ? Hash.EMPTY_TRIE_HASH : null)
.buildBlockHeader();
}
@ -217,18 +220,38 @@ public final class GenesisState {
return withNiceErrorMessage("nonce", genesis.getNonce(), GenesisState::parseUnsignedLong);
}
private static long parseDataGasUsed(final GenesisConfigFile genesis) {
return withNiceErrorMessage(
"dataGasUsed", genesis.getDataGasUsed(), GenesisState::parseUnsignedLong);
}
private static DataGas parseExcessDataGas(final GenesisConfigFile genesis) {
long excessDataGas =
withNiceErrorMessage(
"excessDataGas", genesis.getExcessDataGas(), GenesisState::parseUnsignedLong);
return DataGas.of(excessDataGas);
}
private static long parseUnsignedLong(final String value) {
String nonce = value.toLowerCase(Locale.US);
if (nonce.startsWith("0x")) {
nonce = nonce.substring(2);
String v = value.toLowerCase(Locale.US);
if (v.startsWith("0x")) {
v = v.substring(2);
}
return Long.parseUnsignedLong(nonce, 16);
return Long.parseUnsignedLong(v, 16);
}
private static boolean isShanghaiAtGenesis(final GenesisConfigFile genesis) {
final OptionalLong shanghaiTimestamp = genesis.getConfigOptions().getShanghaiTime();
if (shanghaiTimestamp.isPresent()) {
return shanghaiTimestamp.getAsLong() == genesis.getTimestamp();
return genesis.getTimestamp() >= shanghaiTimestamp.getAsLong();
}
return false;
}
private static boolean isCancunAtGenesis(final GenesisConfigFile genesis) {
final OptionalLong cancunTimestamp = genesis.getConfigOptions().getCancunTime();
if (cancunTimestamp.isPresent()) {
return genesis.getTimestamp() >= cancunTimestamp.getAsLong();
}
return false;
}
@ -236,7 +259,7 @@ public final class GenesisState {
private static boolean isExperimentalEipsTimeAtGenesis(final GenesisConfigFile genesis) {
final OptionalLong experimentalEipsTime = genesis.getConfigOptions().getExperimentalEipsTime();
if (experimentalEipsTime.isPresent()) {
return experimentalEipsTime.getAsLong() == genesis.getTimestamp();
return genesis.getTimestamp() >= experimentalEipsTime.getAsLong();
}
return false;
}

@ -19,7 +19,9 @@ import org.hyperledger.besu.ethereum.rlp.RLP;
import org.hyperledger.besu.ethereum.rlp.RLPInput;
import org.hyperledger.besu.ethereum.rlp.RLPOutput;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import org.apache.tuweni.bytes.Bytes;
@ -57,7 +59,10 @@ public class Block {
out.startList();
header.writeTo(out);
body.writeTo(out);
out.writeList(body.getTransactions(), Transaction::writeTo);
out.writeList(body.getOmmers(), BlockHeader::writeTo);
body.getWithdrawals().ifPresent(withdrawals -> out.writeList(withdrawals, Withdrawal::writeTo));
body.getDeposits().ifPresent(deposits -> out.writeList(deposits, Deposit::writeTo));
out.endList();
}
@ -65,10 +70,15 @@ public class Block {
public static Block readFrom(final RLPInput in, final BlockHeaderFunctions hashFunction) {
in.enterList();
final BlockHeader header = BlockHeader.readFrom(in, hashFunction);
final BlockBody body = BlockBody.readFrom(in, hashFunction);
final List<Transaction> transactions = in.readList(Transaction::readFrom);
final List<BlockHeader> ommers = in.readList(rlp -> BlockHeader.readFrom(rlp, hashFunction));
final Optional<List<Withdrawal>> withdrawals =
in.isEndOfCurrentList() ? Optional.empty() : Optional.of(in.readList(Withdrawal::readFrom));
final Optional<List<Deposit>> deposits =
in.isEndOfCurrentList() ? Optional.empty() : Optional.of(in.readList(Deposit::readFrom));
in.leaveList();
return new Block(header, body);
return new Block(header, new BlockBody(transactions, ommers, withdrawals, deposits));
}
@Override

@ -62,6 +62,7 @@ public class BlockHeader extends SealableBlockHeader
final Bytes32 mixHashOrPrevRandao,
final long nonce,
final Hash withdrawalsRoot,
final long dataGasUsed,
final DataGas excessDataGas,
final Hash depositsRoot,
final BlockHeaderFunctions blockHeaderFunctions,
@ -83,6 +84,7 @@ public class BlockHeader extends SealableBlockHeader
baseFee,
mixHashOrPrevRandao,
withdrawalsRoot,
dataGasUsed,
excessDataGas,
depositsRoot);
this.nonce = nonce;
@ -108,6 +110,7 @@ public class BlockHeader extends SealableBlockHeader
final Bytes32 mixHashOrPrevRandao,
final long nonce,
final Hash withdrawalsRoot,
final Long dataGasUsed,
final DataGas excessDataGas,
final Hash depositsRoot,
final BlockHeaderFunctions blockHeaderFunctions) {
@ -128,6 +131,7 @@ public class BlockHeader extends SealableBlockHeader
baseFee,
mixHashOrPrevRandao,
withdrawalsRoot,
dataGasUsed,
excessDataGas,
depositsRoot);
this.nonce = nonce;
@ -212,8 +216,9 @@ public class BlockHeader extends SealableBlockHeader
if (withdrawalsRoot != null) {
out.writeBytes(withdrawalsRoot);
}
if (excessDataGas != null) {
out.writeUInt256Scalar(excessDataGas);
if (excessDataGas != null && dataGasUsed != null) {
out.writeLongScalar(dataGasUsed);
out.writeUInt64Scalar(excessDataGas);
}
if (depositsRoot != null) {
out.writeBytes(depositsRoot);
@ -241,9 +246,12 @@ public class BlockHeader extends SealableBlockHeader
final long nonce = input.readLong();
final Wei baseFee = !input.isEndOfCurrentList() ? Wei.of(input.readUInt256Scalar()) : null;
final Hash withdrawalHashRoot =
!input.isEndOfCurrentList() ? Hash.wrap(input.readBytes32()) : null;
!(input.isEndOfCurrentList() || input.isZeroLengthString())
? Hash.wrap(input.readBytes32())
: null;
final Long dataGasUsed = !input.isEndOfCurrentList() ? input.readLongScalar() : null;
final DataGas excessDataGas =
!input.isEndOfCurrentList() ? DataGas.of(input.readUInt256Scalar()) : null;
!input.isEndOfCurrentList() ? DataGas.of(input.readLongScalar()) : null;
final Hash depositHashRoot =
!input.isEndOfCurrentList() ? Hash.wrap(input.readBytes32()) : null;
input.leaveList();
@ -265,6 +273,7 @@ public class BlockHeader extends SealableBlockHeader
mixHashOrPrevRandao,
nonce,
withdrawalHashRoot,
dataGasUsed,
excessDataGas,
depositHashRoot,
blockHeaderFunctions);
@ -311,6 +320,9 @@ public class BlockHeader extends SealableBlockHeader
if (withdrawalsRoot != null) {
sb.append("withdrawalsRoot=").append(withdrawalsRoot).append(", ");
}
if (dataGasUsed != null) {
sb.append("dataGasUsed=").append(dataGasUsed).append(", ");
}
if (excessDataGas != null) {
sb.append("excessDataGas=").append(excessDataGas).append(", ");
}
@ -344,6 +356,7 @@ public class BlockHeader extends SealableBlockHeader
.getWithdrawalsRoot()
.map(h -> Hash.fromHexString(h.toHexString()))
.orElse(null),
pluginBlockHeader.getDataGasUsed().map(Long::longValue).orElse(null),
pluginBlockHeader.getExcessDataGas().map(DataGas::fromQuantity).orElse(null),
pluginBlockHeader
.getDepositsRoot()

@ -73,6 +73,7 @@ public class BlockHeaderBuilder {
// instead of an invalid identifier such as -1.
private OptionalLong nonce = OptionalLong.empty();
private Long dataGasUsed = null;
private DataGas excessDataGas = null;
public static BlockHeaderBuilder create() {
@ -119,6 +120,7 @@ public class BlockHeaderBuilder {
.nonce(header.getNonce())
.prevRandao(header.getPrevRandao().orElse(null))
.withdrawalsRoot(header.getWithdrawalsRoot().orElse(null))
.dataGasUsed(header.getDataGasUsed().orElse(null))
.excessDataGas(header.getExcessDataGas().orElse(null))
.depositsRoot(header.getDepositsRoot().orElse(null));
}
@ -170,6 +172,7 @@ public class BlockHeaderBuilder {
mixHashOrPrevRandao,
nonce.getAsLong(),
withdrawalsRoot,
dataGasUsed,
excessDataGas,
depositsRoot,
blockHeaderFunctions);
@ -187,6 +190,7 @@ public class BlockHeaderBuilder {
timestamp,
baseFee,
mixHashOrPrevRandao,
dataGasUsed,
excessDataGas);
}
@ -210,6 +214,7 @@ public class BlockHeaderBuilder {
baseFee,
mixHashOrPrevRandao,
withdrawalsRoot,
dataGasUsed,
excessDataGas,
depositsRoot);
}
@ -251,6 +256,7 @@ public class BlockHeaderBuilder {
timestamp(processableBlockHeader.getTimestamp());
baseFee(processableBlockHeader.getBaseFee().orElse(null));
processableBlockHeader.getPrevRandao().ifPresent(this::prevRandao);
processableBlockHeader.getDataGasUsed().ifPresent(this::dataGasUsed);
processableBlockHeader.getExcessDataGas().ifPresent(this::excessDataGas);
return this;
}
@ -273,6 +279,7 @@ public class BlockHeaderBuilder {
baseFee(sealableBlockHeader.getBaseFee().orElse(null));
sealableBlockHeader.getPrevRandao().ifPresent(this::prevRandao);
withdrawalsRoot(sealableBlockHeader.getWithdrawalsRoot().orElse(null));
sealableBlockHeader.getDataGasUsed().ifPresent(this::dataGasUsed);
sealableBlockHeader.getExcessDataGas().ifPresent(this::excessDataGas);
depositsRoot(sealableBlockHeader.getDepositsRoot().orElse(null));
return this;
@ -399,4 +406,9 @@ public class BlockHeaderBuilder {
this.excessDataGas = excessDataGas;
return this;
}
public BlockHeaderBuilder dataGasUsed(final Long dataGasUsed) {
this.dataGasUsed = dataGasUsed;
return this;
}
}

@ -44,6 +44,9 @@ public class ProcessableBlockHeader implements BlockValues {
protected final Wei baseFee;
// prevRandao is included for post-merge blocks
protected final Bytes32 mixHashOrPrevRandao;
// dataGasUsed is included for Cancun
protected final Long dataGasUsed;
// excessDataGas is included for Cancun
protected final DataGas excessDataGas;
protected ProcessableBlockHeader(
@ -55,6 +58,7 @@ public class ProcessableBlockHeader implements BlockValues {
final long timestamp,
final Wei baseFee,
final Bytes32 mixHashOrPrevRandao,
final Long dataGasUsed,
final DataGas excessDataGas) {
this.parentHash = parentHash;
this.coinbase = coinbase;
@ -64,6 +68,7 @@ public class ProcessableBlockHeader implements BlockValues {
this.timestamp = timestamp;
this.baseFee = baseFee;
this.mixHashOrPrevRandao = mixHashOrPrevRandao;
this.dataGasUsed = dataGasUsed;
this.excessDataGas = excessDataGas;
}
@ -167,6 +172,10 @@ public class ProcessableBlockHeader implements BlockValues {
return Optional.ofNullable(excessDataGas);
}
public Optional<Long> getDataGasUsed() {
return Optional.ofNullable(dataGasUsed);
}
public String toLogString() {
return getNumber() + " (time: " + getTimestamp() + ")";
}

@ -62,6 +62,7 @@ public class SealableBlockHeader extends ProcessableBlockHeader {
final Wei baseFee,
final Bytes32 mixHashOrPrevRandao,
final Hash withdrawalsRoot,
final Long dataGasUsed,
final DataGas excessDataGas,
final Hash depositsRoot) {
super(
@ -73,6 +74,7 @@ public class SealableBlockHeader extends ProcessableBlockHeader {
timestamp,
baseFee,
mixHashOrPrevRandao,
dataGasUsed,
excessDataGas);
this.ommersHash = ommersHash;
this.stateRoot = stateRoot;

@ -17,6 +17,7 @@ package org.hyperledger.besu.ethereum.core;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static org.hyperledger.besu.crypto.Hash.keccak256;
import static org.hyperledger.besu.datatypes.VersionedHash.SHA256_VERSION_ID;
import org.hyperledger.besu.crypto.KeyPair;
import org.hyperledger.besu.crypto.SECPPublicKey;
@ -24,10 +25,17 @@ import org.hyperledger.besu.crypto.SECPSignature;
import org.hyperledger.besu.crypto.SignatureAlgorithm;
import org.hyperledger.besu.crypto.SignatureAlgorithmFactory;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Blob;
import org.hyperledger.besu.datatypes.BlobsWithCommitments;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.KZGCommitment;
import org.hyperledger.besu.datatypes.KZGProof;
import org.hyperledger.besu.datatypes.Quantity;
import org.hyperledger.besu.datatypes.Sha256Hash;
import org.hyperledger.besu.datatypes.TransactionType;
import org.hyperledger.besu.datatypes.VersionedHash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.core.encoding.BlobTransactionEncoder;
import org.hyperledger.besu.ethereum.core.encoding.TransactionDecoder;
import org.hyperledger.besu.ethereum.core.encoding.TransactionEncoder;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput;
@ -107,7 +115,9 @@ public class Transaction
private final TransactionType transactionType;
private final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithmFactory.getInstance();
private final Optional<List<Hash>> versionedHashes;
private final Optional<List<VersionedHash>> versionedHashes;
private final Optional<BlobsWithCommitments> blobsWithCommitments;
public static Builder builder() {
return new Builder();
@ -159,7 +169,8 @@ public class Transaction
final Optional<List<AccessListEntry>> maybeAccessList,
final Address sender,
final Optional<BigInteger> chainId,
final Optional<List<Hash>> versionedHashes) {
final Optional<List<VersionedHash>> versionedHashes,
final Optional<BlobsWithCommitments> blobsWithCommitments) {
if (transactionType.requiresChainId()) {
checkArgument(
@ -207,6 +218,7 @@ public class Transaction
this.sender = sender;
this.chainId = chainId;
this.versionedHashes = versionedHashes;
this.blobsWithCommitments = blobsWithCommitments;
if (isUpfrontGasCostTooHigh()) {
throw new IllegalArgumentException("Upfront gas cost exceeds UInt256");
@ -226,7 +238,8 @@ public class Transaction
final Bytes payload,
final Address sender,
final Optional<BigInteger> chainId,
final Optional<List<Hash>> versionedHashes) {
final Optional<List<VersionedHash>> versionedHashes,
final Optional<BlobsWithCommitments> blobsWithCommitments) {
this(
TransactionType.FRONTIER,
nonce,
@ -242,7 +255,8 @@ public class Transaction
Optional.empty(),
sender,
chainId,
versionedHashes);
versionedHashes,
blobsWithCommitments);
}
public Transaction(
@ -254,7 +268,8 @@ public class Transaction
final SECPSignature signature,
final Bytes payload,
final Optional<BigInteger> chainId,
final Optional<List<Hash>> versionedHashes) {
final Optional<List<VersionedHash>> versionedHashes,
final Optional<BlobsWithCommitments> blobsWithCommitments) {
this(
TransactionType.FRONTIER,
nonce,
@ -270,7 +285,8 @@ public class Transaction
Optional.empty(),
null,
chainId,
versionedHashes);
versionedHashes,
blobsWithCommitments);
}
/**
@ -300,7 +316,7 @@ public class Transaction
final Bytes payload,
final Address sender,
final Optional<BigInteger> chainId,
final Optional<List<Hash>> versionedHashes) {
final Optional<List<VersionedHash>> versionedHashes) {
this(
nonce,
Optional.of(gasPrice),
@ -314,7 +330,55 @@ public class Transaction
payload,
sender,
chainId,
versionedHashes);
versionedHashes,
Optional.empty());
}
/**
* Instantiates a transaction instance.
*
* @param nonce the nonce
* @param gasPrice the gas price
* @param gasLimit the gas limit
* @param to the transaction recipient
* @param value the value being transferred to the recipient
* @param signature the signature
* @param payload the payload
* @param sender the transaction sender
* @param chainId the chain id to apply the transaction to
* <p>The {@code to} will be an {@code Optional.empty()} for a contract creation transaction;
* otherwise it should contain an address.
* <p>The {@code chainId} must be greater than 0 to be applied to a specific chain; otherwise
* it will default to any chain.
*/
public Transaction(
final long nonce,
final Wei gasPrice,
final long gasLimit,
final Optional<Address> to,
final Wei value,
final SECPSignature signature,
final Bytes payload,
final Address sender,
final Optional<BigInteger> chainId,
final Optional<Wei> maxFeePerDataGas,
final Optional<List<VersionedHash>> versionedHashes,
final Optional<BlobsWithCommitments> blobsWithCommitments) {
this(
nonce,
Optional.of(gasPrice),
Optional.empty(),
Optional.empty(),
maxFeePerDataGas,
gasLimit,
to,
value,
signature,
payload,
sender,
chainId,
versionedHashes,
blobsWithCommitments);
}
/**
@ -617,9 +681,15 @@ public class Transaction
final Bytes bytes = TransactionEncoder.encodeOpaqueBytes(this);
hash = Hash.hash(bytes);
final BytesValueRLPOutput rlpOutput = new BytesValueRLPOutput();
TransactionEncoder.encodeForWire(transactionType, bytes, rlpOutput);
size = rlpOutput.encodedSize();
if (transactionType.supportsBlob()) {
if (getBlobsWithCommitments().isPresent()) {
size = TransactionEncoder.encodeOpaqueBytes(this).size();
}
} else {
final BytesValueRLPOutput rlpOutput = new BytesValueRLPOutput();
TransactionEncoder.encodeForWire(transactionType, bytes, rlpOutput);
size = rlpOutput.encodedSize();
}
}
/**
@ -731,8 +801,12 @@ public class Transaction
return this.transactionType;
}
public Optional<List<Hash>> getVersionedHashes() {
return this.versionedHashes;
public Optional<List<VersionedHash>> getVersionedHashes() {
return versionedHashes;
}
public Optional<BlobsWithCommitments> getBlobsWithCommitments() {
return blobsWithCommitments;
}
/**
@ -758,7 +832,7 @@ public class Transaction
final Wei value,
final Bytes payload,
final Optional<List<AccessListEntry>> accessList,
final List<Hash> versionedHashes,
final List<VersionedHash> versionedHashes,
final Optional<BigInteger> chainId) {
if (transactionType.requiresChainId()) {
checkArgument(chainId.isPresent(), "Transaction type %s requires chainId", transactionType);
@ -907,7 +981,8 @@ public class Transaction
final Bytes payload,
final Optional<BigInteger> chainId,
final Optional<List<AccessListEntry>> accessList,
final List<Hash> versionedHashes) {
final List<VersionedHash> versionedHashes) {
final Bytes encoded =
RLP.encode(
rlpOutput -> {
@ -924,7 +999,7 @@ public class Transaction
accessList,
rlpOutput);
rlpOutput.writeUInt256Scalar(maxFeePerDataGas);
TransactionEncoder.writeBlobVersionedHashes(rlpOutput, versionedHashes);
BlobTransactionEncoder.writeBlobVersionedHashes(rlpOutput, versionedHashes);
rlpOutput.endList();
});
return Bytes.concatenate(Bytes.of(TransactionType.BLOB.getSerializedType()), encoded);
@ -989,7 +1064,11 @@ public class Transaction
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append(isContractCreation() ? "ContractCreation" : "MessageCall").append("{");
sb.append(
transactionType.supportsBlob()
? "Blob"
: isContractCreation() ? "ContractCreation" : "MessageCall")
.append("{");
sb.append("type=").append(getType()).append(", ");
sb.append("nonce=").append(getNonce()).append(", ");
getGasPrice()
@ -1021,7 +1100,11 @@ public class Transaction
public String toTraceLog() {
final StringBuilder sb = new StringBuilder();
sb.append(getHash()).append("={");
sb.append(isContractCreation() ? "ContractCreation" : "MessageCall").append(", ");
sb.append(
transactionType.supportsBlob()
? "Blob"
: isContractCreation() ? "ContractCreation" : "MessageCall")
.append(", ");
sb.append(getNonce()).append(", ");
sb.append(getSender()).append(", ");
sb.append(getType()).append(", ");
@ -1080,9 +1163,9 @@ public class Transaction
protected Address sender;
protected Optional<BigInteger> chainId = Optional.empty();
protected Optional<BigInteger> v = Optional.empty();
protected List<Hash> versionedHashes = null;
protected List<VersionedHash> versionedHashes = null;
private BlobsWithCommitments blobsWithCommitments;
public Builder type(final TransactionType transactionType) {
this.transactionType = transactionType;
@ -1162,7 +1245,7 @@ public class Transaction
return this;
}
public Builder versionedHashes(final List<Hash> versionedHashes) {
public Builder versionedHashes(final List<VersionedHash> versionedHashes) {
this.versionedHashes = versionedHashes;
return this;
}
@ -1201,7 +1284,8 @@ public class Transaction
accessList,
sender,
chainId,
Optional.ofNullable(versionedHashes));
Optional.ofNullable(versionedHashes),
Optional.ofNullable(blobsWithCommitments));
}
public Transaction signAndBuild(final KeyPair keys) {
@ -1231,5 +1315,20 @@ public class Transaction
chainId),
keys);
}
public Builder kzgBlobs(
final List<KZGCommitment> kzgCommitments,
final List<Blob> blobs,
final List<KZGProof> kzgProofs) {
if (this.versionedHashes == null || this.versionedHashes.isEmpty()) {
this.versionedHashes =
kzgCommitments.stream()
.map(c -> new VersionedHash(SHA256_VERSION_ID, Sha256Hash.hash(c.getData())))
.collect(Collectors.toList());
}
this.blobsWithCommitments =
new BlobsWithCommitments(kzgCommitments, blobs, kzgProofs, versionedHashes);
return this;
}
}
}

@ -0,0 +1,108 @@
/*
* 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.core.encoding;
import org.hyperledger.besu.crypto.SignatureAlgorithm;
import org.hyperledger.besu.crypto.SignatureAlgorithmFactory;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Blob;
import org.hyperledger.besu.datatypes.KZGCommitment;
import org.hyperledger.besu.datatypes.KZGProof;
import org.hyperledger.besu.datatypes.TransactionType;
import org.hyperledger.besu.datatypes.VersionedHash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.rlp.RLPInput;
import org.hyperledger.besu.evm.AccessListEntry;
import java.util.List;
import java.util.function.Supplier;
import com.google.common.base.Suppliers;
public class BlobTransactionDecoder {
private static final Supplier<SignatureAlgorithm> SIGNATURE_ALGORITHM =
Suppliers.memoize(SignatureAlgorithmFactory::getInstance);
static Transaction decode(final RLPInput input) {
Transaction transaction;
input.enterList();
// BlobTransactionNetworkWrapper
if (input.nextIsList()) {
transaction = readNetworkWrapperInner(input);
} else {
transaction = readTransactionPayload(input);
}
input.leaveList();
return transaction;
}
private static Transaction readTransactionPayload(final RLPInput input) {
final Transaction.Builder builder = Transaction.builder();
readTransactionPayloadInner(builder, input);
return builder.build();
}
private static void readTransactionPayloadInner(
final Transaction.Builder builder, final RLPInput input) {
builder
.type(TransactionType.BLOB)
.chainId(input.readBigIntegerScalar())
.nonce(input.readLongScalar())
.maxPriorityFeePerGas(Wei.of(input.readUInt256Scalar()))
.maxFeePerGas(Wei.of(input.readUInt256Scalar()))
.gasLimit(input.readLongScalar())
.to(input.readBytes(v -> v.size() == 0 ? null : Address.wrap(v)))
.value(Wei.of(input.readUInt256Scalar()))
.payload(input.readBytes())
.accessList(
input.readList(
accessListEntryRLPInput -> {
accessListEntryRLPInput.enterList();
final AccessListEntry accessListEntry =
new AccessListEntry(
Address.wrap(accessListEntryRLPInput.readBytes()),
accessListEntryRLPInput.readList(RLPInput::readBytes32));
accessListEntryRLPInput.leaveList();
return accessListEntry;
}))
.maxFeePerDataGas(Wei.of(input.readUInt256Scalar()))
.versionedHashes(
input.readList(versionedHashes -> new VersionedHash(versionedHashes.readBytes32())));
final byte recId = (byte) input.readIntScalar();
builder.signature(
SIGNATURE_ALGORITHM
.get()
.createSignature(
input.readUInt256Scalar().toUnsignedBigInteger(),
input.readUInt256Scalar().toUnsignedBigInteger(),
recId));
}
private static Transaction readNetworkWrapperInner(final RLPInput input) {
final Transaction.Builder builder = Transaction.builder();
input.enterList();
readTransactionPayloadInner(builder, input);
input.leaveList();
List<Blob> blobs = input.readList(Blob::readFrom);
List<KZGCommitment> commitments = input.readList(KZGCommitment::readFrom);
List<KZGProof> proofs = input.readList(KZGProof::readFrom);
builder.kzgBlobs(commitments, blobs, proofs);
return builder.build();
}
}

@ -0,0 +1,87 @@
/*
* 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.core.encoding;
import static org.slf4j.LoggerFactory.getLogger;
import org.hyperledger.besu.datatypes.Blob;
import org.hyperledger.besu.datatypes.KZGCommitment;
import org.hyperledger.besu.datatypes.KZGProof;
import org.hyperledger.besu.datatypes.VersionedHash;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.rlp.RLP;
import org.hyperledger.besu.ethereum.rlp.RLPOutput;
import java.util.List;
import org.apache.tuweni.bytes.Bytes;
import org.slf4j.Logger;
public class BlobTransactionEncoder {
private static final Logger LOG = getLogger(BlobTransactionEncoder.class);
public static void encodeEIP4844(final Transaction transaction, final RLPOutput out) {
out.startList();
out.writeBigIntegerScalar(transaction.getChainId().orElseThrow());
out.writeLongScalar(transaction.getNonce());
out.writeUInt256Scalar(transaction.getMaxPriorityFeePerGas().orElseThrow());
out.writeUInt256Scalar(transaction.getMaxFeePerGas().orElseThrow());
out.writeLongScalar(transaction.getGasLimit());
out.writeBytes(transaction.getTo().map(Bytes::copy).orElse(Bytes.EMPTY));
out.writeUInt256Scalar(transaction.getValue());
out.writeBytes(transaction.getPayload());
TransactionEncoder.writeAccessList(out, transaction.getAccessList());
out.writeUInt256Scalar(transaction.getMaxFeePerDataGas().orElseThrow());
out.startList();
transaction
.getVersionedHashes()
.get()
.forEach(
vh -> {
out.writeBytes(vh.toBytes());
});
out.endList();
TransactionEncoder.writeSignatureAndRecoveryId(transaction, out);
out.endList();
}
private static void encodeEIP4844Network(final Transaction transaction, final RLPOutput out) {
LOG.trace("Encoding transaction with blobs {}", transaction);
out.startList();
var blobsWithCommitments = transaction.getBlobsWithCommitments().orElseThrow();
encodeEIP4844(transaction, out);
out.writeList(blobsWithCommitments.getBlobs(), Blob::writeTo);
out.writeList(blobsWithCommitments.getKzgCommitments(), KZGCommitment::writeTo);
out.writeList(blobsWithCommitments.getKzgProofs(), KZGProof::writeTo);
out.endList();
}
public static void encodeForWireNetwork(
final Transaction transaction, final RLPOutput rlpOutput) {
rlpOutput.writeBytes(encodeOpaqueBytesNetwork(transaction));
}
private static Bytes encodeOpaqueBytesNetwork(final Transaction transaction) {
return Bytes.concatenate(
Bytes.of(transaction.getType().getSerializedType()),
RLP.encode(rlpOutput -> encodeEIP4844Network(transaction, rlpOutput)));
}
public static void writeBlobVersionedHashes(
final RLPOutput rlpOutput, final List<VersionedHash> versionedHashes) {
rlpOutput.writeList(versionedHashes, (h, out) -> out.writeBytes(h.toBytes()));
}
}

@ -52,7 +52,9 @@ public class TransactionDecoder {
TransactionType.ACCESS_LIST,
TransactionDecoder::decodeAccessList,
TransactionType.EIP1559,
TransactionDecoder::decodeEIP1559);
TransactionDecoder::decodeEIP1559,
TransactionType.BLOB,
BlobTransactionDecoder::decode);
private static final Supplier<SignatureAlgorithm> SIGNATURE_ALGORITHM =
Suppliers.memoize(SignatureAlgorithmFactory::getInstance);

@ -17,7 +17,6 @@ package org.hyperledger.besu.ethereum.core.encoding;
import static com.google.common.base.Preconditions.checkNotNull;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.TransactionType;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.core.Transaction;
@ -44,7 +43,9 @@ public class TransactionEncoder {
TransactionType.ACCESS_LIST,
TransactionEncoder::encodeAccessList,
TransactionType.EIP1559,
TransactionEncoder::encodeEIP1559);
TransactionEncoder::encodeEIP1559,
TransactionType.BLOB,
BlobTransactionEncoder::encodeEIP4844);
public static void encodeForWire(final Transaction transaction, final RLPOutput rlpOutput) {
final TransactionType transactionType =
@ -185,17 +186,12 @@ public class TransactionEncoder {
}
}
public static void writeBlobVersionedHashes(
final RLPOutput rlpOutput, final List<Hash> versionedHashes) {
// ToDo 4844: implement
}
private static void writeSignatureAndV(final Transaction transaction, final RLPOutput out) {
out.writeBigIntegerScalar(transaction.getV());
writeSignature(transaction, out);
}
private static void writeSignatureAndRecoveryId(
public static void writeSignatureAndRecoveryId(
final Transaction transaction, final RLPOutput out) {
out.writeIntScalar(transaction.getSignature().getRecId());
writeSignature(transaction, out);

@ -1,5 +1,5 @@
/*
* Copyright ConsenSys AG.
* 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
@ -14,9 +14,11 @@
*/
package org.hyperledger.besu.ethereum.core.feemarket;
import org.hyperledger.besu.datatypes.DataGas;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.core.Transaction;
import java.math.BigInteger;
import java.util.Optional;
@FunctionalInterface
@ -42,4 +44,36 @@ public interface TransactionPriceCalculator {
return price;
};
}
// curiously named as in the spec
// https://eips.ethereum.org/EIPS/eip-4844#cryptographic-helpers
private static BigInteger fakeExponential(
final BigInteger factor, final BigInteger numerator, final BigInteger denominator) {
int i = 1;
BigInteger output = BigInteger.ZERO;
BigInteger numeratorAccumulator = factor.multiply(denominator);
while (numeratorAccumulator.signum() > 0) {
output = output.add(numeratorAccumulator);
numeratorAccumulator =
(numeratorAccumulator.multiply(numerator))
.divide(denominator.multiply(BigInteger.valueOf(i)));
++i;
}
return output.divide(denominator);
}
static TransactionPriceCalculator dataGas(
final int minDataGasPrice,
final int dataGasPriceUpdateFraction,
final DataGas excessDataGas) {
return ((transaction, baseFee) -> {
final var dataGasPrice =
Wei.of(
fakeExponential(
BigInteger.valueOf(minDataGasPrice),
excessDataGas.toBigInteger(),
BigInteger.valueOf(dataGasPriceUpdateFraction)));
return dataGasPrice;
});
}
}

@ -14,8 +14,9 @@
*/
package org.hyperledger.besu.ethereum.mainnet;
import static org.hyperledger.besu.ethereum.mainnet.feemarket.ExcessDataGasCalculator.calculateExcessDataGasForParent;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.DataGas;
import org.hyperledger.besu.datatypes.TransactionType;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.BlockProcessingOutputs;
@ -112,14 +113,19 @@ public abstract class AbstractBlockProcessor implements BlockProcessor {
final BlockHashLookup blockHashLookup = new CachingBlockHashLookup(blockHeader, blockchain);
final Address miningBeneficiary =
miningBeneficiaryCalculator.calculateBeneficiary(blockHeader);
final Wei dataGasPrice =
protocolSpec
.getFeeMarket()
.dataPrice(
blockchain
.getBlockHeader(blockHeader.getParentHash())
.flatMap(BlockHeader::getExcessDataGas)
.orElse(DataGas.ZERO));
Optional<BlockHeader> maybeParentHeader =
blockchain.getBlockHeader(blockHeader.getParentHash());
Wei dataGasPrice =
maybeParentHeader
.map(
(parentHeader) ->
protocolSpec
.getFeeMarket()
.dataPricePerGas(
calculateExcessDataGasForParent(protocolSpec, parentHeader)))
.orElse(Wei.ZERO);
final TransactionProcessingResult result =
transactionProcessor.processTransaction(

@ -17,7 +17,7 @@ package org.hyperledger.besu.ethereum.mainnet;
import org.hyperledger.besu.ethereum.mainnet.feemarket.BaseFeeMarket;
public class CancunTargetingGasLimitCalculator extends LondonTargetingGasLimitCalculator {
private static final long MAX_DATA_GAS_PER_BLOCK = 1 << 19;
private static final long MAX_DATA_GAS_PER_BLOCK = 786432L;
public CancunTargetingGasLimitCalculator(
final long londonForkBlock, final BaseFeeMarket feeMarket) {

@ -40,7 +40,7 @@ public class DefaultProtocolSchedule implements ProtocolSchedule {
@VisibleForTesting
protected NavigableSet<ScheduledProtocolSpec> protocolSpecs =
new TreeSet<>(Comparator.comparing(ScheduledProtocolSpec::milestone).reversed());
new TreeSet<>(Comparator.comparing(ScheduledProtocolSpec::fork).reversed());
private final Optional<BigInteger> chainId;
@ -54,12 +54,23 @@ public class DefaultProtocolSchedule implements ProtocolSchedule {
this.protocolSpecs = protocolSchedule.protocolSpecs;
}
public ScheduledProtocolSpec specScheduledForBlock(final ProcessableBlockHeader blockHeader) {
return protocolSpecs.stream()
.filter(s -> s.isOnOrAfterMilestoneBoundary(blockHeader))
.findFirst()
.orElseThrow(
() ->
new IllegalStateException(
"No protocol spec found for block " + blockHeader.getNumber()));
}
@Override
public ProtocolSpec getByBlockHeader(final ProcessableBlockHeader blockHeader) {
checkArgument(
!protocolSpecs.isEmpty(), "At least 1 milestone must be provided to the protocol schedule");
checkArgument(
protocolSpecs.last().milestone() == 0, "There must be a milestone starting from block 0");
protocolSpecs.last().fork().milestone() == 0,
"There must be a milestone starting from block 0");
// protocolSpecs is sorted in descending block order, so the first one we find that's lower than
// the requested level will be the most appropriate spec
@ -79,8 +90,8 @@ public class DefaultProtocolSchedule implements ProtocolSchedule {
@Override
public String listMilestones() {
return protocolSpecs.stream()
.sorted(Comparator.comparing(ScheduledProtocolSpec::milestone))
.map(scheduledSpec -> scheduledSpec.spec().getName() + ": " + scheduledSpec.milestone())
.sorted(Comparator.comparing(ScheduledProtocolSpec::fork))
.map(scheduledSpec -> scheduledSpec.fork().toString())
.collect(Collectors.joining(", ", "[", "]"));
}
@ -110,6 +121,15 @@ public class DefaultProtocolSchedule implements ProtocolSchedule {
return this.protocolSpecs.stream().anyMatch(predicate);
}
@Override
public Optional<ScheduledProtocolSpec.Hardfork> hardforkFor(
final Predicate<ScheduledProtocolSpec> predicate) {
return this.protocolSpecs.stream()
.filter(predicate)
.findFirst()
.map(ScheduledProtocolSpec::fork);
}
@Override
public void setPermissionTransactionFilter(
final PermissionTransactionFilter permissionTransactionFilter) {

@ -23,6 +23,7 @@ import org.hyperledger.besu.ethereum.mainnet.headervalidationrules.BaseFeeMarket
import org.hyperledger.besu.ethereum.mainnet.headervalidationrules.CalculatedDifficultyValidationRule;
import org.hyperledger.besu.ethereum.mainnet.headervalidationrules.ConstantFieldValidationRule;
import org.hyperledger.besu.ethereum.mainnet.headervalidationrules.ConstantOmmersHashRule;
import org.hyperledger.besu.ethereum.mainnet.headervalidationrules.DataGasValidationRule;
import org.hyperledger.besu.ethereum.mainnet.headervalidationrules.ExtraDataMaxLengthValidationRule;
import org.hyperledger.besu.ethereum.mainnet.headervalidationrules.GasLimitRangeAndDeltaValidationRule;
import org.hyperledger.besu.ethereum.mainnet.headervalidationrules.GasUsageValidationRule;
@ -32,6 +33,7 @@ import org.hyperledger.besu.ethereum.mainnet.headervalidationrules.NoNonceRule;
import org.hyperledger.besu.ethereum.mainnet.headervalidationrules.ProofOfWorkValidationRule;
import org.hyperledger.besu.ethereum.mainnet.headervalidationrules.TimestampBoundedByFutureParameter;
import org.hyperledger.besu.ethereum.mainnet.headervalidationrules.TimestampMoreRecentThanParent;
import org.hyperledger.besu.evm.gascalculator.CancunGasCalculator;
import java.util.Optional;
@ -195,4 +197,9 @@ public final class MainnetBlockHeaderValidator {
.addRule(new NoDifficultyRule())
.addRule(new IncrementalTimestampRule());
}
public static BlockHeaderValidator.Builder cancunBlockHeaderValidator(final FeeMarket feeMarket) {
return mergeBlockHeaderValidator(feeMarket)
.addRule(new DataGasValidationRule(new CancunGasCalculator()));
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save