diff --git a/CHANGELOG.md b/CHANGELOG.md index 9965aed0e8f..686fbf0a468 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,13 +6,13 @@ - Add new methods to `OperationTracer` to capture contexts enter/exit [#5756](https://github.com/hyperledger/besu/pull/5756) ### Breaking Changes - - Add ABI-decoded revert reason to `eth_call` and `eth_estimateGas` responses [#5705](https://github.com/hyperledger/besu/issues/5705) ### Additions and Improvements - Add missing methods to the `Transaction` interface [#5732](https://github.com/hyperledger/besu/pull/5732) -- Added `benchmark` subcommand to `evmtool` [#5754](https://github.com/hyperledger/besu/issues/5754) +- Add `benchmark` subcommand to `evmtool` [#5754](https://github.com/hyperledger/besu/issues/5754) - JSON output is now compact by default. This can be overridden by the new `--json-pretty-print-enabled` CLI option. [#5766](https://github.com/hyperledger/besu/pull/5766) +- New `eth_getBlockReceipts` JSON-RPC method to retrieve all transaction receipts for a block in a single call [#5771](https://github.com/hyperledger/besu/pull/5771) - Add new methods to `OperationTracer` to capture contexts enter/exit [#5756](https://github.com/hyperledger/besu/pull/5756) ### Bug Fixes diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/TransactionType.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/TransactionType.java index b100240058c..984a4cc7467 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/TransactionType.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/TransactionType.java @@ -50,6 +50,17 @@ public byte getSerializedType() { return (byte) this.typeValue; } + /** + * Gets serialized type for returning in an eth transaction result, factoring in the special case + * for FRONTIER transactions which have enum type 0xf8 but are represented as 0x00 in transaction + * results. + * + * @return the serialized type + */ + public byte getEthSerializedType() { + return (this == FRONTIER ? 0x00 : this.getSerializedType()); + } + /** * Compare to serialized type. * diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java index 5808c8f3fad..979ff433688 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java @@ -98,6 +98,7 @@ public enum RpcMethod { ETH_GET_BALANCE("eth_getBalance"), ETH_GET_BLOCK_BY_HASH("eth_getBlockByHash"), ETH_GET_BLOCK_BY_NUMBER("eth_getBlockByNumber"), + ETH_GET_BLOCK_RECEIPTS("eth_getBlockReceipts"), ETH_GET_BLOCK_TRANSACTION_COUNT_BY_HASH("eth_getBlockTransactionCountByHash"), ETH_GET_BLOCK_TRANSACTION_COUNT_BY_NUMBER("eth_getBlockTransactionCountByNumber"), ETH_GET_CODE("eth_getCode"), diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBlockReceipts.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBlockReceipts.java new file mode 100644 index 00000000000..92b3eb54542 --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBlockReceipts.java @@ -0,0 +1,96 @@ +/* + * 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; + +import org.hyperledger.besu.datatypes.Hash; +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.parameters.BlockParameterOrBlockHash; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlockReceiptsResult; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.TransactionReceiptResult; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.TransactionReceiptRootResult; +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.api.query.TransactionWithMetadata; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.mainnet.TransactionReceiptType; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import com.google.common.base.Suppliers; + +public class EthGetBlockReceipts extends AbstractBlockParameterOrBlockHashMethod { + + private final ProtocolSchedule protocolSchedule; + + public EthGetBlockReceipts( + final BlockchainQueries blockchain, final ProtocolSchedule protocolSchedule) { + super(Suppliers.ofInstance(blockchain)); + this.protocolSchedule = protocolSchedule; + } + + @Override + public String getName() { + return RpcMethod.ETH_GET_BLOCK_RECEIPTS.getMethodName(); + } + + @Override + protected BlockParameterOrBlockHash blockParameterOrBlockHash( + final JsonRpcRequestContext request) { + return request.getRequiredParameter(0, BlockParameterOrBlockHash.class); + } + + @Override + protected Object resultByBlockHash(final JsonRpcRequestContext request, final Hash blockHash) { + return getBlockReceiptsResult(blockHash); + } + + /* + * For a given transaction, get its receipt and if it exists, wrap in a transaction receipt of the correct type + */ + private Optional txReceipt(final TransactionWithMetadata tx) { + Optional receipt = + blockchainQueries + .get() + .transactionReceiptByTransactionHash(tx.getTransaction().getHash(), protocolSchedule); + if (receipt.isPresent()) { + if (receipt.get().getReceipt().getTransactionReceiptType() == TransactionReceiptType.ROOT) { + return Optional.of(new TransactionReceiptRootResult(receipt.get())); + } else { + return Optional.of(new TransactionReceiptStatusResult(receipt.get())); + } + } + return Optional.empty(); + } + + private BlockReceiptsResult getBlockReceiptsResult(final Hash blockHash) { + final List receiptList = + blockchainQueries + .get() + .blockByHash(blockHash) + .map( + block -> + block.getTransactions().stream() + .map(tx -> txReceipt(tx).get()) + .collect(Collectors.toList())) + .orElse(new ArrayList<>()); + + return new BlockReceiptsResult(receiptList); + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlockReceiptsResult.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlockReceiptsResult.java new file mode 100644 index 00000000000..2a239bdd3cb --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlockReceiptsResult.java @@ -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.ethereum.api.jsonrpc.internal.results; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonValue; + +/** The result set from querying the receipts for a given block. */ +public class BlockReceiptsResult { + + private final List results; + + public BlockReceiptsResult(final List receipts) { + results = receipts; + } + + @JsonValue + public List getResults() { + return results; + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/EthJsonRpcMethods.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/EthJsonRpcMethods.java index 938e3f62c94..b3ca5514d7b 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/EthJsonRpcMethods.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/EthJsonRpcMethods.java @@ -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 @@ -28,6 +28,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthGetBalance; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthGetBlockByHash; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthGetBlockByNumber; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthGetBlockReceipts; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthGetBlockTransactionCountByHash; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthGetBlockTransactionCountByNumber; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthGetCode; @@ -119,6 +120,7 @@ protected Map create() { new EthGetBalance(blockchainQueries), new EthGetBlockByHash(blockchainQueries, blockResult), new EthGetBlockByNumber(blockchainQueries, blockResult, synchronizer), + new EthGetBlockReceipts(blockchainQueries, protocolSchedule), new EthGetBlockTransactionCountByNumber(blockchainQueries), new EthGetBlockTransactionCountByHash(blockchainQueries), new EthCall( diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBlockReceiptsTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBlockReceiptsTest.java new file mode 100644 index 00000000000..dad5099ee7c --- /dev/null +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBlockReceiptsTest.java @@ -0,0 +1,195 @@ +/* + * 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; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider.createInMemoryBlockchain; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import org.hyperledger.besu.datatypes.Hash; +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.exception.InvalidJsonRpcParameters; +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.BlockReceiptsResult; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.TransactionReceiptResult; +import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; +import org.hyperledger.besu.ethereum.chain.MutableBlockchain; +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.BlockDataGenerator; +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.worldstate.WorldStateArchive; + +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class EthGetBlockReceiptsTest { + + private static final int BLOCKCHAIN_LENGTH = 5; + private static final String ZERO_HASH = String.valueOf(Hash.ZERO); + private static final String HASH_63_CHARS_LONG = + "0xd3d3d1340c085e1b14182e01fd0b7cc5b585dca77f809f78fcca3e1a165b189"; + private static final String ETH_METHOD = "eth_getBlockReceipts"; + private static final String JSON_RPC_VERSION = "2.0"; + + @Mock private BlockchainQueries blockchainQueries; + @Mock private WorldStateArchive worldStateArchive; + private MutableBlockchain blockchain; + private static final BlockDataGenerator blockDataGenerator = new BlockDataGenerator(); + private EthGetBlockReceipts method; + private ProtocolSchedule protocolSchedule; + final JsonRpcResponse blockNotFoundResponse = + new JsonRpcErrorResponse(null, RpcErrorType.BLOCK_NOT_FOUND); + + @BeforeEach + public void setUp() { + blockchain = createInMemoryBlockchain(blockDataGenerator.genesisBlock()); + + for (int i = 1; i < BLOCKCHAIN_LENGTH; i++) { + final BlockDataGenerator.BlockOptions options = + new BlockDataGenerator.BlockOptions() + .setBlockNumber(i) + .setParentHash(blockchain.getBlockHashByNumber(i - 1).orElseThrow()); + final Block block = blockDataGenerator.block(options); + final List receipts = blockDataGenerator.receipts(block); + + blockchain.appendBlock(block, receipts); + } + + blockchainQueries = spy(new BlockchainQueries(blockchain, worldStateArchive)); + protocolSchedule = mock(ProtocolSchedule.class); + method = new EthGetBlockReceipts(blockchainQueries, protocolSchedule); + } + + @Test + public void returnsCorrectMethodName() { + assertThat(method.getName()).isEqualTo(ETH_METHOD); + } + + @Test + public void exceptionWhenNoParamsSupplied() { + assertThatThrownBy(() -> method.response(requestWithParams())) + .isInstanceOf(InvalidJsonRpcParameters.class) + .hasMessage("Missing required json rpc parameter at index 0"); + verifyNoMoreInteractions(blockchainQueries); + } + + @Test + public void exceptionWhenBlockNumberTooLarge() { + assertThatThrownBy(() -> method.response(requestWithParams("0x1212121212121212121212"))) + .isInstanceOf(InvalidJsonRpcParameters.class); + verifyNoMoreInteractions(blockchainQueries); + } + + @Test + public void twoReceiptsForLatestBlock() { + + // Read expected transactions from the generated blockchain + final Transaction expectedTx1 = + blockchain.getBlockByNumber(BLOCKCHAIN_LENGTH - 1).get().getBody().getTransactions().get(0); + final Transaction expectedTx2 = + blockchain.getBlockByNumber(BLOCKCHAIN_LENGTH - 1).get().getBody().getTransactions().get(1); + + /* Block generator defaults to 2 transactions per mocked block */ + JsonRpcResponse actualResponse = method.response(requestWithParams("latest")); + assertThat(actualResponse).isInstanceOf(JsonRpcSuccessResponse.class); + final BlockReceiptsResult result = + (BlockReceiptsResult) ((JsonRpcSuccessResponse) actualResponse).getResult(); + + assertThat(result.getResults().size()).isEqualTo(2); + + // Check TX1 receipt is correct + TransactionReceiptResult tx1 = result.getResults().get(0); + assertThat(tx1.getBlockNumber()).isEqualTo("0x" + (BLOCKCHAIN_LENGTH - 1)); + assertThat(tx1.getEffectiveGasPrice()).isNotEmpty(); + assertThat(tx1.getTo()).isEqualTo(expectedTx1.getTo().get().toString()); + assertThat(tx1.getType()) + .isEqualTo(String.format("0x%X", expectedTx1.getType().getEthSerializedType())); + + // Check TX2 receipt is correct + TransactionReceiptResult tx2 = result.getResults().get(1); + assertThat(tx2.getBlockNumber()).isEqualTo("0x" + (BLOCKCHAIN_LENGTH - 1)); + assertThat(tx2.getEffectiveGasPrice()).isNotEmpty(); + assertThat(tx2.getTo()).isEqualTo(expectedTx2.getTo().get().toString()); + assertThat(tx2.getType()) + .isEqualTo(String.format("0x%X", expectedTx2.getType().getEthSerializedType())); + } + + @Test + public void twoReceiptsForBlockOne() { + + // Read expected transactions from the generated blockchain + final Transaction expectedTx1 = + blockchain.getBlockByNumber(1).get().getBody().getTransactions().get(0); + final Transaction expectedTx2 = + blockchain.getBlockByNumber(1).get().getBody().getTransactions().get(1); + + /* Block generator defaults to 2 transactions per block */ + JsonRpcResponse actualResponse = method.response(requestWithParams("0x01")); + assertThat(actualResponse).isInstanceOf(JsonRpcSuccessResponse.class); + final BlockReceiptsResult result = + (BlockReceiptsResult) ((JsonRpcSuccessResponse) actualResponse).getResult(); + + assertThat(result.getResults().size()).isEqualTo(2); + + // Check TX1 receipt is correct + TransactionReceiptResult tx1 = result.getResults().get(0); + assertThat(tx1.getBlockNumber()).isEqualTo("0x1"); + assertThat(tx1.getEffectiveGasPrice()).isNotEmpty(); + assertThat(tx1.getTo()).isEqualTo(expectedTx1.getTo().get().toString()); + assertThat(tx1.getType()) + .isEqualTo(String.format("0x%X", expectedTx1.getType().getEthSerializedType())); + + // Check TX2 receipt is correct + TransactionReceiptResult tx2 = result.getResults().get(1); + assertThat(tx2.getBlockNumber()).isEqualTo("0x1"); + assertThat(tx2.getEffectiveGasPrice()).isNotEmpty(); + assertThat(tx2.getTo()).isEqualTo(expectedTx2.getTo().get().toString()); + assertThat(tx2.getType()) + .isEqualTo(String.format("0x%X", expectedTx2.getType().getEthSerializedType())); + } + + @Test + public void blockNotFoundWhenHash63CharsLong() { + /* Valid hash with 63 chars in - should result in block not found */ + JsonRpcResponse actualResponse = method.response(requestWithParams(HASH_63_CHARS_LONG)); + assertThat(actualResponse).usingRecursiveComparison().isEqualTo(blockNotFoundResponse); + } + + @Test + public void blockNotFoundForZeroHash() { + /* Zero hash - should result in block not found */ + JsonRpcResponse actualResponse = method.response(requestWithParams(ZERO_HASH)); + assertThat(actualResponse).usingRecursiveComparison().isEqualTo(blockNotFoundResponse); + } + + private JsonRpcRequestContext requestWithParams(final Object... params) { + return new JsonRpcRequestContext(new JsonRpcRequest(JSON_RPC_VERSION, ETH_METHOD, params)); + } +} diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBlockReceiptsByHash_00.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBlockReceiptsByHash_00.json new file mode 100644 index 00000000000..a468a6af592 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBlockReceiptsByHash_00.json @@ -0,0 +1,31 @@ +{ + "request": { + "id": 302, + "jsonrpc": "2.0", + "method": "eth_getBlockReceipts", + "params": [ + "0x1878c6f27178250f3d55186a2887b076936599f307d96dabcf331b2ff0a38f0c" + ] + }, + "response": { + "jsonrpc": "2.0", + "id": 302, + "result": [ { + "blockHash" : "0x1878c6f27178250f3d55186a2887b076936599f307d96dabcf331b2ff0a38f0c", + "blockNumber" : "0x10", + "contractAddress" : null, + "cumulativeGasUsed" : "0xab90", + "from" : "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "gasUsed" : "0xab90", + "effectiveGasPrice" : "0x1", + "logs" : [ ], + "logsBloom" : "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "root" : "0xed8541244d07552bed21ab19a7a26bc9f6ac0826d91c20fed11961a746270af0", + "to" : "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f", + "transactionHash" : "0x9f6a5eb65991ce483a9e1bfc57b44cd2c8ff4c26ca804240d593b4b63c3eafcd", + "transactionIndex" : "0x0", + "type" : "0x0" + } ] + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBlockReceiptsByHash_NotFound.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBlockReceiptsByHash_NotFound.json new file mode 100644 index 00000000000..32d19394a48 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBlockReceiptsByHash_NotFound.json @@ -0,0 +1,19 @@ +{ + "request": { + "id": 303, + "jsonrpc": "2.0", + "method": "eth_getBlockReceipts", + "params": [ + "0x0000000000000000000000000000000000000000000000000000000000000000" + ] + }, + "response": { + "jsonrpc": "2.0", + "id": 303, + "error" : { + "code" : -32000, + "message" : "Block not found" + } +}, +"statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBlockReceiptsByNumber_1.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBlockReceiptsByNumber_1.json new file mode 100644 index 00000000000..2fb13be4b64 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBlockReceiptsByNumber_1.json @@ -0,0 +1,31 @@ +{ + "request": { + "id": 304, + "jsonrpc": "2.0", + "method": "eth_getBlockReceipts", + "params": [ + "0x1" + ] + }, + "response": { + "jsonrpc": "2.0", + "id": 304, + "result": [ { + "blockHash" : "0x10aaf14a53caf27552325374429d3558398a36d3682ede6603c2c6511896e9f9", + "blockNumber" : "0x1", + "contractAddress" : "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f", + "cumulativeGasUsed" : "0x78674", + "from" : "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "gasUsed" : "0x78674", + "effectiveGasPrice" : "0x1", + "logs" : [ ], + "logsBloom" : "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "root" : "0x6a59608add7cee26032d1c5c3923b91d0b50e6103f61b2137b68229bcdc87395", + "to" : null, + "transactionHash" : "0x812742182a79a8e67733edc58cfa3767aa2d7ad06439d156ddbbb33e3403b4ed", + "transactionIndex" : "0x0", + "type" : "0x0" + } ] + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBlockReceiptsByNumber_32.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBlockReceiptsByNumber_32.json new file mode 100644 index 00000000000..f67a4a37262 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBlockReceiptsByNumber_32.json @@ -0,0 +1,41 @@ +{ + "request": { + "id": 305, + "jsonrpc": "2.0", + "method": "eth_getBlockReceipts", + "params": [ + "0x20" + ] + }, + "response": { + "jsonrpc": "2.0", + "id": 305, + "result": [ { + "blockHash" : "0x71d59849ddd98543bdfbe8548f5eed559b07b8aaf196369f39134500eab68e53", + "blockNumber" : "0x20", + "contractAddress" : null, + "cumulativeGasUsed" : "0x5c99", + "from" : "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "gasUsed" : "0x5c99", + "effectiveGasPrice" : "0x1", + "logs" : [ { + "address" : "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f", + "topics" : [ "0x0000000000000000000000000000000000000000000000000000000000000001", "0x000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b", "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" ], + "data" : "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe9000000000000000000000000000000000000000000000000000000000000002a", + "blockNumber" : "0x20", + "transactionHash" : "0xcef53f2311d7c80e9086d661e69ac11a5f3d081e28e02a9ba9b66749407ac310", + "transactionIndex" : "0x0", + "blockHash" : "0x71d59849ddd98543bdfbe8548f5eed559b07b8aaf196369f39134500eab68e53", + "logIndex" : "0x0", + "removed" : false + } ], + "logsBloom" : "0x00000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000080000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000400000000000000000200000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000800000000040000000000000000000000000000000000000000010000000000000000000000000", + "root" : "0x6d54fb51c667e4cc4bd8f2633b675f831b35d7784a70b5df91cd58f593c5286e", + "to" : "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f", + "transactionHash" : "0xcef53f2311d7c80e9086d661e69ac11a5f3d081e28e02a9ba9b66749407ac310", + "transactionIndex" : "0x0", + "type" : "0x0" + } ] + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBlockReceiptsByNumber_NotFound.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBlockReceiptsByNumber_NotFound.json new file mode 100644 index 00000000000..37453eadb42 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBlockReceiptsByNumber_NotFound.json @@ -0,0 +1,19 @@ +{ + "request": { + "id": 306, + "jsonrpc": "2.0", + "method": "eth_getBlockReceipts", + "params": [ + "0x20202020202020" + ] + }, + "response": { + "jsonrpc": "2.0", + "id": 306, + "error" : { + "code" : -32000, + "message" : "Block not found" + } +}, +"statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBlockReceiptsByTag_latest.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBlockReceiptsByTag_latest.json new file mode 100644 index 00000000000..229039240a5 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBlockReceiptsByTag_latest.json @@ -0,0 +1,41 @@ +{ + "request": { + "id": 307, + "jsonrpc": "2.0", + "method": "eth_getBlockReceipts", + "params": [ + "latest" + ] + }, + "response": { + "jsonrpc": "2.0", + "id": 307, + "result": [ { + "blockHash" : "0x71d59849ddd98543bdfbe8548f5eed559b07b8aaf196369f39134500eab68e53", + "blockNumber" : "0x20", + "contractAddress" : null, + "cumulativeGasUsed" : "0x5c99", + "from" : "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "gasUsed" : "0x5c99", + "effectiveGasPrice" : "0x1", + "logs" : [ { + "address" : "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f", + "topics" : [ "0x0000000000000000000000000000000000000000000000000000000000000001", "0x000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b", "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" ], + "data" : "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe9000000000000000000000000000000000000000000000000000000000000002a", + "blockNumber" : "0x20", + "transactionHash" : "0xcef53f2311d7c80e9086d661e69ac11a5f3d081e28e02a9ba9b66749407ac310", + "transactionIndex" : "0x0", + "blockHash" : "0x71d59849ddd98543bdfbe8548f5eed559b07b8aaf196369f39134500eab68e53", + "logIndex" : "0x0", + "removed" : false + } ], + "logsBloom" : "0x00000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000080000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000400000000000000000200000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000800000000040000000000000000000000000000000000000000010000000000000000000000000", + "root" : "0x6d54fb51c667e4cc4bd8f2633b675f831b35d7784a70b5df91cd58f593c5286e", + "to" : "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f", + "transactionHash" : "0xcef53f2311d7c80e9086d661e69ac11a5f3d081e28e02a9ba9b66749407ac310", + "transactionIndex" : "0x0", + "type" : "0x0" + } ] + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBlockReceipts_invalidParams_blockNumber.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBlockReceipts_invalidParams_blockNumber.json new file mode 100644 index 00000000000..91f6a6af8f7 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBlockReceipts_invalidParams_blockNumber.json @@ -0,0 +1,19 @@ +{ + "request": { + "id": 300, + "jsonrpc": "2.0", + "method": "eth_getBlockReceipts", + "params": [ + "0x02020202020202020202020202" + ] + }, + "response": { + "jsonrpc": "2.0", + "id": 300, + "error" : { + "code" : -32602, + "message" : "Invalid params" + } + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBlockReceipts_invalidParams_tag.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBlockReceipts_invalidParams_tag.json new file mode 100644 index 00000000000..655ff6013ae --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBlockReceipts_invalidParams_tag.json @@ -0,0 +1,19 @@ +{ + "request": { + "id": 301, + "jsonrpc": "2.0", + "method": "eth_getBlockReceipts", + "params": [ + "invalidtag" + ] + }, + "response": { + "jsonrpc": "2.0", + "id": 301, + "error" : { + "code" : -32602, + "message" : "Invalid params" + } + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/forkid/ForkIdManager.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/forkid/ForkIdManager.java index 6cb560192ef..61457b5bbce 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/forkid/ForkIdManager.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/forkid/ForkIdManager.java @@ -70,7 +70,7 @@ public ForkIdManager( .collect(Collectors.toUnmodifiableList()); this.timestampForks = timestampForks.stream() - .filter(fork -> fork > 0L) + .filter(fork -> fork > blockchain.getGenesisBlock().getHeader().getTimestamp()) .distinct() .sorted() .collect(Collectors.toUnmodifiableList()); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/forkid/ForkIdTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/forkid/ForkIdTest.java index 5e7b28514d1..87f210bfa00 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/forkid/ForkIdTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/forkid/ForkIdTest.java @@ -21,16 +21,19 @@ import static org.hyperledger.besu.ethereum.forkid.ForkIdTestUtil.mockBlockchain; import static org.hyperledger.besu.ethereum.forkid.ForkIdTestUtil.wantPeerCheck; +import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.forkid.ForkIdTestUtil.ForkIds; import org.hyperledger.besu.ethereum.forkid.ForkIdTestUtil.GenesisHash; import org.hyperledger.besu.ethereum.forkid.ForkIdTestUtil.Network; import org.hyperledger.besu.ethereum.forkid.ForkIdTestUtil.PeerCheckCase; +import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.stream.Stream; import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -603,4 +606,17 @@ public void test( peerCheckCase.forkIdNext))) .isEqualTo(peerCheckCase.want)); } + + @Test + public void testGenesisTimestampEqualToShanghaiTimestamp() { + final ForkIdManager forkIdManager = + new ForkIdManager( + mockBlockchain(Hash.ZERO.toHexString(), 10L, 0L), + Collections.emptyList(), + List.of(1L, 2L), + false); + assertThat(forkIdManager.getAllForkIds().size()).isEqualTo(2); + assertThat(forkIdManager.getAllForkIds().get(0).getNext()).isEqualTo(2L); + assertThat(forkIdManager.getAllForkIds().get(1).getNext()).isEqualTo(0L); + } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/forkid/ForkIdTestUtil.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/forkid/ForkIdTestUtil.java index d88d0fcd027..7fc23c2d475 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/forkid/ForkIdTestUtil.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/forkid/ForkIdTestUtil.java @@ -16,6 +16,7 @@ import static java.util.Collections.emptyList; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import org.hyperledger.besu.datatypes.Hash; @@ -44,7 +45,7 @@ public static Blockchain mockBlockchain( final String genesisHash, final LongSupplier chainHeightSupplier, final long timestamp) { final Blockchain mockchain = mock(Blockchain.class); final BlockHeader mockHeader = mock(BlockHeader.class); - final Block block = new Block(mockHeader, null); + final Block block = spy(new Block(mockHeader, null)); final BlockHeader mockChainHeadHeader = mock(BlockHeader.class); when(mockchain.getGenesisBlock()).thenReturn(block); when(mockchain.getChainHeadBlockNumber()).thenReturn(chainHeightSupplier.getAsLong()); @@ -52,6 +53,9 @@ public static Blockchain mockBlockchain( when(mockchain.getChainHeadHeader()).thenReturn(mockChainHeadHeader); when(mockChainHeadHeader.getNumber()).thenReturn(chainHeightSupplier.getAsLong()); when(mockChainHeadHeader.getTimestamp()).thenReturn(timestamp); + final BlockHeader mockGenesisBlockHeader = mock(BlockHeader.class); + when(block.getHeader()).thenReturn(mockGenesisBlockHeader); + when(mockGenesisBlockHeader.getTimestamp()).thenReturn(1L); return mockchain; } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/encoding/TransactionAnnouncementEncoder.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/encoding/TransactionAnnouncementEncoder.java index dd7f14db928..ee55cc0de20 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/encoding/TransactionAnnouncementEncoder.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/encoding/TransactionAnnouncementEncoder.java @@ -81,7 +81,7 @@ private static Bytes encodeForEth68(final List transactions) { for (int i = 0; i < transactions.size(); i++) { final TransactionType type = transactions.get(i).getType(); - types[i] = type == TransactionType.FRONTIER ? 0x00 : type.getSerializedType(); + types[i] = type.getEthSerializedType(); sizes.add(transactions.get(i).getSize()); hashes.add(transactions.get(i).getHash()); } @@ -96,7 +96,7 @@ public static Bytes encodeForEth68( final byte[] byteTypes = new byte[types.size()]; for (int i = 0; i < types.size(); i++) { final TransactionType type = types.get(i); - byteTypes[i] = type == TransactionType.FRONTIER ? 0x00 : type.getSerializedType(); + byteTypes[i] = type.getEthSerializedType(); } return encodeForEth68(byteTypes, sizes, hashes); }