Skip to content

Commit

Permalink
Improve linea_estimateGas error response
Browse files Browse the repository at this point in the history
Signed-off-by: Fabio Di Fabio <fabio.difabio@consensys.net>
  • Loading branch information
fab-10 committed Mar 21, 2024
1 parent 41a989e commit 61d6f54
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,13 @@

import java.io.IOException;
import java.math.BigInteger;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;

import linea.plugin.acc.test.LineaPluginTestBase;
import linea.plugin.acc.test.TestCommandLineOptionsBuilder;
Expand All @@ -32,12 +37,15 @@
import org.bouncycastle.crypto.digests.KeccakDigest;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType;
import org.hyperledger.besu.tests.acceptance.dsl.account.Account;
import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests;
import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.web3j.protocol.core.Request;
import org.web3j.protocol.core.Response;
import org.web3j.protocol.http.HttpService;

public class EstimateGasTest extends LineaPluginTestBase {
protected static final int VERIFICATION_GAS_COST = 1_200_000;
Expand Down Expand Up @@ -183,6 +191,36 @@ public void lineaEstimateGasPriorityFeeMinGasPriceLowerBound() {
assertMinGasPriceLowerBound(baseFee, estimatedMaxGasPrice);
}

@Test
public void invalidParametersLineaEstimateGasRequestReturnErrorResponse() {
final var reqLinea =
new BadLineaEstimateGasRequest(Map.of("gasLimit", String.valueOf(Integer.MAX_VALUE)));
final var respLinea = reqLinea.execute(minerNode.nodeRequests());
assertThat(respLinea.getCode()).isEqualTo(RpcErrorType.INVALID_PARAMS.getCode());
assertThat(respLinea.getMessage()).isEqualTo(RpcErrorType.INVALID_PARAMS.getMessage());
}

@Test
public void parseErrorLineaEstimateGasRequestReturnErrorResponse()
throws IOException, InterruptedException {
final var httpService = (HttpService) minerNode.nodeRequests().getWeb3jService();
final var httpClient = HttpClient.newHttpClient();
final var badJsonRequest =
HttpRequest.newBuilder(URI.create(httpService.getUrl()))
.headers("Content-Type", "application/json")
.POST(
HttpRequest.BodyPublishers.ofString(
"""
{"jsonrpc":"2.0","method":"linea_estimateGas","params":[malformed json],"id":53}
"""))
.build();
final var errorResponse = httpClient.send(badJsonRequest, HttpResponse.BodyHandlers.ofString());
assertThat(errorResponse.body())
.isEqualTo(
"""
{"jsonrpc":"2.0","id":null,"error":{"code":-32700,"message":"Parse error"}}""");
}

protected void assertMinGasPriceLowerBound(final Wei baseFee, final Wei estimatedMaxGasPrice) {
final var minGasPrice = minerNode.getMiningParameters().getMinTransactionGasPrice();
assertThat(estimatedMaxGasPrice).isEqualTo(minGasPrice);
Expand Down Expand Up @@ -215,6 +253,34 @@ static class LineaEstimateGasResponse extends org.web3j.protocol.core.Response<R
record Response(String gasLimit, String baseFeePerGas, String priorityFeePerGas) {}
}

static class BadLineaEstimateGasRequest
implements Transaction<org.web3j.protocol.core.Response.Error> {
private final Map<String, String> badCallParams;

public BadLineaEstimateGasRequest(final Map<String, String> badCallParams) {
this.badCallParams = badCallParams;
}

@Override
public org.web3j.protocol.core.Response.Error execute(final NodeRequests nodeRequests) {
try {
return new Request<>(
"linea_estimateGas",
List.of(badCallParams),
nodeRequests.getWeb3jService(),
BadLineaEstimateGasResponse.class)
.send()
.getError();
} catch (IOException e) {
throw new RuntimeException(e);
}
}

static class BadLineaEstimateGasResponse extends org.web3j.protocol.core.Response<Response> {}

record Response(String gasLimit, String baseFeePerGas, String priorityFeePerGas) {}
}

static class RawEstimateGasRequest implements Transaction<String> {
private final CallParams callParams;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.google.common.cache.CacheBuilder;
import lombok.extern.slf4j.Slf4j;
import net.consensys.linea.zktracer.ZkTracer;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType;
import org.hyperledger.besu.plugin.BesuContext;
import org.hyperledger.besu.plugin.services.TraceService;
import org.hyperledger.besu.plugin.services.exception.PluginRpcEndpointException;
Expand Down Expand Up @@ -97,7 +98,7 @@ public Counters execute(final PluginRpcRequest request) {
log.info("counters for {} returned in {}", requestedBlockNumber, sw);
return r;
} catch (Exception ex) {
throw new PluginRpcEndpointException(ex.getMessage());
throw new PluginRpcEndpointException(RpcErrorType.PLUGIN_INTERNAL_ERROR, ex.getMessage());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.util.Optional;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.annotations.VisibleForTesting;
Expand All @@ -37,12 +38,15 @@
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonCallParameter;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.evm.tracing.EstimateGasOperationTracer;
import org.hyperledger.besu.plugin.services.BesuConfiguration;
import org.hyperledger.besu.plugin.services.BlockchainService;
import org.hyperledger.besu.plugin.services.TransactionSimulationService;
import org.hyperledger.besu.plugin.services.exception.PluginRpcEndpointException;
import org.hyperledger.besu.plugin.services.rpc.PluginRpcRequest;
import org.hyperledger.besu.plugin.services.rpc.RpcMethodError;

@Slf4j
public class LineaEstimateGas {
Expand Down Expand Up @@ -117,7 +121,10 @@ public LineaEstimateGas.Response execute(final PluginRpcRequest request) {
final Wei baseFee =
blockchainService
.getNextBlockBaseFee()
.orElseThrow(() -> new IllegalStateException("Not on a baseFee market"));
.orElseThrow(
() ->
new PluginRpcEndpointException(
RpcErrorType.INVALID_REQUEST, "Not on a baseFee market"));

final Wei estimatedPriorityFee =
getEstimatedPriorityFee(transaction, baseFee, minGasPrice, estimatedGasUsed);
Expand Down Expand Up @@ -174,19 +181,32 @@ private Long estimateGasUsed(
return maybeSimulationResults
.map(
r -> {

// if the transaction is invalid or doesn't have enough gas with the max it never
// will!
if (r.isInvalid() || !r.isSuccessful()) {
// if the transaction is invalid or doesn't have enough gas with the max it never will
if (r.isInvalid()) {
log.atDebug()
.setMessage("Invalid or unsuccessful transaction {}, reason {}")
.setMessage("Invalid transaction {}, reason {}")
.addArgument(transaction::toTraceLog)
.addArgument(r.result())
.log();
throw new PluginRpcEndpointException(
new InvalidTransactionError(r.result().getInvalidReason()));
}
if (!r.isSuccessful()) {
log.atDebug()
.setMessage("Failed transaction {}, reason {}")
.addArgument(transaction::toTraceLog)
.addArgument(r.result())
.log();
r.getRevertReason()
.ifPresent(
rr -> {
throw new PluginRpcEndpointException(
RpcErrorType.REVERT_ERROR, rr.toHexString());
});
final var invalidReason = r.result().getInvalidReason();
throw new RuntimeException(
"Invalid or unsuccessful transaction"
+ invalidReason.map(ir -> ", reason: " + ir).orElse(""));
throw new PluginRpcEndpointException(
RpcErrorType.PLUGIN_INTERNAL_ERROR,
"Failed transaction" + invalidReason.map(ir -> ", reason: " + ir).orElse(""));
}

final var lowGasEstimation = r.result().getEstimateGasUsedByTransaction();
Expand All @@ -200,8 +220,7 @@ private Long estimateGasUsed(
return lowResult
.map(
lr -> {
// if with the low estimation gas is successful the return this
// estimation
// if with the low estimation gas is successful the return this estimation
if (lr.isSuccessful()) {
log.trace(
"Low gas estimation {} successful, call params {}",
Expand Down Expand Up @@ -232,35 +251,42 @@ private Long estimateGasUsed(

if (binarySearchResult.isEmpty()
|| !binarySearchResult.get().isSuccessful()) {
low = mid;
log.atTrace()
.setMessage(
"Binary gas estimation search low={},med={},high={}, unsuccessful result {}, call params {}")
.addArgument(lowGasEstimation)
"Binary gas estimation search low={},mid={},high={}, unsuccessful result {}, call params {}")
.addArgument(low)
.addArgument(mid)
.addArgument(high)
.addArgument(
() ->
binarySearchResult
.map(result -> result.result().toString())
.orElse("empty"))
.addArgument(callParameters)
.log();

low = mid;
} else {
high = mid;
log.trace(
"Binary gas estimation search low={},med={},high={}, successful, call params {}",
"Binary gas estimation search low={},mid={},high={}, successful, call params {}",
low,
mid,
high,
callParameters);
high = mid;
}
}
return high;
}
})
.orElseThrow();
.orElseThrow(
() ->
new PluginRpcEndpointException(
RpcErrorType.PLUGIN_INTERNAL_ERROR, "Empty result from simulation"));
})
.orElseThrow();
.orElseThrow(
() ->
new PluginRpcEndpointException(
RpcErrorType.PLUGIN_INTERNAL_ERROR, "Empty result from simulation"));
}

private JsonCallParameter parseRequest(final Object[] params) {
Expand All @@ -272,14 +298,16 @@ private JsonCallParameter parseRequest(final Object[] params) {
private void validateParameters(final JsonCallParameter callParameters) {
if (callParameters.getGasPrice() != null
&& (callParameters.getMaxFeePerGas().isPresent()
|| callParameters.getMaxPriorityFeePerGas().isPresent())) {
|| callParameters.getMaxPriorityFeePerGas().isPresent()
|| callParameters.getMaxFeePerBlobGas().isPresent())) {
throw new InvalidJsonRpcParameters(
"gasPrice cannot be used with maxFeePerGas or maxPriorityFeePerGas");
"gasPrice cannot be used with maxFeePerGas or maxPriorityFeePerGas or maxFeePerBlobGas");
}

if (callParameters.getGasLimit() > 0
&& callParameters.getGasLimit() > txValidatorConf.maxTxGasLimit()) {
throw new InvalidJsonRpcParameters("gasLimit above maximum");
throw new InvalidJsonRpcParameters(
"gasLimit above maximum of: " + txValidatorConf.maxTxGasLimit());
}
}

Expand Down Expand Up @@ -327,4 +355,18 @@ public record Response(
@JsonProperty String gasLimit,
@JsonProperty String baseFeePerGas,
@JsonProperty String priorityFeePerGas) {}

private record InvalidTransactionError(Optional<String> maybeInvalidReason)
implements RpcMethodError {

@Override
public int getCode() {
return -32000;
}

@Override
public String getMessage() {
return maybeInvalidReason.orElse("");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.google.common.base.Stopwatch;
import lombok.extern.slf4j.Slf4j;
import net.consensys.linea.zktracer.ZkTracer;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType;
import org.hyperledger.besu.plugin.BesuContext;
import org.hyperledger.besu.plugin.services.BesuConfiguration;
import org.hyperledger.besu.plugin.services.TraceService;
Expand Down Expand Up @@ -85,7 +86,7 @@ public TraceFile execute(final PluginRpcRequest request) {
log.info("[TRACING] trace for {}-{} serialized to {} in {}", path, toBlock, fromBlock, sw);
return new TraceFile(params.runtimeVersion(), path);
} catch (Exception ex) {
throw new PluginRpcEndpointException(ex.getMessage());
throw new PluginRpcEndpointException(RpcErrorType.PLUGIN_INTERNAL_ERROR, ex.getMessage());
}
}

Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
releaseVersion=0.1.4-SNAPSHOT
besuVersion=24.2.0-SNAPSHOT
besuVersion=24.3-develop-5a0618f151
besuArtifactGroup=io.consensys.linea-besu
distributionIdentifier=besu-sequencer-plugins
distributionBaseUrl=https://artifacts.consensys.net/public/linea-besu/raw/names/linea-besu.tar.gz/versions/
Expand Down
5 changes: 3 additions & 2 deletions native/Dockerfile-win-dockcross
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
FROM dockcross/windows-shared-x64
ARG IMAGE
FROM dockcross/$IMAGE

ARG GO_VERSION=1.22.1
ARG GO_ARCHIVE_CHECKSUM=aab8e15785c997ae20f9c88422ee35d962c4562212bb0f879d052a35c8307c7f

ENV DEFAULT_DOCKCROSS_IMAGE native-windows-cross-compile
ENV DEFAULT_DOCKCROSS_IMAGE native-${IMAGE}-cross-compile

RUN wget https://go.dev/dl/go$GO_VERSION.linux-amd64.tar.gz
RUN echo "$GO_ARCHIVE_CHECKSUM go$GO_VERSION.linux-amd64.tar.gz" | sha256sum --check --status
Expand Down
15 changes: 11 additions & 4 deletions native/wsl.sh
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
#!/bin/bash
#!/bin/bash -x

docker build -f Dockerfile-win-dockcross -t native-windows-cross-compile .
docker build -f Dockerfile-win-dockcross --build-arg IMAGE=windows-shared-x64 -t native-windows-shared-x64-cross-compile .
docker build -f Dockerfile-win-dockcross --build-arg IMAGE=linux-x64 -t native-linux-x64-cross-compile .

docker run --rm native-windows-cross-compile > compress/build/native/native-windows-cross-compile
mkdir -p compress/build/native/

compress/build/native/native-windows-cross-compile \
docker run --rm native-windows-shared-x64-cross-compile > compress/build/native/native-windows-shared-x64-cross-compile
docker run --rm native-linux-x64-cross-compile > compress/build/native/native-linux-x64-cross-compile

compress/build/native/native-windows-shared-x64-cross-compile --image native-windows-shared-x64-cross-compile \
bash -c "cd compress/compress-jni &&
CGO_ENABLED=1 GOOS=windows GOARCH=amd64 go build -buildmode=c-shared -o ../build/native/compress_jni.dll compress-jni.go"

compress/build/native/native-linux-x64-cross-compile --image native-linux-x64-cross-compile \
bash -c "cd compress/compress-jni &&
CGO_ENABLED=1 go build -buildmode=c-shared -o ../build/native/libcompress_jni.so compress-jni.go"

0 comments on commit 61d6f54

Please sign in to comment.