Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
### Bug fixes
- BFT forks that change block period on time-based forks don't take effect [9681](https://github.com/hyperledger/besu/issues/9681)
- Fix QBFT `RLPException` when decoding proposals from pre-26.1.0 nodes that do not include the `blockAccessList` field [#9977](https://github.com/hyperledger/besu/pull/9977)
- Fix eth_simulateV1 discrepancy [9960] (https://github.com/besu-eth/besu/issues/9960) eth_simulateV1 now accepts calls where both input and data
are provided with different values, using input as per the execution-apis spec instead of returning an error.
- Wait for peers before starting chain download. Prevents an OutOfMemory (OOM) error when the node has zero peers [#9979](https://github.com/hyperledger/besu/pull/9979)

### Additions and Improvements
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,15 @@
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;

@JsonIgnoreProperties(ignoreUnknown = true)
public class JsonBlockStateCallParameter extends BlockStateCall {
@JsonCreator
public JsonBlockStateCallParameter(
@JsonProperty("calls") final List<CallParameter> calls,
@JsonProperty("calls")
@JsonDeserialize(contentUsing = SimulateCallParameterDeserializer.class)
final List<CallParameter> calls,
@JsonProperty("blockOverrides") final BlockOverridesParameter blockOverrides,
@JsonProperty("stateOverrides") final StateOverrideMap stateOverrides) {
super(calls, blockOverrides, stateOverrides);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright contributors to 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 org.hyperledger.besu.ethereum.transaction.CallParameter;

import java.io.IOException;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.node.ObjectNode;

/**
* Custom deserializer for {@link CallParameter} within eth_simulateV1 calls.
*
* <p>When both {@code input} and {@code data} are provided with different values, {@code input}
* takes precedence and {@code data} is ignored. This matches the behaviour of other EL clients
* (Geth, Nethermind, Reth, Erigon) for eth_simulateV1, where only {@code input} is defined in the
* execution-apis spec.
*
* <p>For eth_call and other methods, the standard {@link CallParameter} validation still applies
* and an error is returned when the two fields conflict.
*/
public class SimulateCallParameterDeserializer extends StdDeserializer<CallParameter> {

public SimulateCallParameterDeserializer() {
super(CallParameter.class);
}

@Override
public CallParameter deserialize(final JsonParser p, final DeserializationContext ctxt)
throws IOException {
final ObjectNode node = p.readValueAsTree();
final JsonNode inputNode = node.get("input");
final JsonNode dataNode = node.get("data");
if (inputNode != null && dataNode != null && !inputNode.equals(dataNode)) {
// input takes precedence; remove data so the standard CallParameter check does not fire
node.remove("data");
}
return p.getCodec().treeToValue(node, CallParameter.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,22 @@
import org.hyperledger.besu.ethereum.core.MiningConfiguration;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec;
import org.hyperledger.besu.ethereum.transaction.BlockSimulationParameter;
import org.hyperledger.besu.ethereum.transaction.BlockSimulator;
import org.hyperledger.besu.ethereum.transaction.TransactionSimulator;
import org.hyperledger.besu.ethereum.transaction.exceptions.BlockStateCallError;
import org.hyperledger.besu.ethereum.transaction.exceptions.BlockStateCallException;
import org.hyperledger.besu.evm.precompile.PrecompileContractRegistry;

import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.tuweni.bytes.Bytes;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
Expand Down Expand Up @@ -135,6 +139,39 @@ public void shouldReturnOriginalErrorCodeWhenUpfrontCostExceedsBalanceWithoutVal
.isEqualTo(BlockStateCallError.UPFRONT_COST_EXCEEDS_BALANCE.getCode());
}

@Test
public void shouldNotReturnInvalidParamsWhenInputAndDataHaveDifferentValues() {
setupMethodWithMockSimulator();
setupBlockchainForLatest();
when(blockSimulator.process(any(BlockHeader.class), any())).thenReturn(List.of());

// Reproduces issue #9960: both input and data provided with different values.
// Other EL clients (Geth, Nethermind, Reth, Erigon) accept this and use input.
final Map<String, Object> callObj =
Map.of(
"from", "0xc000000000000000000000000000000000000000",
"to", "0xd000000000000000000000000000000000000000",
"input", "0xDEADBEEF",
"data", "0xCAFEBABE");
final Map<String, Object> simulateParam =
Map.of("blockStateCalls", List.of(Map.of("calls", List.of(callObj))), "validation", false);

final JsonRpcRequestContext request =
new JsonRpcRequestContext(
new JsonRpcRequest("2.0", "eth_simulateV1", new Object[] {simulateParam, "latest"}));

final JsonRpcResponse response = method.response(request);

assertThat(response).isNotInstanceOf(JsonRpcErrorResponse.class);
Comment thread
macfarla marked this conversation as resolved.

final ArgumentCaptor<BlockSimulationParameter> captor =
ArgumentCaptor.forClass(BlockSimulationParameter.class);
verify(blockSimulator).process(any(BlockHeader.class), captor.capture());
final Bytes payload =
captor.getValue().getBlockStateCalls().get(0).getCalls().get(0).getPayload().orElseThrow();
assertThat(payload).isEqualTo(Bytes.fromHexString("0xDEADBEEF"));
}

@Test
public void shouldReturnInvalidParamsWhenParameterParsingFails() {
setupMethodWithMockSimulator();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@
}
},
"statusCode": 200
}
}
Loading