Corrected eth_getLogs default fromBlock value. (#4513)

* Fixed default fromBlock value and improved parameter interpretation in eth_getLogs RPC handler. Improved test coverage.

Signed-off-by: mark-terry <mark.terry@consensys.net>
pull/4560/head
mark-terry 2 years ago committed by GitHub
parent 17a370d2f9
commit 26c4bcf1ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      CHANGELOG.md
  2. 38
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetLogs.java
  3. 18
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/BlockParameter.java
  4. 299
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetLogsTest.java
  5. 147
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/BlockParameterTest.java

@ -6,6 +6,9 @@
### Additions and Improvements
- Updated jackson-databind library to version 2.13.4.2 addressing [CVE-2022-42003](https://nvd.nist.gov/vuln/detail/CVE-2022-42003)
### Bug Fixes
- Fixed default fromBlock value and improved parameter interpetation in eth_getLogs RPC handler [#4513](https://github.com/hyperledger/besu/pull/4513)
## 22.10.0-RC2
### Breaking Changes
@ -36,8 +39,6 @@
- Avoid a cyclic reference while printing EngineExchangeTransitionConfigurationParameter [#4357](https://github.com/hyperledger/besu/pull/4357)
- Corrects treating a block as bad on internal error [#4512](https://github.com/hyperledger/besu/issues/4512)
- In GraphQL update scalar parsing to be variable friendly [#4522](https://github.com/hyperledger/besu/pull/4522)
- Initiate connection to maintained peers soon after startup. [#4469](https://github.com/hyperledger/besu/pull/4469)
- Update apache-commons-text to 1.10.0 to address CVE-2022-42889 [#4542](https://github.com/hyperledger/besu/pull/4542)
### Download Links

@ -25,10 +25,17 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.LogsResult;
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.core.LogWithMetadata;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class EthGetLogs implements JsonRpcMethod {
private static final Logger LOG = LoggerFactory.getLogger(EthGetLogs.class);
private final BlockchainQueries blockchain;
public EthGetLogs(final BlockchainQueries blockchain) {
@ -49,6 +56,7 @@ public class EthGetLogs implements JsonRpcMethod {
requestContext.getRequest().getId(), JsonRpcError.INVALID_PARAMS);
}
final AtomicReference<Exception> ex = new AtomicReference<>();
final List<LogWithMetadata> matchingLogs =
filter
.getBlockHash()
@ -58,9 +66,27 @@ public class EthGetLogs implements JsonRpcMethod {
blockHash, filter.getLogsQuery(), requestContext::isAlive))
.orElseGet(
() -> {
final long fromBlockNumber = filter.getFromBlock().getNumber().orElse(0L);
final long toBlockNumber =
filter.getToBlock().getNumber().orElse(blockchain.headBlockNumber());
final long fromBlockNumber;
final long toBlockNumber;
try {
fromBlockNumber =
filter
.getFromBlock()
.getBlockNumber(blockchain)
.orElseThrow(
() ->
new Exception("fromBlock not found: " + filter.getFromBlock()));
toBlockNumber =
filter
.getToBlock()
.getBlockNumber(blockchain)
.orElseThrow(
() -> new Exception("toBlock not found: " + filter.getToBlock()));
} catch (final Exception e) {
ex.set(e);
return Collections.emptyList();
}
return blockchain.matchingLogs(
fromBlockNumber,
toBlockNumber,
@ -68,6 +94,12 @@ public class EthGetLogs implements JsonRpcMethod {
requestContext::isAlive);
});
if (ex.get() != null) {
LOG.debug("eth_getLogs call failed: ", ex.get());
return new JsonRpcErrorResponse(
requestContext.getRequest().getId(), JsonRpcError.INTERNAL_ERROR);
}
return new JsonRpcSuccessResponse(
requestContext.getRequest().getId(), new LogsResult(matchingLogs));
}

@ -14,7 +14,9 @@
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters;
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader;
import java.util.Objects;
import java.util.Optional;
@ -99,6 +101,22 @@ public class BlockParameter {
return this.type == BlockParameterType.NUMERIC;
}
public Optional<Long> getBlockNumber(final BlockchainQueries blockchain) {
if (this.isFinalized()) {
return blockchain.finalizedBlockHeader().map(ProcessableBlockHeader::getNumber);
} else if (this.isLatest()) {
return Optional.of(blockchain.headBlockNumber());
} else if (this.isPending()) {
// Pending not implemented, returns latest
return Optional.of(blockchain.headBlockNumber());
} else if (this.isSafe()) {
return blockchain.safeBlockHeader().map(ProcessableBlockHeader::getNumber);
} else {
// Alternate cases (numeric input or "earliest")
return this.getNumber();
}
}
@Override
public String toString() {
return "BlockParameter{" + "type=" + type + ", number=" + number + '}';

@ -0,0 +1,299 @@
/*
* Copyright contributors to Hyperledger Besu
*
* 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.catchThrowable;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
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.parameters.BlockParameter;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.FilterParameter;
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.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import java.util.ArrayList;
import java.util.Optional;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class EthGetLogsTest {
private EthGetLogs method;
@Mock BlockchainQueries blockchainQueries;
@Before
public void setUp() {
method = new EthGetLogs(blockchainQueries);
}
@Test
public void shouldReturnCorrectMethodName() {
assertThat(method.getName()).isEqualTo("eth_getLogs");
}
@Test
public void shouldReturnErrorWhenMissingParams() {
final JsonRpcRequestContext request =
new JsonRpcRequestContext(new JsonRpcRequest("2.0", "eth_getLogs", new Object[] {}));
final Throwable thrown = catchThrowable(() -> method.response(request));
assertThat(thrown)
.isInstanceOf(InvalidJsonRpcParameters.class)
.hasMessage("Missing required json rpc parameter at index 0");
verifyNoInteractions(blockchainQueries);
}
@Test
public void shouldQueryFinalizedBlock() {
final long blockNumber = 7L;
final JsonRpcRequestContext request = buildRequest("finalized", "finalized");
final BlockHeader blockHeader = mock(BlockHeader.class);
when(blockchainQueries.finalizedBlockHeader()).thenReturn(Optional.of(blockHeader));
when(blockHeader.getNumber()).thenReturn(blockNumber);
when(blockchainQueries.matchingLogs(anyLong(), anyLong(), any(), any()))
.thenReturn(new ArrayList<>());
final JsonRpcResponse response = method.response(request);
assertThat(response).isInstanceOf(JsonRpcSuccessResponse.class);
verify(blockchainQueries).matchingLogs(eq(blockNumber), eq(blockNumber), any(), any());
verify(blockchainQueries, never()).headBlockNumber();
verify(blockchainQueries, never()).safeBlockHeader();
}
@Test
public void shouldQueryLatestBlock() {
final long latestBlockNumber = 7L;
final JsonRpcRequestContext request = buildRequest("latest", "latest");
when(blockchainQueries.headBlockNumber()).thenReturn(latestBlockNumber);
when(blockchainQueries.matchingLogs(anyLong(), anyLong(), any(), any()))
.thenReturn(new ArrayList<>());
final JsonRpcResponse response = method.response(request);
assertThat(response).isInstanceOf(JsonRpcSuccessResponse.class);
verify(blockchainQueries)
.matchingLogs(eq(latestBlockNumber), eq(latestBlockNumber), any(), any());
verify(blockchainQueries, never()).finalizedBlockHeader();
verify(blockchainQueries, never()).safeBlockHeader();
}
@Test
public void shouldQuerySafeBlock() {
final long blockNumber = 7L;
final JsonRpcRequestContext request = buildRequest("safe", "safe");
final BlockHeader blockHeader = mock(BlockHeader.class);
when(blockchainQueries.safeBlockHeader()).thenReturn(Optional.of(blockHeader));
when(blockHeader.getNumber()).thenReturn(blockNumber);
when(blockchainQueries.matchingLogs(anyLong(), anyLong(), any(), any()))
.thenReturn(new ArrayList<>());
final JsonRpcResponse response = method.response(request);
assertThat(response).isInstanceOf(JsonRpcSuccessResponse.class);
verify(blockchainQueries).matchingLogs(eq(blockNumber), eq(blockNumber), any(), any());
verify(blockchainQueries, never()).finalizedBlockHeader();
verify(blockchainQueries, never()).headBlockNumber();
}
@Test
public void shouldQueryNumericBlock() {
final long fromBlock = 3L;
final long toBlock = 10L;
final JsonRpcRequestContext request = buildRequest(fromBlock, toBlock);
when(blockchainQueries.matchingLogs(anyLong(), anyLong(), any(), any()))
.thenReturn(new ArrayList<>());
final JsonRpcResponse response = method.response(request);
assertThat(response).isInstanceOf(JsonRpcSuccessResponse.class);
verify(blockchainQueries).matchingLogs(eq(fromBlock), eq(toBlock), any(), any());
verify(blockchainQueries, never()).finalizedBlockHeader();
verify(blockchainQueries, never()).headBlockNumber();
verify(blockchainQueries, never()).safeBlockHeader();
}
@Test
public void shouldQueryEarliestToNumeric() {
final long genesisBlock = 0L;
final long latestBlock = 50L;
final JsonRpcRequestContext request = buildRequest("earliest", latestBlock);
when(blockchainQueries.matchingLogs(anyLong(), anyLong(), any(), any()))
.thenReturn(new ArrayList<>());
final JsonRpcResponse response = method.response(request);
assertThat(response).isInstanceOf(JsonRpcSuccessResponse.class);
verify(blockchainQueries).matchingLogs(eq(genesisBlock), eq(latestBlock), any(), any());
verify(blockchainQueries, never()).finalizedBlockHeader();
verify(blockchainQueries, never()).safeBlockHeader();
}
@Test
public void shouldQueryWrappedNumeric() {
final long fromBlock = 50L;
final long toBlock = 100L;
final JsonRpcRequestContext request =
buildRequest(String.valueOf(fromBlock), String.valueOf(toBlock));
when(blockchainQueries.matchingLogs(anyLong(), anyLong(), any(), any()))
.thenReturn(new ArrayList<>());
final JsonRpcResponse response = method.response(request);
assertThat(response).isInstanceOf(JsonRpcSuccessResponse.class);
verify(blockchainQueries).matchingLogs(eq(fromBlock), eq(toBlock), any(), any());
verify(blockchainQueries, never()).finalizedBlockHeader();
verify(blockchainQueries, never()).headBlockNumber();
verify(blockchainQueries, never()).safeBlockHeader();
}
@Test
public void shouldQueryWrappedNumericToNumeric() {
final long fromBlock = 50L;
final long toBlock = 100L;
final JsonRpcRequestContext request = buildRequest(String.valueOf(fromBlock), toBlock);
when(blockchainQueries.matchingLogs(anyLong(), anyLong(), any(), any()))
.thenReturn(new ArrayList<>());
final JsonRpcResponse response = method.response(request);
assertThat(response).isInstanceOf(JsonRpcSuccessResponse.class);
verify(blockchainQueries).matchingLogs(eq(fromBlock), eq(toBlock), any(), any());
verify(blockchainQueries, never()).finalizedBlockHeader();
verify(blockchainQueries, never()).headBlockNumber();
verify(blockchainQueries, never()).safeBlockHeader();
}
@Test
public void shouldQueryEarliestToLatest() {
final long genesisBlock = 0L;
final long latestBlock = 50L;
final JsonRpcRequestContext request = buildRequest("earliest", "latest");
when(blockchainQueries.headBlockNumber()).thenReturn(latestBlock);
when(blockchainQueries.matchingLogs(anyLong(), anyLong(), any(), any()))
.thenReturn(new ArrayList<>());
final JsonRpcResponse response = method.response(request);
assertThat(response).isInstanceOf(JsonRpcSuccessResponse.class);
verify(blockchainQueries).matchingLogs(eq(genesisBlock), eq(latestBlock), any(), any());
verify(blockchainQueries, never()).finalizedBlockHeader();
verify(blockchainQueries, never()).safeBlockHeader();
}
@Test
public void shouldQuerySafeToFinalized() {
final long finalizedBlockNumber = 50L;
final long safeBlockNumber = 25L;
final JsonRpcRequestContext request = buildRequest("safe", "finalized");
final BlockHeader finalizedBlockHeader = mock(BlockHeader.class);
final BlockHeader safeBlockHeader = mock(BlockHeader.class);
when(blockchainQueries.finalizedBlockHeader()).thenReturn(Optional.of(finalizedBlockHeader));
when(blockchainQueries.safeBlockHeader()).thenReturn(Optional.of(safeBlockHeader));
when(finalizedBlockHeader.getNumber()).thenReturn(finalizedBlockNumber);
when(safeBlockHeader.getNumber()).thenReturn(safeBlockNumber);
when(blockchainQueries.matchingLogs(anyLong(), anyLong(), any(), any()))
.thenReturn(new ArrayList<>());
final JsonRpcResponse response = method.response(request);
assertThat(response).isInstanceOf(JsonRpcSuccessResponse.class);
verify(blockchainQueries)
.matchingLogs(eq(safeBlockNumber), eq(finalizedBlockNumber), any(), any());
verify(blockchainQueries, times(1)).finalizedBlockHeader();
verify(blockchainQueries, times(1)).safeBlockHeader();
verify(blockchainQueries, never()).headBlockNumber();
}
@Test
public void shouldFailIfNoSafeBlock() {
final JsonRpcRequestContext request = buildRequest("safe", "finalized");
when(blockchainQueries.safeBlockHeader()).thenReturn(Optional.empty());
final JsonRpcResponse response = method.response(request);
assertThat(response).isInstanceOf(JsonRpcErrorResponse.class);
}
@Test
public void shouldFailIfNoFinalizedBlock() {
final JsonRpcRequestContext request = buildRequest("finalized", "safe");
when(blockchainQueries.finalizedBlockHeader()).thenReturn(Optional.empty());
final JsonRpcResponse response = method.response(request);
assertThat(response).isInstanceOf(JsonRpcErrorResponse.class);
}
private JsonRpcRequestContext buildRequest(final long fromBlock, final long toBlock) {
final FilterParameter filterParameter =
buildFilterParameter(new BlockParameter(fromBlock), new BlockParameter(toBlock));
return new JsonRpcRequestContext(
new JsonRpcRequest("2.0", "eth_getLogs", new Object[] {filterParameter}));
}
private JsonRpcRequestContext buildRequest(final String fromBlock, final long toBlock) {
final FilterParameter filterParameter =
buildFilterParameter(new BlockParameter(fromBlock), new BlockParameter(toBlock));
return new JsonRpcRequestContext(
new JsonRpcRequest("2.0", "eth_getLogs", new Object[] {filterParameter}));
}
private JsonRpcRequestContext buildRequest(final String fromBlock, final String toBlock) {
final FilterParameter filterParameter =
buildFilterParameter(new BlockParameter(fromBlock), new BlockParameter(toBlock));
return new JsonRpcRequestContext(
new JsonRpcRequest("2.0", "eth_getLogs", new Object[] {filterParameter}));
}
private FilterParameter buildFilterParameter(
final BlockParameter fromBlock, final BlockParameter toBlock) {
return new FilterParameter(fromBlock, toBlock, null, null, null, null, null, null, null);
}
}

@ -0,0 +1,147 @@
/*
* Copyright contributors to Hyperledger Besu
*
* 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.parameters;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import org.junit.Test;
public class BlockParameterTest {
@Test
public void earliestShouldReturnZeroNumberValue() {
final BlockParameter blockParameter = new BlockParameter("earliest");
assertThat(blockParameter.getNumber()).isPresent();
assertThat(blockParameter.getNumber().get()).isEqualTo(0);
assertThat(blockParameter.isEarliest()).isTrue();
assertThat(blockParameter.isFinalized()).isFalse();
assertThat(blockParameter.isLatest()).isFalse();
assertThat(blockParameter.isNumeric()).isFalse();
assertThat(blockParameter.isPending()).isFalse();
assertThat(blockParameter.isSafe()).isFalse();
}
@Test
public void latestShouldReturnEmptyNumberValue() {
final BlockParameter blockParameter = new BlockParameter("latest");
assertThat(blockParameter.getNumber()).isEmpty();
assertThat(blockParameter.isLatest()).isTrue();
assertThat(blockParameter.isEarliest()).isFalse();
assertThat(blockParameter.isFinalized()).isFalse();
assertThat(blockParameter.isNumeric()).isFalse();
assertThat(blockParameter.isPending()).isFalse();
assertThat(blockParameter.isSafe()).isFalse();
}
@Test
public void pendingShouldReturnEmptyNumberValue() {
final BlockParameter blockParameter = new BlockParameter("pending");
assertThat(blockParameter.getNumber()).isEmpty();
assertThat(blockParameter.isPending()).isTrue();
assertThat(blockParameter.isEarliest()).isFalse();
assertThat(blockParameter.isFinalized()).isFalse();
assertThat(blockParameter.isLatest()).isFalse();
assertThat(blockParameter.isNumeric()).isFalse();
assertThat(blockParameter.isSafe()).isFalse();
}
@Test
public void finalizedShouldReturnEmptyNumberValue() {
final BlockParameter blockParameter = new BlockParameter("finalized");
assertThat(blockParameter.getNumber()).isEmpty();
assertThat(blockParameter.isFinalized()).isTrue();
assertThat(blockParameter.isEarliest()).isFalse();
assertThat(blockParameter.isLatest()).isFalse();
assertThat(blockParameter.isNumeric()).isFalse();
assertThat(blockParameter.isPending()).isFalse();
assertThat(blockParameter.isSafe()).isFalse();
}
@Test
public void safeShouldReturnEmptyNumberValue() {
final BlockParameter blockParameter = new BlockParameter("safe");
assertThat(blockParameter.getNumber()).isEmpty();
assertThat(blockParameter.isSafe()).isTrue();
assertThat(blockParameter.isEarliest()).isFalse();
assertThat(blockParameter.isFinalized()).isFalse();
assertThat(blockParameter.isLatest()).isFalse();
assertThat(blockParameter.isNumeric()).isFalse();
assertThat(blockParameter.isPending()).isFalse();
}
@Test
public void stringNumberShouldReturnLongNumberValue() {
final BlockParameter blockParameter = new BlockParameter("7");
assertThat(blockParameter.getNumber()).isPresent();
assertThat(blockParameter.getNumber().get()).isEqualTo(7L);
assertThat(blockParameter.isNumeric()).isTrue();
assertThat(blockParameter.isEarliest()).isFalse();
assertThat(blockParameter.isFinalized()).isFalse();
assertThat(blockParameter.isLatest()).isFalse();
assertThat(blockParameter.isPending()).isFalse();
assertThat(blockParameter.isSafe()).isFalse();
}
@Test
public void longShouldReturnLongNumberValue() {
final BlockParameter blockParameter = new BlockParameter(5);
assertThat(blockParameter.getNumber()).isPresent();
assertThat(blockParameter.getNumber().get()).isEqualTo(5L);
assertThat(blockParameter.isNumeric()).isTrue();
assertThat(blockParameter.isEarliest()).isFalse();
assertThat(blockParameter.isFinalized()).isFalse();
assertThat(blockParameter.isLatest()).isFalse();
assertThat(blockParameter.isPending()).isFalse();
assertThat(blockParameter.isSafe()).isFalse();
}
@Test
public void upperCaseStringShouldBeHandled() {
final BlockParameter blockParameter = new BlockParameter("LATEST");
assertThat(blockParameter.getNumber()).isEmpty();
assertThat(blockParameter.isLatest()).isTrue();
assertThat(blockParameter.isEarliest()).isFalse();
assertThat(blockParameter.isFinalized()).isFalse();
assertThat(blockParameter.isNumeric()).isFalse();
assertThat(blockParameter.isPending()).isFalse();
assertThat(blockParameter.isSafe()).isFalse();
}
@Test
public void mixedCaseStringShouldBeHandled() {
final BlockParameter blockParameter = new BlockParameter("lATest");
assertThat(blockParameter.getNumber()).isEmpty();
assertThat(blockParameter.isLatest()).isTrue();
assertThat(blockParameter.isEarliest()).isFalse();
assertThat(blockParameter.isFinalized()).isFalse();
assertThat(blockParameter.isNumeric()).isFalse();
assertThat(blockParameter.isPending()).isFalse();
assertThat(blockParameter.isSafe()).isFalse();
}
@Test
public void invalidValueShouldThrowException() {
assertThatThrownBy(() -> new BlockParameter("invalid"))
.isInstanceOf(NumberFormatException.class)
.hasMessageContaining("invalid");
}
}
Loading…
Cancel
Save