diff --git a/CHANGELOG.md b/CHANGELOG.md index 90321de0db9..8f85ea0def3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - Improve SLOAD and SSTORE performance by caching empty slots [#4874](https://github.com/hyperledger/besu/pull/4874) - RPC methods that lookup block by hash will now return an error response if no block found [#4582](https://github.com/hyperledger/besu/pull/4582) +- Added support for `safe` and `finalized` strings for the RPC methods using defaultBlock parameter [#4902](https://github.com/hyperledger/besu/pull/4902) ### Bug Fixes diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractBlockParameterOrBlockHashMethod.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractBlockParameterOrBlockHashMethod.java index 7b718da9166..dfe53fef1bb 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractBlockParameterOrBlockHashMethod.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractBlockParameterOrBlockHashMethod.java @@ -26,6 +26,7 @@ import java.util.Optional; import java.util.OptionalLong; +import java.util.function.Function; import java.util.function.Supplier; import com.google.common.base.Suppliers; @@ -63,6 +64,26 @@ protected Object latestResult(final JsonRpcRequestContext request) { request, getBlockchainQueries().getBlockchain().getChainHead().getHash()); } + protected Object finalizedResult(final JsonRpcRequestContext request) { + return posRelatedResult(request, BlockchainQueries::finalizedBlockHeader); + } + + protected Object safeResult(final JsonRpcRequestContext request) { + return posRelatedResult(request, BlockchainQueries::safeBlockHeader); + } + + private Object posRelatedResult( + final JsonRpcRequestContext request, + final Function> blockHeaderSupplier) { + + return blockHeaderSupplier + .apply(blockchainQueries.get()) + .map(header -> resultByBlockHash(request, header.getBlockHash())) + .orElseGet( + () -> + new JsonRpcErrorResponse(request.getRequest().getId(), JsonRpcError.UNKNOWN_BLOCK)); + } + protected Object handleParamTypes(final JsonRpcRequestContext requestContext) { final BlockParameterOrBlockHash blockParameterOrBlockHash = blockParameterOrBlockHash(requestContext); @@ -72,6 +93,10 @@ protected Object handleParamTypes(final JsonRpcRequestContext requestContext) { result = latestResult(requestContext); } else if (blockParameterOrBlockHash.isPending()) { result = pendingResult(requestContext); + } else if (blockParameterOrBlockHash.isSafe()) { + result = safeResult(requestContext); + } else if (blockParameterOrBlockHash.isFinalized()) { + result = finalizedResult(requestContext); } else if (blockParameterOrBlockHash.isNumeric() || blockParameterOrBlockHash.isEarliest()) { final OptionalLong blockNumber = blockParameterOrBlockHash.getNumber(); if (blockNumber.isEmpty() || blockNumber.getAsLong() < 0) { diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/BlockParameterOrBlockHash.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/BlockParameterOrBlockHash.java index 7b5f4477280..172a34d56f6 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/BlockParameterOrBlockHash.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/BlockParameterOrBlockHash.java @@ -27,8 +27,8 @@ import com.fasterxml.jackson.databind.JsonNode; /** - * Represents a block parameter that can be a special value ("pending", "earliest", "latest") or a - * number formatted as a hex string or a block hash. + * Represents a block parameter (or block hash) that can be a special value ("pending", "earliest", + * "latest", "finalized", "safe") or a number formatted as a hex string, or a block hash. * *

When distinguishing between a hash and a number it is presumed that a hash won't have three * quarters of the leading bytes as zero. This is fine for block hashes but not for precompiled @@ -61,11 +61,23 @@ public BlockParameterOrBlockHash(final Object value) throws JsonProcessingExcept number = OptionalLong.empty(); blockHash = Optional.empty(); requireCanonical = false; - } else if (normalizedValue.length() > 16) { + } else if (Objects.equals(normalizedValue, "safe")) { + type = BlockParameterType.SAFE; + number = OptionalLong.empty(); + blockHash = Optional.empty(); + requireCanonical = false; + } else if (Objects.equals(normalizedValue, "finalized")) { + type = BlockParameterType.FINALIZED; + number = OptionalLong.empty(); + blockHash = Optional.empty(); + requireCanonical = false; + } else if (normalizedValue.length() >= 65) { // with or without hex prefix type = BlockParameterType.HASH; number = OptionalLong.empty(); blockHash = Optional.of(Hash.fromHexStringLenient(normalizedValue)); requireCanonical = false; + } else if (normalizedValue.length() > 16) { + throw new IllegalArgumentException("hex number > 64 bits"); } else { type = BlockParameterType.NUMERIC; number = OptionalLong.of(Long.decode(value.toString())); @@ -112,6 +124,14 @@ public boolean isLatest() { return this.type == BlockParameterType.LATEST; } + public boolean isSafe() { + return this.type == BlockParameterType.SAFE; + } + + public boolean isFinalized() { + return this.type == BlockParameterType.FINALIZED; + } + public boolean isEarliest() { return this.type == BlockParameterType.EARLIEST; } @@ -128,6 +148,8 @@ private enum BlockParameterType { EARLIEST, LATEST, PENDING, + SAFE, + FINALIZED, NUMERIC, HASH } 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 d8c79be5c69..9fc6fc13a61 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 @@ -64,9 +64,13 @@ public class EthCallTest { @Mock private BlockchainQueries blockchainQueries; @Mock private TransactionSimulator transactionSimulator; + @Mock private BlockHeader blockHeader; + @Before public void setUp() { method = new EthCall(blockchainQueries, transactionSimulator); + blockHeader = mock(BlockHeader.class); + when(blockHeader.getBlockHash()).thenReturn(Hash.ZERO); } @Test @@ -160,6 +164,32 @@ public void shouldUseCorrectBlockNumberWhenEarliest() { verify(transactionSimulator).process(any(), any(), any(), any()); } + @Test + public void shouldUseCorrectBlockNumberWhenSafe() { + final JsonRpcRequestContext request = ethCallRequest(callParameter(), "safe"); + when(blockchainQueries.getBlockHeaderByHash(Hash.ZERO)).thenReturn(Optional.of(blockHeader)); + when(blockchainQueries.safeBlockHeader()).thenReturn(Optional.of(blockHeader)); + when(transactionSimulator.process(any(), any(), any(), any())).thenReturn(Optional.empty()); + method.response(request); + + verify(blockchainQueries).getBlockHeaderByHash(Hash.ZERO); + verify(blockchainQueries).safeBlockHeader(); + verify(transactionSimulator).process(any(), any(), any(), any()); + } + + @Test + public void shouldUseCorrectBlockNumberWhenFinalized() { + final JsonRpcRequestContext request = ethCallRequest(callParameter(), "finalized"); + when(blockchainQueries.getBlockHeaderByHash(Hash.ZERO)).thenReturn(Optional.of(blockHeader)); + when(blockchainQueries.finalizedBlockHeader()).thenReturn(Optional.of(blockHeader)); + when(transactionSimulator.process(any(), any(), any(), any())).thenReturn(Optional.empty()); + method.response(request); + + verify(blockchainQueries).getBlockHeaderByHash(Hash.ZERO); + verify(blockchainQueries).finalizedBlockHeader(); + verify(transactionSimulator).process(any(), any(), any(), any()); + } + @Test public void shouldUseCorrectBlockNumberWhenSpecified() { final JsonRpcRequestContext request = ethCallRequest(callParameter(), Quantity.create(13L)); @@ -226,7 +256,7 @@ public void shouldAutoSelectIsAllowedExceedingBalanceToFalseWhenFeesAreZero() { private void internalAutoSelectIsAllowedExceedingBalance( final JsonCallParameter callParameter, final Optional baseFee, - final boolean isAllowedExeedingBalance) { + final boolean isAllowedExceedingBalance) { final JsonRpcRequestContext request = ethCallRequest(callParameter, "latest"); final BlockHeader blockHeader = mock(BlockHeader.class); @@ -241,7 +271,7 @@ private void internalAutoSelectIsAllowedExceedingBalance( final TransactionValidationParams transactionValidationParams = ImmutableTransactionValidationParams.builder() .from(TransactionValidationParams.transactionSimulator()) - .isAllowExceedingBalance(isAllowedExeedingBalance) + .isAllowExceedingBalance(isAllowedExceedingBalance) .build(); verify(transactionSimulator).process(any(), eq(transactionValidationParams), any(), any()); diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionCount_invalidBlockNumber.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionCount_invalidBlockNumber.json new file mode 100644 index 00000000000..30eb13f0ecc --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionCount_invalidBlockNumber.json @@ -0,0 +1,20 @@ +{ + "request": { + "id": 487, + "jsonrpc": "2.0", + "method": "eth_getTransactionCount", + "params": [ + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "0xD5C9556Ec8Fef8A240EDdFdd01433Dc6EC08CBCa" + ] + }, + "response": { + "jsonrpc": "2.0", + "id": 487, + "error" : { + "code" : -32602, + "message" : "Invalid params" + } + }, + "statusCode": 400 +} \ No newline at end of file