From 698ec4204f51b19e00e5da6f9401c3f46cf5909c Mon Sep 17 00:00:00 2001 From: Sally MacFarlane Date: Tue, 5 Mar 2024 16:42:04 +1000 Subject: [PATCH] RC4 - eth_call and eth_feeHistory add blob fields (#6681) * Add blobs to `eth_feeHistory` (#6679) Signed-off-by: Gabriel-Trintinalia * merge changelog Signed-off-by: Sally MacFarlane * merge Signed-off-by: Sally MacFarlane --------- Signed-off-by: Gabriel-Trintinalia Signed-off-by: Sally MacFarlane Co-authored-by: Gabriel-Trintinalia --- CHANGELOG.md | 2 + .../fork/frontier/EthCallIntegrationTest.java | 26 +++++ .../EthCreateAccessListIntegrationTest.java | 16 ++- .../EthEstimateGasIntegrationTest.java | 14 ++- .../fork/london/EthCallIntegrationTest.java | 18 +++ .../london/EthEstimateGasIntegrationTest.java | 12 +- .../api/jsonrpc/JsonRpcErrorConverter.java | 2 + .../internal/methods/EthFeeHistory.java | 73 ++++++++++++- .../parameters/JsonCallParameter.java | 9 +- .../internal/response/RpcErrorType.java | 2 + .../jsonrpc/internal/results/FeeHistory.java | 12 ++ .../jsonrpc/RpcErrorTypeConverterTest.java | 8 +- .../jsonrpc/internal/methods/EthCallTest.java | 4 +- .../methods/EthCreateAccessListTest.java | 6 +- .../internal/methods/EthEstimateGasTest.java | 4 + .../internal/methods/EthFeeHistoryTest.java | 102 ++++++++++++----- .../privacy/methods/priv/PrivCallTest.java | 6 + .../api/jsonrpc/eth/eth_call_blob.json | 23 ++++ ...th_call_blob_missing_maxFeePerBlobGas.json | 22 ++++ .../eth/eth_call_blob_too_many_blobs.json | 23 ++++ .../jsonrpc/eth/eth_call_blob_zero_fee.json | 23 ++++ .../eth_feeHistory_fieldNamesWithReward.json | 2 + ...th_feeHistory_fieldNamesWithoutReward.json | 4 +- .../ethereum/transaction/CallParameter.java | 61 ++++++++++- .../transaction/TransactionSimulator.java | 4 + .../transaction/TransactionSimulatorTest.java | 103 ++++++++++++++++++ 26 files changed, 536 insertions(+), 45 deletions(-) create mode 100644 ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_blob.json create mode 100644 ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_blob_missing_maxFeePerBlobGas.json create mode 100644 ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_blob_too_many_blobs.json create mode 100644 ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_blob_zero_fee.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b64570bfcd..71e168c7380 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ - `--Xfilter-on-enr-fork-id` has been removed. To disable the feature use `--filter-on-enr-fork-id=false`. - `--engine-jwt-enabled` has been removed. Use `--engine-jwt-disabled` instead. [#6491](https://github.com/hyperledger/besu/pull/6491) - Release docker images now provided at ghcr.io instead of dockerhub +- Add blob transaction support to `eth_call` [#6661](https://github.com/hyperledger/besu/pull/6661) +- Add blobs to `eth_feeHistory` [#6679](https://github.com/hyperledger/besu/pull/6679) ### Deprecations - X_SNAP and X_CHECKPOINT are marked for deprecation and will be removed in 24.4.0 in favor of SNAP and CHECKPOINT [#6405](https://github.com/hyperledger/besu/pull/6405) diff --git a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthCallIntegrationTest.java b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthCallIntegrationTest.java index f80fa0c3a7a..5fa1c70ae16 100644 --- a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthCallIntegrationTest.java +++ b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthCallIntegrationTest.java @@ -75,6 +75,8 @@ public void shouldReturnExpectedResultForCallAtLatestBlock() { Bytes.fromHexString("0x12a7b914"), null, null, + null, + null, null); final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); final JsonRpcResponse expectedResponse = @@ -100,6 +102,8 @@ public void shouldReturnExpectedResultForCallAtSpecificBlock() { Bytes.fromHexString("0x12a7b914"), null, null, + null, + null, null); final JsonRpcRequestContext request = requestWithParams(callParameter, "0x8"); final JsonRpcResponse expectedResponse = @@ -126,6 +130,8 @@ public void shouldReturnSuccessWhenCreatingContract() { "0x608060405234801561001057600080fd5b50610157806100206000396000f30060806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633bdab8bf146100515780639ae97baa14610068575b600080fd5b34801561005d57600080fd5b5061006661007f565b005b34801561007457600080fd5b5061007d6100b9565b005b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60016040518082815260200191505060405180910390a1565b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60026040518082815260200191505060405180910390a17fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60036040518082815260200191505060405180910390a15600a165627a7a7230582010ddaa52e73a98c06dbcd22b234b97206c1d7ed64a7c048e10c2043a3d2309cb0029"), null, null, + null, + null, null); final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); final JsonRpcResponse expectedResponse = @@ -152,6 +158,8 @@ public void shouldReturnErrorWithGasLimitTooLow() { Bytes.fromHexString("0x12a7b914"), null, null, + null, + null, null); final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); final JsonRpcResponse expectedResponse = @@ -176,6 +184,8 @@ public void shouldReturnErrorWithGasPriceTooHighAndStrict() { Bytes.fromHexString("0x12a7b914"), null, true, + null, + null, null); final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); final JsonRpcResponse expectedResponse = @@ -200,6 +210,8 @@ public void shouldReturnSuccessWithGasPriceTooHighNotStrict() { Bytes.fromHexString("0x12a7b914"), null, false, + null, + null, null); final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); final JsonRpcResponse expectedResponse = @@ -225,6 +237,8 @@ public void shouldReturnErrorWithGasPriceTooHigh() { Bytes.fromHexString("0x12a7b914"), null, null, + null, + null, null); final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); final JsonRpcResponse expectedResponse = @@ -249,6 +263,8 @@ public void shouldReturnSuccessWithValidGasPrice() { Bytes.fromHexString("0x12a7b914"), null, null, + null, + null, null); final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); final JsonRpcResponse expectedResponse = @@ -274,6 +290,8 @@ public void shouldReturnErrorWithGasPriceAndEmptyBalance() { Bytes.fromHexString("0x12a7b914"), null, null, + null, + null, null); final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); final JsonRpcResponse expectedResponse = @@ -298,6 +316,8 @@ public void shouldReturnSuccessWithZeroGasPriceAndEmptyBalance() { Bytes.fromHexString("0x12a7b914"), null, null, + null, + null, null); final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); final JsonRpcResponse expectedResponse = @@ -323,6 +343,8 @@ public void shouldReturnSuccessWithoutGasPriceAndEmptyBalance() { Bytes.fromHexString("0x12a7b914"), null, null, + null, + null, null); final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); final JsonRpcResponse expectedResponse = @@ -348,6 +370,8 @@ public void shouldReturnSuccessWithInvalidGasPricingAndEmptyBalance() { Bytes.fromHexString("0x12a7b914"), null, null, + null, + null, null); final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); final JsonRpcResponse expectedResponse = @@ -373,6 +397,8 @@ public void shouldReturnEmptyHashResultForCallWithOnlyToField() { null, null, null, + null, + null, null); final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, "0x"); diff --git a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthCreateAccessListIntegrationTest.java b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthCreateAccessListIntegrationTest.java index fe4dc6316ec..e68fd29d18b 100644 --- a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthCreateAccessListIntegrationTest.java +++ b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthCreateAccessListIntegrationTest.java @@ -140,7 +140,8 @@ public void shouldSucceedWhenCreateAccessListSimpleContract() { @Test public void shouldReturnExpectedValueForEmptyCallParameter() { final JsonCallParameter callParameter = - new JsonCallParameter(null, null, null, null, null, null, null, null, null, null, null); + new JsonCallParameter( + null, null, null, null, null, null, null, null, null, null, null, null, null); final JsonRpcRequestContext request = requestWithParams(callParameter); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, new CreateAccessListResult(new ArrayList<>(), 0xcf08)); @@ -164,6 +165,8 @@ public void shouldReturnExpectedValueForTransfer() { null, null, null, + null, + null, null); final JsonRpcRequestContext request = requestWithParams(callParameter); final JsonRpcResponse expectedResponse = @@ -189,6 +192,8 @@ public void shouldReturnExpectedValueForContractDeploy() { "0x608060405234801561001057600080fd5b50610157806100206000396000f30060806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633bdab8bf146100515780639ae97baa14610068575b600080fd5b34801561005d57600080fd5b5061006661007f565b005b34801561007457600080fd5b5061007d6100b9565b005b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60016040518082815260200191505060405180910390a1565b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60026040518082815260200191505060405180910390a17fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60036040518082815260200191505060405180910390a15600a165627a7a7230582010ddaa52e73a98c06dbcd22b234b97206c1d7ed64a7c048e10c2043a3d2309cb0029"), null, null, + null, + null, null); final JsonRpcRequestContext request = requestWithParams(callParameter); final JsonRpcResponse expectedResponse = @@ -214,6 +219,8 @@ public void shouldIgnoreSenderBalanceAccountWhenStrictModeDisabledAndReturnExpec "0x608060405234801561001057600080fd5b50610157806100206000396000f30060806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633bdab8bf146100515780639ae97baa14610068575b600080fd5b34801561005d57600080fd5b5061006661007f565b005b34801561007457600080fd5b5061007d6100b9565b005b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60016040518082815260200191505060405180910390a1565b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60026040518082815260200191505060405180910390a17fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60036040518082815260200191505060405180910390a15600a165627a7a7230582010ddaa52e73a98c06dbcd22b234b97206c1d7ed64a7c048e10c2043a3d2309cb0029"), null, false, + null, + null, null); final JsonRpcRequestContext request = requestWithParams(callParameter); final JsonRpcResponse expectedResponse = @@ -227,7 +234,8 @@ public void shouldIgnoreSenderBalanceAccountWhenStrictModeDisabledAndReturnExpec @Test public void shouldReturnExpectedValueForInsufficientGas() { final JsonCallParameter callParameter = - new JsonCallParameter(null, null, 1L, null, null, null, null, null, null, null, null); + new JsonCallParameter( + null, null, 1L, null, null, null, null, null, null, null, null, null, null); final JsonRpcRequestContext request = requestWithParams(callParameter); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, new CreateAccessListResult(new ArrayList<>(), 0xcf08)); @@ -262,7 +270,9 @@ private JsonCallParameter createAccessListJsonCallParameters( null, null, null, - accessList); + accessList, + null, + null); } private JsonRpcRequestContext requestWithParams(final Object... params) { diff --git a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthEstimateGasIntegrationTest.java b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthEstimateGasIntegrationTest.java index 8717fe05600..ca64e046fd1 100644 --- a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthEstimateGasIntegrationTest.java +++ b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthEstimateGasIntegrationTest.java @@ -65,7 +65,8 @@ public void setUp() { @Test public void shouldReturnExpectedValueForEmptyCallParameter() { final JsonCallParameter callParameter = - new JsonCallParameter(null, null, null, null, null, null, null, null, null, null, null); + new JsonCallParameter( + null, null, null, null, null, null, null, null, null, null, null, null, null); final JsonRpcRequestContext request = requestWithParams(callParameter); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, "0x5208"); @@ -88,6 +89,8 @@ public void shouldReturnExpectedValueForTransfer() { null, null, null, + null, + null, null); final JsonRpcRequestContext request = requestWithParams(callParameter); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, "0x5208"); @@ -112,6 +115,8 @@ public void shouldReturnExpectedValueForContractDeploy() { "0x608060405234801561001057600080fd5b50610157806100206000396000f30060806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633bdab8bf146100515780639ae97baa14610068575b600080fd5b34801561005d57600080fd5b5061006661007f565b005b34801561007457600080fd5b5061007d6100b9565b005b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60016040518082815260200191505060405180910390a1565b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60026040518082815260200191505060405180910390a17fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60036040518082815260200191505060405180910390a15600a165627a7a7230582010ddaa52e73a98c06dbcd22b234b97206c1d7ed64a7c048e10c2043a3d2309cb0029"), null, null, + null, + null, null); final JsonRpcRequestContext request = requestWithParams(callParameter); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, "0x1b551"); @@ -136,6 +141,8 @@ public void shouldIgnoreSenderBalanceAccountWhenStrictModeDisabledAndReturnExpec "0x608060405234801561001057600080fd5b50610157806100206000396000f30060806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633bdab8bf146100515780639ae97baa14610068575b600080fd5b34801561005d57600080fd5b5061006661007f565b005b34801561007457600080fd5b5061007d6100b9565b005b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60016040518082815260200191505060405180910390a1565b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60026040518082815260200191505060405180910390a17fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60036040518082815260200191505060405180910390a15600a165627a7a7230582010ddaa52e73a98c06dbcd22b234b97206c1d7ed64a7c048e10c2043a3d2309cb0029"), null, false, + null, + null, null); final JsonRpcRequestContext request = requestWithParams(callParameter); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, "0x1b551"); @@ -160,6 +167,8 @@ public void shouldNotIgnoreSenderBalanceAccountWhenStrictModeDisabledAndThrowErr "0x608060405234801561001057600080fd5b50610157806100206000396000f30060806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633bdab8bf146100515780639ae97baa14610068575b600080fd5b34801561005d57600080fd5b5061006661007f565b005b34801561007457600080fd5b5061007d6100b9565b005b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60016040518082815260200191505060405180910390a1565b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60026040518082815260200191505060405180910390a17fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60036040518082815260200191505060405180910390a15600a165627a7a7230582010ddaa52e73a98c06dbcd22b234b97206c1d7ed64a7c048e10c2043a3d2309cb0029"), null, true, + null, + null, null); final JsonRpcRequestContext request = requestWithParams(callParameter); @@ -176,7 +185,8 @@ public void shouldNotIgnoreSenderBalanceAccountWhenStrictModeDisabledAndThrowErr @Test public void shouldReturnExpectedValueForInsufficientGas() { final JsonCallParameter callParameter = - new JsonCallParameter(null, null, 1L, null, null, null, null, null, null, null, null); + new JsonCallParameter( + null, null, 1L, null, null, null, null, null, null, null, null, null, null); final JsonRpcRequestContext request = requestWithParams(callParameter); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, "0x5208"); diff --git a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/london/EthCallIntegrationTest.java b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/london/EthCallIntegrationTest.java index f72a452db9d..e92076123b2 100644 --- a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/london/EthCallIntegrationTest.java +++ b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/london/EthCallIntegrationTest.java @@ -75,6 +75,8 @@ public void shouldReturnSuccessWithoutGasPriceAndEmptyBalance() { Bytes.fromHexString("0x2e64cec1"), null, null, + null, + null, null); final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); final JsonRpcResponse expectedResponse = @@ -100,6 +102,8 @@ public void shouldReturnErrorWithGasPriceTooHigh() { Bytes.fromHexString("0x2e64cec1"), null, null, + null, + null, null); final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); final JsonRpcResponse expectedResponse = @@ -124,6 +128,8 @@ public void shouldReturnSuccessWithValidGasPrice() { Bytes.fromHexString("0x2e64cec1"), null, null, + null, + null, null); final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); final JsonRpcResponse expectedResponse = @@ -149,6 +155,8 @@ public void shouldReturnErrorWithGasPriceLessThanCurrentBaseFee() { Bytes.fromHexString("0x2e64cec1"), null, null, + null, + null, null); final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); final JsonRpcResponse expectedResponse = @@ -173,6 +181,8 @@ public void shouldReturnSuccessWithValidMaxFeePerGas() { Bytes.fromHexString("0x2e64cec1"), null, null, + null, + null, null); final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); final JsonRpcResponse expectedResponse = @@ -198,6 +208,8 @@ public void shouldReturnSuccessWithValidMaxFeePerGasAndMaxPriorityFeePerGas() { Bytes.fromHexString("0x2e64cec1"), null, null, + null, + null, null); final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); final JsonRpcResponse expectedResponse = @@ -223,6 +235,8 @@ public void shouldReturnErrorWithValidMaxFeePerGasLessThanCurrentBaseFee() { Bytes.fromHexString("0x2e64cec1"), null, null, + null, + null, null); final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); final JsonRpcResponse expectedResponse = @@ -247,6 +261,8 @@ public void shouldReturnErrorWithValidMaxFeePerGasLessThanMaxPriorityFeePerGas() Bytes.fromHexString("0x2e64cec1"), null, null, + null, + null, null); final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); final JsonRpcResponse expectedResponse = @@ -272,6 +288,8 @@ public void shouldReturnErrorWithMaxFeePerGasAndEmptyBalance() { Bytes.fromHexString("0x2e64cec1"), null, null, + null, + null, null); final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); final JsonRpcResponse expectedResponse = diff --git a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/london/EthEstimateGasIntegrationTest.java b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/london/EthEstimateGasIntegrationTest.java index e34466a644a..0a3ba1be7fe 100644 --- a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/london/EthEstimateGasIntegrationTest.java +++ b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/london/EthEstimateGasIntegrationTest.java @@ -78,6 +78,8 @@ public void shouldReturnExpectedValueForTransfer() { null, null, null, + null, + null, null); final JsonRpcResponse response = method.response(requestWithParams(callParameter)); @@ -100,7 +102,9 @@ public void shouldReturnExpectedValueForTransfer_WithAccessList() { null, null, null, - createAccessList()); + createAccessList(), + null, + null); final JsonRpcResponse response = method.response(requestWithParams(callParameter)); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, "0x62d4"); @@ -122,6 +126,8 @@ public void shouldReturnExpectedValueForContractDeploy() { "0x608060405234801561001057600080fd5b50610157806100206000396000f30060806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633bdab8bf146100515780639ae97baa14610068575b600080fd5b34801561005d57600080fd5b5061006661007f565b005b34801561007457600080fd5b5061007d6100b9565b005b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60016040518082815260200191505060405180910390a1565b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60026040518082815260200191505060405180910390a17fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60036040518082815260200191505060405180910390a15600a165627a7a7230582010ddaa52e73a98c06dbcd22b234b97206c1d7ed64a7c048e10c2043a3d2309cb0029"), null, null, + null, + null, null); final JsonRpcResponse response = method.response(requestWithParams(callParameter)); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, "0x1f081"); @@ -143,7 +149,9 @@ public void shouldReturnExpectedValueForContractDeploy_WithAccessList() { "0x608060405234801561001057600080fd5b50610157806100206000396000f30060806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633bdab8bf146100515780639ae97baa14610068575b600080fd5b34801561005d57600080fd5b5061006661007f565b005b34801561007457600080fd5b5061007d6100b9565b005b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60016040518082815260200191505060405180910390a1565b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60026040518082815260200191505060405180910390a17fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60036040518082815260200191505060405180910390a15600a165627a7a7230582010ddaa52e73a98c06dbcd22b234b97206c1d7ed64a7c048e10c2043a3d2309cb0029"), null, null, - createAccessList()); + createAccessList(), + null, + null); final JsonRpcResponse response = method.response(requestWithParams(callParameter)); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, "0x2014d"); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcErrorConverter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcErrorConverter.java index 202759c18e7..8a17f101c21 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcErrorConverter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcErrorConverter.java @@ -79,6 +79,8 @@ public static RpcErrorType convertTransactionInvalidReason( return RpcErrorType.PLUGIN_TX_VALIDATOR; case INVALID_BLOBS: return RpcErrorType.INVALID_BLOBS; + case BLOB_GAS_PRICE_BELOW_CURRENT_BLOB_BASE_FEE: + return RpcErrorType.BLOB_GAS_PRICE_BELOW_CURRENT_BLOB_BASE_FEE; case EXECUTION_HALTED: return RpcErrorType.EXECUTION_HALTED; default: diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthFeeHistory.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthFeeHistory.java index 4455bd30b55..6bea493d312 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthFeeHistory.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthFeeHistory.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; import static java.util.stream.Collectors.toUnmodifiableList; +import static org.hyperledger.besu.ethereum.mainnet.feemarket.ExcessBlobGasCalculator.calculateExcessBlobGasForParent; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; @@ -36,14 +37,17 @@ import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.mainnet.feemarket.BaseFeeMarket; import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; import java.util.stream.LongStream; import java.util.stream.Stream; @@ -104,15 +108,23 @@ public JsonRpcResponse response(final JsonRpcRequestContext request) { final List blockHeaderRange = getBlockHeaders(firstBlock, lastBlock); final List requestedBaseFees = getBaseFees(blockHeaderRange); + final List requestedBlobBaseFees = getBlobBaseFees(blockHeaderRange); final Wei nextBaseFee = getNextBaseFee(highestBlockNumber, chainHeadHeader, requestedBaseFees, blockHeaderRange); final List gasUsedRatios = getGasUsedRatios(blockHeaderRange); + final List blobGasUsedRatios = getBlobGasUsedRatios(blockHeaderRange); final Optional>> maybeRewards = maybeRewardPercentiles.map(rewards -> getRewards(rewards, blockHeaderRange)); return new JsonRpcSuccessResponse( requestId, createFeeHistoryResult( - firstBlock, requestedBaseFees, nextBaseFee, gasUsedRatios, maybeRewards)); + firstBlock, + requestedBaseFees, + requestedBlobBaseFees, + nextBaseFee, + gasUsedRatios, + blobGasUsedRatios, + maybeRewards)); } private Wei getNextBaseFee( @@ -326,23 +338,78 @@ private List getBlockHeaders(final long oldestBlock, final long las } private List getBaseFees(final List blockHeaders) { - // we return the base fees for the blocks requested and 1 more because we can always compute it return blockHeaders.stream() .map(blockHeader -> blockHeader.getBaseFee().orElse(Wei.ZERO)) .toList(); } + private List getBlobBaseFees(final List blockHeaders) { + if (blockHeaders.isEmpty()) { + return Collections.emptyList(); + } + // Calculate the BlobFee for the requested range + List baseFeesPerBlobGas = + blockHeaders.stream().map(this::getBlobGasFee).collect(Collectors.toList()); + + // Calculate the next blob base fee and add it to the list + Wei nextBlobBaseFee = getNextBlobFee(blockHeaders.get(blockHeaders.size() - 1)); + baseFeesPerBlobGas.add(nextBlobBaseFee); + + return baseFeesPerBlobGas; + } + + private Wei getBlobGasFee(final BlockHeader header) { + return blockchain + .getBlockHeader(header.getParentHash()) + .map(parent -> getBlobGasFee(protocolSchedule.getByBlockHeader(header), parent)) + .orElse(Wei.ZERO); + } + + private Wei getBlobGasFee(final ProtocolSpec spec, final BlockHeader parent) { + return spec.getFeeMarket().blobGasPricePerGas(calculateExcessBlobGasForParent(spec, parent)); + } + + private Wei getNextBlobFee(final BlockHeader header) { + // Attempt to retrieve the next header based on the current header's number. + long nextBlockNumber = header.getNumber() + 1; + return blockchain + .getBlockHeader(nextBlockNumber) + .map(nextHeader -> getBlobGasFee(protocolSchedule.getByBlockHeader(nextHeader), header)) + // If the next header is not present, calculate the fee using the current time. + .orElseGet( + () -> + getBlobGasFee( + protocolSchedule.getForNextBlockHeader(header, System.currentTimeMillis()), + header)); + } + private List getGasUsedRatios(final List blockHeaders) { return blockHeaders.stream() .map(blockHeader -> blockHeader.getGasUsed() / (double) blockHeader.getGasLimit()) .toList(); } + private List getBlobGasUsedRatios(final List blockHeaders) { + return blockHeaders.stream().map(this::calculateBlobGasUsedRatio).toList(); + } + + private double calculateBlobGasUsedRatio(final BlockHeader blockHeader) { + ProtocolSpec spec = protocolSchedule.getByBlockHeader(blockHeader); + long blobGasUsed = blockHeader.getBlobGasUsed().orElse(0L); + double currentBlobGasLimit = spec.getGasLimitCalculator().currentBlobGasLimit(); + if (currentBlobGasLimit == 0) { + return 0; + } + return blobGasUsed / currentBlobGasLimit; + } + private FeeHistory.FeeHistoryResult createFeeHistoryResult( final long oldestBlock, final List explicitlyRequestedBaseFees, + final List requestedBlobBaseFees, final Wei nextBaseFee, final List gasUsedRatios, + final List blobGasUsedRatio, final Optional>> maybeRewards) { return FeeHistory.FeeHistoryResult.from( ImmutableFeeHistory.builder() @@ -350,7 +417,9 @@ private FeeHistory.FeeHistoryResult createFeeHistoryResult( .baseFeePerGas( Stream.concat(explicitlyRequestedBaseFees.stream(), Stream.of(nextBaseFee)) .collect(toUnmodifiableList())) + .baseFeePerBlobGas(requestedBlobBaseFees) .gasUsedRatio(gasUsedRatios) + .blobGasUsedRatio(blobGasUsedRatio) .reward(maybeRewards) .build()); } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/JsonCallParameter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/JsonCallParameter.java index 4141278c883..dae98e6896a 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/JsonCallParameter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/JsonCallParameter.java @@ -16,6 +16,7 @@ import org.hyperledger.besu.datatypes.AccessListEntry; import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.VersionedHash; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.core.json.HexLongDeserializer; import org.hyperledger.besu.ethereum.core.json.HexStringDeserializer; @@ -53,7 +54,9 @@ public JsonCallParameter( @JsonDeserialize(using = HexStringDeserializer.class) @JsonProperty("input") final Bytes input, @JsonProperty("strict") final Boolean strict, - @JsonProperty("accessList") final List accessList) { + @JsonProperty("accessList") final List accessList, + @JsonProperty("maxFeePerBlobGas") final Wei maxFeePerBlobGas, + @JsonProperty("blobVersionedHashes") final List blobVersionedHashes) { super( from, @@ -64,7 +67,9 @@ public JsonCallParameter( Optional.ofNullable(maxFeePerGas), value, Optional.ofNullable(input != null ? input : data).orElse(null), - Optional.ofNullable(accessList)); + Optional.ofNullable(accessList), + Optional.ofNullable(maxFeePerBlobGas), + Optional.ofNullable(blobVersionedHashes)); if (input != null && data != null) { throw new IllegalArgumentException("Only one of 'input' or 'data' should be provided"); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/RpcErrorType.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/RpcErrorType.java index 7e49ed9387e..17d6d229fb3 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/RpcErrorType.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/RpcErrorType.java @@ -61,6 +61,8 @@ public enum RpcErrorType { CHAIN_HEAD_WORLD_STATE_NOT_AVAILABLE(-32008, "Initial sync is still in progress"), GAS_PRICE_TOO_LOW(-32009, "Gas price below configured minimum gas price"), GAS_PRICE_BELOW_CURRENT_BASE_FEE(-32009, "Gas price below current base fee"), + + BLOB_GAS_PRICE_BELOW_CURRENT_BLOB_BASE_FEE(-32009, "blob gas price below current blob base fee"), WRONG_CHAIN_ID(-32000, "Wrong chainId"), REPLAY_PROTECTED_SIGNATURES_NOT_SUPPORTED(-32000, "ChainId not supported"), REPLAY_PROTECTED_SIGNATURE_REQUIRED(-32000, "ChainId is required"), diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/FeeHistory.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/FeeHistory.java index 08b130c3f5c..0985479ea5f 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/FeeHistory.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/FeeHistory.java @@ -33,8 +33,12 @@ public interface FeeHistory { List getBaseFeePerGas(); + List getBaseFeePerBlobGas(); + List getGasUsedRatio(); + List getBlobGasUsedRatio(); + Optional>> getReward(); @Value.Immutable @@ -47,9 +51,15 @@ interface FeeHistoryResult { @JsonProperty("baseFeePerGas") List getBaseFeePerGas(); + @JsonProperty("baseFeePerBlobGas") + List getBaseFeePerBlobGas(); + @JsonProperty("gasUsedRatio") List getGasUsedRatio(); + @JsonProperty("blobGasUsedRatio") + List getBlobGasUsedRatio(); + @Nullable @JsonProperty("reward") List> getReward(); @@ -60,7 +70,9 @@ static FeeHistoryResult from(final FeeHistory feeHistory) { feeHistory.getBaseFeePerGas().stream() .map(Quantity::create) .collect(toUnmodifiableList()), + feeHistory.getBaseFeePerBlobGas().stream().map(Quantity::create).toList(), feeHistory.getGasUsedRatio(), + feeHistory.getBlobGasUsedRatio(), feeHistory .getReward() .map( diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcErrorTypeConverterTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcErrorTypeConverterTest.java index 6a52b39c853..8dbee4bf91d 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcErrorTypeConverterTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcErrorTypeConverterTest.java @@ -68,7 +68,13 @@ public static Collection expectedErrorMapping() { { TransactionInvalidReason.TRANSACTION_REPLACEMENT_UNDERPRICED, RpcErrorType.ETH_SEND_TX_REPLACEMENT_UNDERPRICED - } + }, + { + TransactionInvalidReason.BLOB_GAS_PRICE_BELOW_CURRENT_BLOB_BASE_FEE, + RpcErrorType.BLOB_GAS_PRICE_BELOW_CURRENT_BLOB_BASE_FEE + }, + {TransactionInvalidReason.TOTAL_BLOB_GAS_TOO_HIGH, RpcErrorType.TOTAL_BLOB_GAS_TOO_HIGH}, + {TransactionInvalidReason.INVALID_BLOBS, RpcErrorType.INVALID_BLOBS} }); } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCallTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCallTest.java index 00c5af2f94c..8aba16bfb9c 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCallTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCallTest.java @@ -116,7 +116,7 @@ public void shouldReturnInternalErrorWhenProcessorReturnsEmpty() { public void shouldAcceptRequestWhenMissingOptionalFields() { final JsonCallParameter callParameter = new JsonCallParameter( - null, null, null, null, null, null, null, null, null, Boolean.FALSE, null); + null, null, null, null, null, null, null, null, null, Boolean.FALSE, null, null, null); final JsonRpcRequestContext request = ethCallRequest(callParameter, "latest"); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, Bytes.of().toString()); @@ -455,6 +455,8 @@ private JsonCallParameter callParameter( Bytes.EMPTY, null, null, + null, + null, null); } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCreateAccessListTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCreateAccessListTest.java index 674617b4b57..0d129d26652 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCreateAccessListTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCreateAccessListTest.java @@ -316,6 +316,8 @@ private JsonCallParameter legacyTransactionCallParameter(final Wei gasPrice) { Bytes.EMPTY, null, false, + null, + null, null); } @@ -345,7 +347,9 @@ private JsonCallParameter eip1559TransactionCallParameter( Bytes.EMPTY, null, false, - accessListEntries); + accessListEntries, + null, + null); } private List createAccessList() { diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGasTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGasTest.java index 44b3bb9ba04..307424ea9a1 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGasTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGasTest.java @@ -473,6 +473,8 @@ private JsonCallParameter legacyTransactionCallParameter( Bytes.EMPTY, null, isStrict, + null, + null, null); } @@ -505,6 +507,8 @@ private JsonCallParameter eip1559TransactionCallParameter(final Optional ga Bytes.EMPTY, null, false, + null, + null, null); } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthFeeHistoryTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthFeeHistoryTest.java index 7d58848a132..a0821abf2de 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthFeeHistoryTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthFeeHistoryTest.java @@ -26,6 +26,7 @@ import org.hyperledger.besu.consensus.merge.blockcreation.MergeCoordinator; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.GasLimitCalculator; import org.hyperledger.besu.ethereum.api.ApiConfiguration; import org.hyperledger.besu.ethereum.api.ImmutableApiConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; @@ -47,9 +48,12 @@ import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionReceipt; +import org.hyperledger.besu.ethereum.mainnet.CancunTargetingGasLimitCalculator; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; +import org.hyperledger.besu.evm.gascalculator.CancunGasCalculator; +import org.hyperledger.besu.evm.gascalculator.LondonGasCalculator; import java.util.ArrayList; import java.util.Arrays; @@ -80,6 +84,8 @@ public void setUp() { miningCoordinator = mock(MergeCoordinator.class); when(miningCoordinator.getMinPriorityFeePerGas()).thenReturn(Wei.ONE); + mockFork(); + method = new EthFeeHistory( protocolSchedule, @@ -90,9 +96,6 @@ public void setUp() { @Test public void params() { - final ProtocolSpec londonSpec = mock(ProtocolSpec.class); - when(londonSpec.getFeeMarket()).thenReturn(FeeMarket.london(5)); - when(protocolSchedule.getForNextBlockHeader(any(), anyLong())).thenReturn(londonSpec); // should fail because no required params given assertThatThrownBy(this::feeHistoryRequest).isInstanceOf(InvalidJsonRpcParameters.class); // should fail because newestBlock not given @@ -110,12 +113,7 @@ public void params() { @Test public void allFieldsPresentForLatestBlock() { - final ProtocolSpec londonSpec = mock(ProtocolSpec.class); - when(londonSpec.getFeeMarket()).thenReturn(FeeMarket.london(5)); - when(protocolSchedule.getForNextBlockHeader( - eq(blockchain.getChainHeadHeader()), - eq(blockchain.getChainHeadHeader().getTimestamp()))) - .thenReturn(londonSpec); + final Object latest = ((JsonRpcSuccessResponse) feeHistoryRequest("0x1", "latest", new double[] {100.0})) .getResult(); @@ -126,6 +124,8 @@ public void allFieldsPresentForLatestBlock() { .oldestBlock(10) .baseFeePerGas(List.of(Wei.of(25496L), Wei.of(28683L))) .gasUsedRatio(List.of(0.9999999992132459)) + .baseFeePerBlobGas(List.of(Wei.of(0), Wei.of(0))) + .blobGasUsedRatio(List.of(0.0)) .reward(List.of(List.of(Wei.of(1524763764L)))) .build())); } @@ -250,29 +250,19 @@ public void blockCountBounds() { @Test public void doesntGoPastChainHeadWithHighBlockCount() { - final ProtocolSpec londonSpec = mock(ProtocolSpec.class); - when(londonSpec.getFeeMarket()).thenReturn(FeeMarket.london(5)); - when(protocolSchedule.getForNextBlockHeader( - eq(blockchain.getChainHeadHeader()), - eq(blockchain.getChainHeadHeader().getTimestamp()))) - .thenReturn(londonSpec); final FeeHistory.FeeHistoryResult result = (ImmutableFeeHistoryResult) ((JsonRpcSuccessResponse) feeHistoryRequest("0x14", "latest")).getResult(); assertThat(Long.decode(result.getOldestBlock())).isEqualTo(0); assertThat(result.getBaseFeePerGas()).hasSize(12); assertThat(result.getGasUsedRatio()).hasSize(11); + assertThat(result.getBaseFeePerBlobGas()).hasSize(12); + assertThat(result.getBlobGasUsedRatio()).hasSize(11); assertThat(result.getReward()).isNull(); } @Test public void feeValuesAreInTheBlockCountAndHighestBlock() { - final ProtocolSpec londonSpec = mock(ProtocolSpec.class); - when(londonSpec.getFeeMarket()).thenReturn(FeeMarket.london(5)); - when(protocolSchedule.getForNextBlockHeader( - eq(blockchain.getChainHeadHeader()), - eq(blockchain.getChainHeadHeader().getTimestamp()))) - .thenReturn(londonSpec); double[] percentile = new double[] {100.0}; final Object ninth = @@ -286,12 +276,6 @@ public void feeValuesAreInTheBlockCountAndHighestBlock() { @Test public void feeValuesDontGoPastHighestBlock() { - final ProtocolSpec londonSpec = mock(ProtocolSpec.class); - when(londonSpec.getFeeMarket()).thenReturn(FeeMarket.london(5)); - when(protocolSchedule.getForNextBlockHeader( - eq(blockchain.getChainHeadHeader()), - eq(blockchain.getChainHeadHeader().getTimestamp()))) - .thenReturn(londonSpec); double[] percentile = new double[] {100.0}; final Object second = @@ -345,6 +329,70 @@ private void assertFeeMetadataSize(final Object feeObject, final int blockCount) assertThat(((ImmutableFeeHistoryResult) feeObject).getReward().size()).isEqualTo(blockCount); assertThat(((ImmutableFeeHistoryResult) feeObject).getGasUsedRatio().size()) .isEqualTo(blockCount); + assertThat(((ImmutableFeeHistoryResult) feeObject).getBaseFeePerBlobGas().size()) + .isEqualTo(blockCount + 1); + assertThat(((ImmutableFeeHistoryResult) feeObject).getBlobGasUsedRatio().size()) + .isEqualTo(blockCount); + } + + @Test + public void shouldCalculateBlobFeeCorrectly_preBlob() { + assertBlobBaseFee(List.of(Wei.ZERO, Wei.ZERO)); + } + + @Test + public void shouldCalculateBlobFeeCorrectly_postBlob() { + mockPostBlobFork(); + assertBlobBaseFee(List.of(Wei.ONE, Wei.ONE)); + } + + @Test + public void shouldCalculateBlobFeeCorrectly_transitionFork() { + mockTransitionBlobFork(); + assertBlobBaseFee(List.of(Wei.ZERO, Wei.ONE)); + } + + private void mockFork() { + final ProtocolSpec londonSpec = mock(ProtocolSpec.class); + when(londonSpec.getGasCalculator()).thenReturn(new LondonGasCalculator()); + when(londonSpec.getFeeMarket()).thenReturn(FeeMarket.london(5)); + when(londonSpec.getGasLimitCalculator()).thenReturn(mock(GasLimitCalculator.class)); + + when(protocolSchedule.getByBlockHeader(any())).thenReturn(londonSpec); + when(protocolSchedule.getForNextBlockHeader(any(), anyLong())).thenReturn(londonSpec); + } + + private void mockPostBlobFork() { + final ProtocolSpec cancunSpec = mock(ProtocolSpec.class); + when(cancunSpec.getGasCalculator()).thenReturn(new CancunGasCalculator()); + when(cancunSpec.getFeeMarket()).thenReturn(FeeMarket.cancun(5, Optional.empty())); + when(cancunSpec.getGasLimitCalculator()) + .thenReturn(mock(CancunTargetingGasLimitCalculator.class)); + when(protocolSchedule.getByBlockHeader(any())).thenReturn(cancunSpec); + when(protocolSchedule.getForNextBlockHeader(any(), anyLong())).thenReturn(cancunSpec); + } + + private void mockTransitionBlobFork() { + final ProtocolSpec cancunSpec = mock(ProtocolSpec.class); + when(cancunSpec.getGasCalculator()).thenReturn(new CancunGasCalculator()); + when(cancunSpec.getFeeMarket()).thenReturn(FeeMarket.cancun(5, Optional.empty())); + when(cancunSpec.getGasLimitCalculator()) + .thenReturn(mock(CancunTargetingGasLimitCalculator.class)); + when(protocolSchedule.getForNextBlockHeader(any(), anyLong())).thenReturn(cancunSpec); + } + + private void assertBlobBaseFee(final List baseFeePerBlobGas) { + final Object latest = ((JsonRpcSuccessResponse) feeHistoryRequest("0x1", "latest")).getResult(); + assertThat(latest) + .isEqualTo( + FeeHistory.FeeHistoryResult.from( + ImmutableFeeHistory.builder() + .oldestBlock(10) + .baseFeePerGas(List.of(Wei.of(25496L), Wei.of(28683L))) + .gasUsedRatio(List.of(0.9999999992132459)) + .baseFeePerBlobGas(baseFeePerBlobGas) + .blobGasUsedRatio(List.of(0.0)) + .build())); } private JsonRpcResponse feeHistoryRequest(final Object... params) { diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivCallTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivCallTest.java index 3542ff5100a..8456fa2847b 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivCallTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivCallTest.java @@ -86,6 +86,8 @@ public void shouldThrowInvalidJsonRpcParametersExceptionWhenMissingToField() { Bytes.EMPTY, null, null, + null, + null, null); final JsonRpcRequestContext request = ethCallRequest(privacyGroupId, callParameter, "latest"); @@ -122,6 +124,8 @@ public void shouldAcceptRequestWhenMissingOptionalFields() { null, null, null, + null, + null, null); final JsonRpcRequestContext request = ethCallRequest(privacyGroupId, callParameter, "latest"); final JsonRpcResponse expectedResponse = @@ -203,6 +207,8 @@ private JsonCallParameter callParameter() { Bytes.EMPTY, null, null, + null, + null, null); } diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_blob.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_blob.json new file mode 100644 index 00000000000..463c57e21e8 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_blob.json @@ -0,0 +1,23 @@ +{ + "request": { + "id": 4, + "jsonrpc": "2.0", + "method": "eth_call", + "params": [ + { + "to": "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f", + "from": "a94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "data": "0x12a7b914", + "blobVersionedHashes" : ["0x0100000051c8833cfbaf272e62da1285b183b0405357f62b052a4894ffcdaa2d"], + "maxFeePerBlobGas": "0x3b9aca00" + }, + "latest" + ] + }, + "response": { + "jsonrpc": "2.0", + "id": 4, + "result": "0x0000000000000000000000000000000000000000000000000000000000000001" + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_blob_missing_maxFeePerBlobGas.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_blob_missing_maxFeePerBlobGas.json new file mode 100644 index 00000000000..fd563c0c1de --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_blob_missing_maxFeePerBlobGas.json @@ -0,0 +1,22 @@ +{ + "request": { + "id": 4, + "jsonrpc": "2.0", + "method": "eth_call", + "params": [ + { + "to": "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f", + "from": "a94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "data": "0x12a7b914", + "blobVersionedHashes" : ["0x0100000051c8833cfbaf272e62da1285b183b0405357f62b052a4894ffcdaa2d"] + }, + "latest" + ] + }, + "response": { + "jsonrpc": "2.0", + "id": 4, + "error":{"code":-32603,"message":"Internal error"} + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_blob_too_many_blobs.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_blob_too_many_blobs.json new file mode 100644 index 00000000000..a43edf17f99 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_blob_too_many_blobs.json @@ -0,0 +1,23 @@ +{ + "request": { + "id": 4, + "jsonrpc": "2.0", + "method": "eth_call", + "params": [ + { + "to": "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f", + "from": "a94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "data": "0x12a7b914", + "blobVersionedHashes" : ["0x0100000051c8833cfbaf272e62da1285b183b0405357f62b052a4894ffcdaa2d","0x0100000051c8833cfbaf272e62da1285b183b0405357f62b052a4894ffcdaa2d","0x0100000051c8833cfbaf272e62da1285b183b0405357f62b052a4894ffcdaa2d","0x0100000051c8833cfbaf272e62da1285b183b0405357f62b052a4894ffcdaa2d","0x0100000051c8833cfbaf272e62da1285b183b0405357f62b052a4894ffcdaa2d","0x0100000051c8833cfbaf272e62da1285b183b0405357f62b052a4894ffcdaa2d","0x0100000051c8833cfbaf272e62da1285b183b0405357f62b052a4894ffcdaa2d"], + "maxFeePerBlobGas": "0x0" + }, + "latest" + ] + }, + "response": { + "jsonrpc": "2.0", + "id": 4, + "error":{"code":-32000,"message":"Total blob gas too high"} + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_blob_zero_fee.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_blob_zero_fee.json new file mode 100644 index 00000000000..f580e45d917 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_blob_zero_fee.json @@ -0,0 +1,23 @@ +{ + "request": { + "id": 4, + "jsonrpc": "2.0", + "method": "eth_call", + "params": [ + { + "to": "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f", + "from": "a94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "data": "0x12a7b914", + "blobVersionedHashes" : ["0x0100000051c8833cfbaf272e62da1285b183b0405357f62b052a4894ffcdaa2d"], + "maxFeePerBlobGas": "0x0" + }, + "latest" + ] + }, + "response": { + "jsonrpc": "2.0", + "id": 4, + "error":{"code":-32009,"message":"blob gas price below current blob base fee"} + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_feeHistory_fieldNamesWithReward.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_feeHistory_fieldNamesWithReward.json index f5b7baee3f0..ed65aaf5f62 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_feeHistory_fieldNamesWithReward.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_feeHistory_fieldNamesWithReward.json @@ -23,10 +23,12 @@ "0x3437004a", "0x2dbc88c1" ], + "baseFeePerBlobGas" : [ "0x0", "0x1", "0x1" ], "gasUsedRatio": [ 0.004079142040086682, 0.003713085594819442 ], + "blobGasUsedRatio" : [ 0.0, 0.3333333333333333 ], "reward": [ [ "0x3b9aca00", diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_feeHistory_fieldNamesWithoutReward.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_feeHistory_fieldNamesWithoutReward.json index f048553904a..efe182637ee 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_feeHistory_fieldNamesWithoutReward.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_feeHistory_fieldNamesWithoutReward.json @@ -18,10 +18,12 @@ "0x3437004a", "0x2dbc88c1" ], + "baseFeePerBlobGas" : [ "0x0", "0x1", "0x1" ], "gasUsedRatio": [ 0.004079142040086682, 0.003713085594819442 - ] + ], + "blobGasUsedRatio" : [ 0.0, 0.3333333333333333 ] } }, "statusCode": 200 diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/CallParameter.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/CallParameter.java index 433be4673a3..e5d37e399ef 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/CallParameter.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/CallParameter.java @@ -16,6 +16,7 @@ import org.hyperledger.besu.datatypes.AccessListEntry; import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.VersionedHash; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.core.Transaction; @@ -37,6 +38,7 @@ public class CallParameter { private final Optional maxPriorityFeePerGas; private final Optional maxFeePerGas; + private final Optional maxFeePerBlobGas; private final Wei gasPrice; @@ -45,6 +47,7 @@ public class CallParameter { private final Bytes payload; private final Optional> accessList; + private final Optional> blobVersionedHashes; public CallParameter( final Address from, @@ -62,6 +65,8 @@ public CallParameter( this.gasPrice = gasPrice; this.value = value; this.payload = payload; + this.maxFeePerBlobGas = Optional.empty(); + this.blobVersionedHashes = Optional.empty(); } public CallParameter( @@ -83,6 +88,33 @@ public CallParameter( this.value = value; this.payload = payload; this.accessList = accessList; + this.maxFeePerBlobGas = Optional.empty(); + this.blobVersionedHashes = Optional.empty(); + } + + public CallParameter( + final Address from, + final Address to, + final long gasLimit, + final Wei gasPrice, + final Optional maxPriorityFeePerGas, + final Optional maxFeePerGas, + final Wei value, + final Bytes payload, + final Optional> accessList, + final Optional maxFeePerBlobGas, + final Optional> blobVersionedHashes) { + this.from = from; + this.to = to; + this.gasLimit = gasLimit; + this.maxPriorityFeePerGas = maxPriorityFeePerGas; + this.maxFeePerGas = maxFeePerGas; + this.gasPrice = gasPrice; + this.value = value; + this.payload = payload; + this.accessList = accessList; + this.maxFeePerBlobGas = maxFeePerBlobGas; + this.blobVersionedHashes = blobVersionedHashes; } public Address getFrom() { @@ -121,6 +153,14 @@ public Optional> getAccessList() { return accessList; } + public Optional getMaxFeePerBlobGas() { + return maxFeePerBlobGas; + } + + public Optional> getBlobVersionedHashes() { + return blobVersionedHashes; + } + @Override public boolean equals(final Object o) { if (this == o) { @@ -137,13 +177,26 @@ public boolean equals(final Object o) { && Objects.equals(maxPriorityFeePerGas, that.maxPriorityFeePerGas) && Objects.equals(maxFeePerGas, that.maxFeePerGas) && Objects.equals(value, that.value) - && Objects.equals(payload, that.payload); + && Objects.equals(payload, that.payload) + && Objects.equals(accessList, that.accessList) + && Objects.equals(maxFeePerBlobGas, that.maxFeePerBlobGas) + && Objects.equals(blobVersionedHashes, that.blobVersionedHashes); } @Override public int hashCode() { return Objects.hash( - from, to, gasLimit, gasPrice, maxPriorityFeePerGas, maxFeePerGas, value, payload); + from, + to, + gasLimit, + gasPrice, + maxPriorityFeePerGas, + maxFeePerGas, + value, + payload, + accessList, + maxFeePerBlobGas, + blobVersionedHashes); } public static CallParameter fromTransaction(final Transaction tx) { @@ -156,6 +209,8 @@ public static CallParameter fromTransaction(final Transaction tx) { tx.getMaxFeePerGas(), Wei.fromQuantity(tx.getValue()), tx.getPayload(), - tx.getAccessList()); + tx.getAccessList(), + tx.getMaxFeePerBlobGas(), + tx.getVersionedHashes()); } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulator.java index 3b94f888179..64efb974647 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulator.java @@ -294,6 +294,10 @@ private Optional buildTransaction( // Set access list if present callParams.getAccessList().ifPresent(transactionBuilder::accessList); + // Set versioned hashes if present + callParams.getBlobVersionedHashes().ifPresent(transactionBuilder::versionedHashes); + // Set max fee per blob gas if present + callParams.getMaxFeePerBlobGas().ifPresent(transactionBuilder::maxFeePerBlobGas); final Wei gasPrice; final Wei maxFeePerGas; diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulatorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulatorTest.java index 1fde6b27e2f..78b7a347f0d 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulatorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulatorTest.java @@ -26,10 +26,12 @@ import org.hyperledger.besu.crypto.SignatureAlgorithm; import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.BlobsWithCommitments; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.TransactionType; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.core.BlobTestFixture; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderFunctions; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; @@ -600,6 +602,86 @@ public void shouldKeepOriginalGasLimitWhenCapIsHigherThanOriginalValue() { verifyTransactionWasProcessed(expectedTransaction); } + @Test + public void shouldReturnSuccessfulResultWhenBlobTransactionProcessingIsSuccessful() { + final CallParameter callParameter = + blobTransactionCallParameter(Wei.ONE, Wei.ONE, Wei.ONE, 300, 3); + + final BlockHeader blockHeader = mockBlockHeader(Hash.ZERO, 1L, Wei.ONE); + + mockBlockchainForBlockHeader(blockHeader); + mockWorldStateForAccount(blockHeader, callParameter.getFrom(), 1L); + + final Transaction expectedTransaction = + Transaction.builder() + .type(TransactionType.BLOB) + .chainId(BigInteger.ONE) + .nonce(1L) + .gasLimit(callParameter.getGasLimit()) + .maxFeePerGas(callParameter.getMaxFeePerGas().orElseThrow()) + .maxPriorityFeePerGas(callParameter.getMaxPriorityFeePerGas().orElseThrow()) + .to(callParameter.getTo()) + .sender(callParameter.getFrom()) + .value(callParameter.getValue()) + .payload(callParameter.getPayload()) + .maxFeePerBlobGas(callParameter.getMaxFeePerBlobGas().get()) + .versionedHashes(callParameter.getBlobVersionedHashes().get()) + .signature(FAKE_SIGNATURE) + .build(); + + final CallParameter reverseEngineeredCallParam = + CallParameter.fromTransaction(expectedTransaction); + assertThat(reverseEngineeredCallParam).isEqualTo(callParameter); + + mockProcessorStatusForTransaction(expectedTransaction, Status.SUCCESSFUL); + + final Optional result = + transactionSimulator.process(callParameter, 1L); + + assertThat(result.get().isSuccessful()).isTrue(); + verifyTransactionWasProcessed(expectedTransaction); + } + + @Test + public void shouldReturnFailureResultWhenBlobTransactionProcessingFails() { + final CallParameter callParameter = + blobTransactionCallParameter(Wei.ONE, Wei.ONE, Wei.ONE, 300, 3); + + final BlockHeader blockHeader = mockBlockHeader(Hash.ZERO, 1L, Wei.ONE); + + mockBlockchainForBlockHeader(blockHeader); + mockWorldStateForAccount(blockHeader, callParameter.getFrom(), 1L); + + final Transaction expectedTransaction = + Transaction.builder() + .type(TransactionType.BLOB) + .chainId(BigInteger.ONE) + .nonce(1L) + .gasLimit(callParameter.getGasLimit()) + .maxFeePerGas(callParameter.getMaxFeePerGas().orElseThrow()) + .maxPriorityFeePerGas(callParameter.getMaxPriorityFeePerGas().orElseThrow()) + .to(callParameter.getTo()) + .sender(callParameter.getFrom()) + .value(callParameter.getValue()) + .payload(callParameter.getPayload()) + .maxFeePerBlobGas(callParameter.getMaxFeePerBlobGas().get()) + .versionedHashes(callParameter.getBlobVersionedHashes().get()) + .signature(FAKE_SIGNATURE) + .build(); + + final CallParameter reverseEngineeredCallParam = + CallParameter.fromTransaction(expectedTransaction); + assertThat(reverseEngineeredCallParam).isEqualTo(callParameter); + + mockProcessorStatusForTransaction(expectedTransaction, Status.FAILED); + + final Optional result = + transactionSimulator.process(callParameter, 1L); + + assertThat(result.get().isSuccessful()).isFalse(); + verifyTransactionWasProcessed(expectedTransaction); + } + private void mockWorldStateForAccount( final BlockHeader blockHeader, final Address address, final long nonce) { final Account account = mock(Account.class); @@ -724,4 +806,25 @@ private CallParameter eip1559TransactionCallParameter( Bytes.EMPTY, Optional.empty()); } + + private CallParameter blobTransactionCallParameter( + final Wei maxFeePerBlobGas, + final Wei maxFeePerGas, + final Wei maxPriorityFeePerGas, + final long gasLimit, + final int numberOfBlobs) { + BlobsWithCommitments bwc = new BlobTestFixture().createBlobsWithCommitments(numberOfBlobs); + return new CallParameter( + Address.fromHexString("0x0"), + Address.fromHexString("0x0"), + gasLimit, + Wei.of(0), + Optional.of(maxFeePerGas), + Optional.of(maxPriorityFeePerGas), + Wei.of(0), + Bytes.EMPTY, + Optional.empty(), + Optional.of(maxFeePerBlobGas), + Optional.of(bwc.getVersionedHashes())); + } }