Skip to content

Commit

Permalink
Honor block number or tag parameter in eth_estimateGas and eth_create…
Browse files Browse the repository at this point in the history
…AccessList

Signed-off-by: Fabio Di Fabio <fabio.difabio@consensys.net>
  • Loading branch information
fab-10 committed Sep 2, 2024
1 parent 4c4f2f3 commit b0f901d
Show file tree
Hide file tree
Showing 9 changed files with 403 additions and 256 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,19 @@
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods;

import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonCallParameterUtil.validateAndGetCallParams;

import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcErrorConverter;
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.JsonCallParameter;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter;
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.RpcErrorType;
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.mainnet.ValidationResult;
import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult;
Expand All @@ -34,32 +38,68 @@

import java.util.Optional;

public abstract class AbstractEstimateGas implements JsonRpcMethod {
public abstract class AbstractEstimateGas extends AbstractBlockParameterMethod {

private static final double SUB_CALL_REMAINING_GAS_RATIO = 65D / 64D;

protected final BlockchainQueries blockchainQueries;
protected final TransactionSimulator transactionSimulator;

public AbstractEstimateGas(
final BlockchainQueries blockchainQueries, final TransactionSimulator transactionSimulator) {
this.blockchainQueries = blockchainQueries;
super(blockchainQueries);
this.transactionSimulator = transactionSimulator;
}

protected BlockHeader blockHeader() {
final Blockchain theChain = blockchainQueries.getBlockchain();

// Optimistically get the block header for the chain head without taking a lock,
// but revert to the safe implementation if it returns an empty optional. (It's
// possible the chain head has been updated but the block is still being persisted
// to storage/cache under the lock).
return theChain
.getBlockHeader(theChain.getChainHeadHash())
.or(() -> theChain.getBlockHeaderSafe(theChain.getChainHeadHash()))
.orElse(null);
@Override
protected BlockParameter blockParameter(final JsonRpcRequestContext request) {
try {
return request.getOptionalParameter(1, BlockParameter.class).orElse(BlockParameter.LATEST);
} catch (JsonRpcParameter.JsonRpcParameterException e) {
throw new InvalidJsonRpcParameters(
"Invalid block parameter (index 1)", RpcErrorType.INVALID_BLOCK_PARAMS, e);
}
}

protected Optional<BlockHeader> blockHeader(final long blockNumber) {
if (getBlockchainQueries().headBlockNumber() == blockNumber) {
// chain head header if cached, and we can return it form memory
return Optional.of(getBlockchainQueries().getBlockchain().getChainHeadHeader());
}
return getBlockchainQueries().getBlockHeaderByNumber(blockNumber);
}

protected Optional<RpcErrorType> validateBlockHeader(
final Optional<BlockHeader> maybeBlockHeader) {
if (maybeBlockHeader.isEmpty()) {
return Optional.of(RpcErrorType.BLOCK_NOT_FOUND);
}

final var blockHeader = maybeBlockHeader.get();
if (!getBlockchainQueries()
.getWorldStateArchive()
.isWorldStateAvailable(blockHeader.getStateRoot(), blockHeader.getHash())) {
return Optional.of(RpcErrorType.WORLD_STATE_UNAVAILABLE);
}
return Optional.empty();
}

@Override
protected Object resultByBlockNumber(
final JsonRpcRequestContext requestContext, final long blockNumber) {
final JsonCallParameter jsonCallParameter = validateAndGetCallParams(requestContext);
final Optional<BlockHeader> maybeBlockHeader = blockHeader(blockNumber);
final Optional<RpcErrorType> jsonRpcError = validateBlockHeader(maybeBlockHeader);
if (jsonRpcError.isPresent()) {
return errorResponse(requestContext, jsonRpcError.get());
}
return resultByBlockHeader(requestContext, jsonCallParameter, maybeBlockHeader.get());
}

protected abstract Object resultByBlockHeader(
final JsonRpcRequestContext requestContext,
final JsonCallParameter jsonCallParameter,
final BlockHeader blockHeader);

protected CallParameter overrideGasLimitAndPrice(
final JsonCallParameter callParams, final long gasLimit) {
return new CallParameter(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,11 @@
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods;

import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonCallParameterUtil.validateAndGetCallParams;

import org.hyperledger.besu.datatypes.AccessListEntry;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonCallParameter;
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.jsonrpc.internal.response.RpcErrorType;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.CreateAccessListResult;
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
Expand Down Expand Up @@ -52,44 +48,29 @@ public String getName() {
}

@Override
public JsonRpcResponse response(final JsonRpcRequestContext requestContext) {
final JsonCallParameter jsonCallParameter = validateAndGetCallParams(requestContext);
final BlockHeader blockHeader = blockHeader();
final Optional<RpcErrorType> jsonRpcError = validateBlockHeader(blockHeader);
if (jsonRpcError.isPresent()) {
return errorResponse(requestContext, jsonRpcError.get());
}
protected Object resultByBlockHeader(
final JsonRpcRequestContext requestContext,
final JsonCallParameter jsonCallParameter,
final BlockHeader blockHeader) {
final AccessListSimulatorResult maybeResult =
processTransaction(jsonCallParameter, blockHeader);
// if the call accessList is different from the simulation result, calculate gas and return
if (shouldProcessWithAccessListOverride(jsonCallParameter, maybeResult.getTracer())) {
if (shouldProcessWithAccessListOverride(jsonCallParameter, maybeResult.tracer())) {
final AccessListSimulatorResult result =
processTransactionWithAccessListOverride(
jsonCallParameter, blockHeader, maybeResult.getTracer().getAccessList());
jsonCallParameter, blockHeader, maybeResult.tracer().getAccessList());
return createResponse(requestContext, result);
} else {
return createResponse(requestContext, maybeResult);
}
}

private Optional<RpcErrorType> validateBlockHeader(final BlockHeader blockHeader) {
if (blockHeader == null) {
return Optional.of(RpcErrorType.INTERNAL_ERROR);
}
if (!blockchainQueries
.getWorldStateArchive()
.isWorldStateAvailable(blockHeader.getStateRoot(), blockHeader.getHash())) {
return Optional.of(RpcErrorType.WORLD_STATE_UNAVAILABLE);
}
return Optional.empty();
}

private JsonRpcResponse createResponse(
private Object createResponse(
final JsonRpcRequestContext requestContext, final AccessListSimulatorResult result) {
return result
.getResult()
.map(createResponse(requestContext, result.getTracer()))
.orElse(errorResponse(requestContext, RpcErrorType.INTERNAL_ERROR));
.result()
.map(createResponse(requestContext, result.tracer()))
.orElseGet(() -> errorResponse(requestContext, RpcErrorType.INTERNAL_ERROR));
}

private TransactionValidationParams transactionValidationParams(
Expand Down Expand Up @@ -117,14 +98,12 @@ private boolean shouldProcessWithAccessListOverride(
return !Objects.equals(tracer.getAccessList(), parameters.getAccessList().get());
}

private Function<TransactionSimulatorResult, JsonRpcResponse> createResponse(
private Function<TransactionSimulatorResult, Object> createResponse(
final JsonRpcRequestContext request, final AccessListOperationTracer operationTracer) {
return result ->
result.isSuccessful()
? new JsonRpcSuccessResponse(
request.getRequest().getId(),
new CreateAccessListResult(
operationTracer.getAccessList(), processEstimateGas(result, operationTracer)))
? new CreateAccessListResult(
operationTracer.getAccessList(), processEstimateGas(result, operationTracer))
: errorResponse(request, result);
}

Expand All @@ -138,8 +117,7 @@ private AccessListSimulatorResult processTransaction(

final AccessListOperationTracer tracer = AccessListOperationTracer.create();
final Optional<TransactionSimulatorResult> result =
transactionSimulator.process(
callParams, transactionValidationParams, tracer, blockHeader.getNumber());
transactionSimulator.process(callParams, transactionValidationParams, tracer, blockHeader);
return new AccessListSimulatorResult(result, tracer);
}

Expand All @@ -156,7 +134,7 @@ private AccessListSimulatorResult processTransactionWithAccessListOverride(

final Optional<TransactionSimulatorResult> result =
transactionSimulator.process(
callParameter, transactionValidationParams, tracer, blockHeader.getNumber());
callParameter, transactionValidationParams, tracer, blockHeader);
return new AccessListSimulatorResult(result, tracer);
}

Expand All @@ -176,22 +154,6 @@ protected CallParameter overrideAccessList(
Optional.ofNullable(accessListEntries));
}

private static class AccessListSimulatorResult {
final Optional<TransactionSimulatorResult> result;
final AccessListOperationTracer tracer;

public AccessListSimulatorResult(
final Optional<TransactionSimulatorResult> result, final AccessListOperationTracer tracer) {
this.result = result;
this.tracer = tracer;
}

public Optional<TransactionSimulatorResult> getResult() {
return result;
}

public AccessListOperationTracer getTracer() {
return tracer;
}
}
private record AccessListSimulatorResult(
Optional<TransactionSimulatorResult> result, AccessListOperationTracer tracer) {}
}
Loading

0 comments on commit b0f901d

Please sign in to comment.