From 5fd87d61f175eec7fd93c4c01bfaa530b5f3703e Mon Sep 17 00:00:00 2001 From: Fabio Di Fabio Date: Fri, 4 Oct 2024 06:48:43 +0200 Subject: [PATCH] Support chainId in CallParameters (#7720) Signed-off-by: Fabio Di Fabio Co-authored-by: Sally MacFarlane --- CHANGELOG.md | 3 +- .../jsonrpc/JsonRpcTestMethodsFactory.java | 12 +++-- .../fork/london/EthCallIntegrationTest.java | 22 +++++++++ .../london/EthEstimateGasIntegrationTest.java | 30 ++++++++++++ .../internal/methods/AbstractEstimateGas.java | 5 +- .../parameters/JsonCallParameter.java | 19 ++++++++ .../core/json/ChainIdDeserializer.java | 46 +++++++++++++++++++ .../ethereum/transaction/CallParameter.java | 18 +++++++- .../transaction/TransactionSimulator.java | 11 +++-- .../transaction/TransactionSimulatorTest.java | 1 + 10 files changed, 157 insertions(+), 10 deletions(-) create mode 100644 ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/json/ChainIdDeserializer.java diff --git a/CHANGELOG.md b/CHANGELOG.md index e304b72632..379c87ee08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,8 @@ - Add configuration of Consolidation Request Contract Address via genesis configuration [#7647](https://github.com/hyperledger/besu/pull/7647) - Interrupt pending transaction processing on block creation timeout [#7673](https://github.com/hyperledger/besu/pull/7673) - Align gas cap calculation for transaction simulation to Geth approach [#7703](https://github.com/hyperledger/besu/pull/7703) -- Expose chainId in the `BlockchainService` [7702](https://github.com/hyperledger/besu/pull/7702) +- Expose chainId in the `BlockchainService` [#7702](https://github.com/hyperledger/besu/pull/7702) +- Add support for `chainId` in `CallParameters` [#7720](https://github.com/hyperledger/besu/pull/7720) ### Bug fixes - Fix mounted data path directory permissions for besu user [#7575](https://github.com/hyperledger/besu/pull/7575) diff --git a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcTestMethodsFactory.java b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcTestMethodsFactory.java index 538060a905..c7ba5a4f93 100644 --- a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcTestMethodsFactory.java +++ b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcTestMethodsFactory.java @@ -68,7 +68,6 @@ public class JsonRpcTestMethodsFactory { private static final String CLIENT_NODE_NAME = "TestClientVersion/0.1.0"; private static final String CLIENT_VERSION = "0.1.0"; private static final String CLIENT_COMMIT = "12345678"; - private static final BigInteger NETWORK_ID = BigInteger.valueOf(123); private final BlockchainImporter importer; private final MutableBlockchain blockchain; @@ -76,6 +75,7 @@ public class JsonRpcTestMethodsFactory { private final ProtocolContext context; private final BlockchainQueries blockchainQueries; private final Synchronizer synchronizer; + private final ProtocolSchedule protocolSchedule; public JsonRpcTestMethodsFactory(final BlockchainImporter importer) { this.importer = importer; @@ -84,7 +84,7 @@ public class JsonRpcTestMethodsFactory { this.importer.getGenesisState().writeStateTo(stateArchive.getMutable()); this.context = new ProtocolContext(blockchain, stateArchive, null, new BadBlockManager()); - final ProtocolSchedule protocolSchedule = importer.getProtocolSchedule(); + this.protocolSchedule = importer.getProtocolSchedule(); this.synchronizer = mock(Synchronizer.class); for (final Block block : importer.getBlocks()) { @@ -106,6 +106,7 @@ public class JsonRpcTestMethodsFactory { this.blockchain = blockchain; this.stateArchive = stateArchive; this.context = context; + this.protocolSchedule = importer.getProtocolSchedule(); this.blockchainQueries = new BlockchainQueries( importer.getProtocolSchedule(), @@ -126,6 +127,7 @@ public class JsonRpcTestMethodsFactory { this.stateArchive = stateArchive; this.context = context; this.synchronizer = synchronizer; + this.protocolSchedule = importer.getProtocolSchedule(); this.blockchainQueries = new BlockchainQueries( importer.getProtocolSchedule(), @@ -142,6 +144,10 @@ public class JsonRpcTestMethodsFactory { return stateArchive; } + public BigInteger getChainId() { + return protocolSchedule.getChainId().get(); + } + public Map methods() { final P2PNetwork peerDiscovery = mock(P2PNetwork.class); final EthPeers ethPeers = mock(EthPeers.class); @@ -183,7 +189,7 @@ public class JsonRpcTestMethodsFactory { CLIENT_NODE_NAME, CLIENT_VERSION, CLIENT_COMMIT, - NETWORK_ID, + getChainId(), new StubGenesisConfigOptions(), peerDiscovery, blockchainQueries, diff --git a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/london/EthCallIntegrationTest.java b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/london/EthCallIntegrationTest.java index 5f03d7f4d5..9fb692ef82 100644 --- a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/london/EthCallIntegrationTest.java +++ b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/london/EthCallIntegrationTest.java @@ -30,6 +30,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSucces import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.testutil.BlockTestUtil; +import java.math.BigInteger; import java.util.Map; import com.google.common.base.Charsets; @@ -142,6 +143,7 @@ public class EthCallIntegrationTest { public void shouldReturnSuccessWithValidMaxFeePerGas() { final JsonCallParameter callParameter = new JsonCallParameter.JsonCallParameterBuilder() + .withChainId(BLOCKCHAIN.getChainId()) .withFrom(Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b")) .withTo(Address.fromHexString("0x9b8397f1b0fecd3a1a40cdd5e8221fa461898517")) .withMaxFeePerGas(Wei.fromHexString("0x3B9ACA01")) @@ -158,6 +160,26 @@ public class EthCallIntegrationTest { assertThat(response).usingRecursiveComparison().isEqualTo(expectedResponse); } + @Test + public void shouldReturnErrorWithInvalidChainId() { + final JsonCallParameter callParameter = + new JsonCallParameter.JsonCallParameterBuilder() + .withChainId(BLOCKCHAIN.getChainId().add(BigInteger.ONE)) + .withFrom(Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b")) + .withTo(Address.fromHexString("0x9b8397f1b0fecd3a1a40cdd5e8221fa461898517")) + .withMaxFeePerGas(Wei.fromHexString("0x3B9ACA01")) + .withInput(Bytes.fromHexString("0x2e64cec1")) + .build(); + + final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); + final JsonRpcResponse expectedResponse = + new JsonRpcErrorResponse(null, RpcErrorType.WRONG_CHAIN_ID); + + final JsonRpcResponse response = method.response(request); + + assertThat(response).usingRecursiveComparison().isEqualTo(expectedResponse); + } + @Test public void shouldReturnSuccessWithValidMaxFeePerGasAndMaxPriorityFeePerGas() { final JsonCallParameter callParameter = diff --git a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/london/EthEstimateGasIntegrationTest.java b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/london/EthEstimateGasIntegrationTest.java index 84447dfc9d..5fde9ccecb 100644 --- a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/london/EthEstimateGasIntegrationTest.java +++ b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/london/EthEstimateGasIntegrationTest.java @@ -25,10 +25,15 @@ 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.methods.JsonRpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonCallParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError; +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.mainnet.ValidationResult; +import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; import org.hyperledger.besu.testutil.BlockTestUtil; +import java.math.BigInteger; import java.util.List; import java.util.Map; @@ -112,6 +117,7 @@ public class EthEstimateGasIntegrationTest { public void shouldReturnExpectedValueForContractDeploy_WithAccessList() { final JsonCallParameter callParameter = new JsonCallParameter.JsonCallParameterBuilder() + .withChainId(BLOCKCHAIN.getChainId()) .withFrom(Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b")) .withInput( Bytes.fromHexString( @@ -124,6 +130,30 @@ public class EthEstimateGasIntegrationTest { assertThat(response).usingRecursiveComparison().isEqualTo(expectedResponse); } + @Test + public void shouldReturnErrorWithInvalidChainId() { + final JsonCallParameter callParameter = + new JsonCallParameter.JsonCallParameterBuilder() + .withChainId(BLOCKCHAIN.getChainId().add(BigInteger.ONE)) + .withFrom(Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b")) + .withTo(Address.fromHexString("0x8888f1f195afa192cfee860698584c030f4c9db1")) + .withValue(Wei.ONE) + .build(); + + final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); + final JsonRpcResponse expectedResponse = + new JsonRpcErrorResponse( + null, + JsonRpcError.from( + ValidationResult.invalid( + TransactionInvalidReason.WRONG_CHAIN_ID, + "transaction was meant for chain id 1983 and not this chain id 1982"))); + + final JsonRpcResponse response = method.response(request); + + assertThat(response).usingRecursiveComparison().isEqualTo(expectedResponse); + } + private List createAccessList() { return List.of( new AccessListEntry( diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractEstimateGas.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractEstimateGas.java index 4cbfb818db..8e826caf9e 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractEstimateGas.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractEstimateGas.java @@ -103,6 +103,7 @@ public abstract class AbstractEstimateGas extends AbstractBlockParameterMethod { protected CallParameter overrideGasLimitAndPrice( final JsonCallParameter callParams, final long gasLimit) { return new CallParameter( + callParams.getChainId(), callParams.getFrom(), callParams.getTo(), gasLimit, @@ -111,7 +112,9 @@ public abstract class AbstractEstimateGas extends AbstractBlockParameterMethod { callParams.getMaxFeePerGas(), callParams.getValue(), callParams.getPayload(), - callParams.getAccessList()); + callParams.getAccessList(), + callParams.getMaxFeePerBlobGas(), + callParams.getBlobVersionedHashes()); } /** diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/JsonCallParameter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/JsonCallParameter.java index 987b68c2e2..85dabda571 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/JsonCallParameter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/JsonCallParameter.java @@ -18,10 +18,12 @@ import org.hyperledger.besu.datatypes.AccessListEntry; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.VersionedHash; import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.core.json.ChainIdDeserializer; import org.hyperledger.besu.ethereum.core.json.GasDeserializer; import org.hyperledger.besu.ethereum.core.json.HexStringDeserializer; import org.hyperledger.besu.ethereum.transaction.CallParameter; +import java.math.BigInteger; import java.util.List; import java.util.Optional; @@ -41,6 +43,7 @@ import org.slf4j.LoggerFactory; * *
{@code
  * JsonCallParameter param = new JsonCallParameter.JsonCallParameterBuilder()
+ *     .withChainId(Optional.of(BigInteger.ONE))
  *     .withFrom(Address.fromHexString("0x..."))
  *     .withTo(Address.fromHexString("0x..."))
  *     .withGas(21000L)
@@ -71,6 +74,7 @@ public class JsonCallParameter extends CallParameter {
   private final Optional strict;
 
   private JsonCallParameter(
+      final Optional chainId,
       final Address from,
       final Address to,
       final Long gasLimit,
@@ -85,6 +89,7 @@ public class JsonCallParameter extends CallParameter {
       final Optional> blobVersionedHashes) {
 
     super(
+        chainId,
         from,
         to,
         gasLimit,
@@ -115,6 +120,7 @@ public class JsonCallParameter extends CallParameter {
    */
   public static final class JsonCallParameterBuilder {
     private Optional strict = Optional.empty();
+    private Optional chainId = Optional.empty();
     private Address from;
     private Address to;
     private long gas = -1;
@@ -145,6 +151,18 @@ public class JsonCallParameter extends CallParameter {
       return this;
     }
 
+    /**
+     * Sets the optional "chainId" for the {@link JsonCallParameter}.
+     *
+     * @param chainId the chainId
+     * @return the {@link JsonCallParameterBuilder} instance for chaining
+     */
+    @JsonDeserialize(using = ChainIdDeserializer.class)
+    public JsonCallParameterBuilder withChainId(final BigInteger chainId) {
+      this.chainId = Optional.of(chainId);
+      return this;
+    }
+
     /**
      * Sets the "from" address for the {@link JsonCallParameter}. This address represents the sender
      * of the call.
@@ -346,6 +364,7 @@ public class JsonCallParameter extends CallParameter {
       final Bytes payload = input != null ? input : data;
 
       return new JsonCallParameter(
+          chainId,
           from,
           to,
           gas,
diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/json/ChainIdDeserializer.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/json/ChainIdDeserializer.java
new file mode 100644
index 0000000000..04be922ecd
--- /dev/null
+++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/json/ChainIdDeserializer.java
@@ -0,0 +1,46 @@
+/*
+ * 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.core.json;
+
+import java.io.IOException;
+import java.math.BigInteger;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+import org.apache.tuweni.units.bigints.UInt256;
+
+public class ChainIdDeserializer extends StdDeserializer {
+
+  public ChainIdDeserializer() {
+    this(null);
+  }
+
+  public ChainIdDeserializer(final Class vc) {
+    super(vc);
+  }
+
+  @Override
+  public BigInteger deserialize(final JsonParser jsonparser, final DeserializationContext context)
+      throws IOException {
+    final var chainId =
+        UInt256.fromHexString(jsonparser.getCodec().readValue(jsonparser, String.class))
+            .toBigInteger();
+    if (chainId.signum() <= 0) {
+      throw new IllegalArgumentException("Non positive chain id: " + chainId);
+    }
+    return chainId;
+  }
+}
diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/CallParameter.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/CallParameter.java
index 292c65cac6..ce2e63a71a 100644
--- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/CallParameter.java
+++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/CallParameter.java
@@ -20,6 +20,7 @@ import org.hyperledger.besu.datatypes.VersionedHash;
 import org.hyperledger.besu.datatypes.Wei;
 import org.hyperledger.besu.ethereum.core.Transaction;
 
+import java.math.BigInteger;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
@@ -28,6 +29,7 @@ import org.apache.tuweni.bytes.Bytes;
 
 // Represents parameters for eth_call and eth_estimateGas JSON-RPC methods.
 public class CallParameter {
+  private final Optional chainId;
 
   private final Address from;
 
@@ -56,6 +58,7 @@ public class CallParameter {
       final Wei gasPrice,
       final Wei value,
       final Bytes payload) {
+    this.chainId = Optional.empty();
     this.from = from;
     this.to = to;
     this.gasLimit = gasLimit;
@@ -79,6 +82,7 @@ public class CallParameter {
       final Wei value,
       final Bytes payload,
       final Optional> accessList) {
+    this.chainId = Optional.empty();
     this.from = from;
     this.to = to;
     this.gasLimit = gasLimit;
@@ -93,6 +97,7 @@ public class CallParameter {
   }
 
   public CallParameter(
+      final Optional chainId,
       final Address from,
       final Address to,
       final long gasLimit,
@@ -104,6 +109,7 @@ public class CallParameter {
       final Optional> accessList,
       final Optional maxFeePerBlobGas,
       final Optional> blobVersionedHashes) {
+    this.chainId = chainId;
     this.from = from;
     this.to = to;
     this.gasLimit = gasLimit;
@@ -117,6 +123,10 @@ public class CallParameter {
     this.blobVersionedHashes = blobVersionedHashes;
   }
 
+  public Optional getChainId() {
+    return chainId;
+  }
+
   public Address getFrom() {
     return from;
   }
@@ -171,6 +181,7 @@ public class CallParameter {
     }
     final CallParameter that = (CallParameter) o;
     return gasLimit == that.gasLimit
+        && chainId.equals(that.chainId)
         && Objects.equals(from, that.from)
         && Objects.equals(to, that.to)
         && Objects.equals(gasPrice, that.gasPrice)
@@ -186,6 +197,7 @@ public class CallParameter {
   @Override
   public int hashCode() {
     return Objects.hash(
+        chainId,
         from,
         to,
         gasLimit,
@@ -202,7 +214,9 @@ public class CallParameter {
   @Override
   public String toString() {
     return "CallParameter{"
-        + "from="
+        + "chainId="
+        + chainId
+        + ", from="
         + from
         + ", to="
         + to
@@ -229,6 +243,7 @@ public class CallParameter {
 
   public static CallParameter fromTransaction(final Transaction tx) {
     return new CallParameter(
+        tx.getChainId(),
         tx.getSender(),
         tx.getTo().orElse(null),
         tx.getGasLimit(),
@@ -244,6 +259,7 @@ public class CallParameter {
 
   public static CallParameter fromTransaction(final org.hyperledger.besu.datatypes.Transaction tx) {
     return new CallParameter(
+        tx.getChainId(),
         tx.getSender(),
         tx.getTo().orElse(null),
         tx.getGasLimit(),
diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulator.java
index 5e78256a78..321dc965ec 100644
--- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulator.java
+++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulator.java
@@ -369,10 +369,13 @@ public class TransactionSimulator {
       transactionBuilder.maxFeePerBlobGas(maxFeePerBlobGas);
     }
     if (transactionBuilder.getTransactionType().requiresChainId()) {
-      transactionBuilder.chainId(
-          protocolSchedule
-              .getChainId()
-              .orElse(BigInteger.ONE)); // needed to make some transactions valid
+      callParams
+          .getChainId()
+          .ifPresentOrElse(
+              transactionBuilder::chainId,
+              () ->
+                  // needed to make some transactions valid
+                  transactionBuilder.chainId(protocolSchedule.getChainId().orElse(BigInteger.ONE)));
     }
 
     final Transaction transaction = transactionBuilder.build();
diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulatorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulatorTest.java
index 73fa402abb..c6c676c415 100644
--- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulatorTest.java
+++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulatorTest.java
@@ -858,6 +858,7 @@ public class TransactionSimulatorTest {
       final int numberOfBlobs) {
     BlobsWithCommitments bwc = new BlobTestFixture().createBlobsWithCommitments(numberOfBlobs);
     return new CallParameter(
+        Optional.of(BigInteger.ONE),
         Address.fromHexString("0x0"),
         Address.fromHexString("0x0"),
         gasLimit,