Skip to content

Commit

Permalink
Add eth_getBlockReceipts() JSON/RPC method (#5771)
Browse files Browse the repository at this point in the history
* Initial commit with new JSON/RPC method

Signed-off-by: Matthew Whitehead <matthew1001@gmail.com>

* Update CHANGELOG.md

Co-authored-by: Sally MacFarlane <macfarla.github@gmail.com>
Signed-off-by: Matt Whitehead <matthew1001@hotmail.com>

* Update unit tests to check receipts against generated blockchain transactions at runtime. Add getEthSerializedType() utility to TransactionType.

Signed-off-by: Matthew Whitehead <matthew1001@gmail.com>

* Add spec JSON/RPC tests

Signed-off-by: Matthew Whitehead <matthew1001@gmail.com>

---------

Signed-off-by: Matthew Whitehead <matthew1001@gmail.com>
Signed-off-by: Matt Whitehead <matthew1001@gmail.com>
Signed-off-by: Matt Whitehead <matthew1001@hotmail.com>
Signed-off-by: Sally MacFarlane <macfarla.github@gmail.com>
Co-authored-by: Sally MacFarlane <macfarla.github@gmail.com>
  • Loading branch information
matthew1001 and macfarla authored Aug 24, 2023
1 parent 6bd3135 commit 9a0f35c
Show file tree
Hide file tree
Showing 16 changed files with 564 additions and 5 deletions.
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
Original file line number Diff line number Diff line change
@@ -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<TransactionReceiptResult> txReceipt(final TransactionWithMetadata tx) {
Optional<TransactionReceiptWithMetadata> 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<TransactionReceiptResult> 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);
}
}
Original file line number Diff line number Diff line change
@@ -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<TransactionReceiptResult> results;

public BlockReceiptsResult(final List<TransactionReceiptResult> receipts) {
results = receipts;
}

@JsonValue
public List<TransactionReceiptResult> getResults() {
return results;
}
}
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -119,6 +120,7 @@ protected Map<String, JsonRpcMethod> 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(
Expand Down
Original file line number Diff line number Diff line change
@@ -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<TransactionReceipt> 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));
}
}
Loading

0 comments on commit 9a0f35c

Please sign in to comment.