Skip to content

Commit

Permalink
[RPC] add SAFE and FINALIZED options for block param (#4902)
Browse files Browse the repository at this point in the history
* new test file with a block number param that is too long to be a block number

* check for block hash parameter length

* added unit test for safe & finalized

Signed-off-by: Sally MacFarlane <[email protected]>
  • Loading branch information
macfarla authored Jan 11, 2023
1 parent d23a187 commit 45c2c47
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<BlockchainQueries, Optional<BlockHeader>> 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);
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
* <p>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
Expand Down Expand Up @@ -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()));
Expand Down Expand Up @@ -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;
}
Expand All @@ -128,6 +148,8 @@ private enum BlockParameterType {
EARLIEST,
LATEST,
PENDING,
SAFE,
FINALIZED,
NUMERIC,
HASH
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -226,7 +256,7 @@ public void shouldAutoSelectIsAllowedExceedingBalanceToFalseWhenFeesAreZero() {
private void internalAutoSelectIsAllowedExceedingBalance(
final JsonCallParameter callParameter,
final Optional<Wei> baseFee,
final boolean isAllowedExeedingBalance) {
final boolean isAllowedExceedingBalance) {
final JsonRpcRequestContext request = ethCallRequest(callParameter, "latest");

final BlockHeader blockHeader = mock(BlockHeader.class);
Expand All @@ -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());
Expand Down
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit 45c2c47

Please sign in to comment.