Add implementation for eth_createAccessList RPC method (#4942)

* Implement method EthCreateAccessList

Signed-off-by: Gabriel Trintinalia <gabriel.trintinalia@gmail.com>

* Add test resource file

Signed-off-by: Gabriel Trintinalia <gabriel.trintinalia@gmail.com>

* Change changelog

Signed-off-by: Gabriel Trintinalia <gabriel.trintinalia@gmail.com>

* fix javadoc

Signed-off-by: Gabriel Trintinalia <gabriel.trintinalia@gmail.com>

* Invert logic and rename methods to improve readability

Signed-off-by: Gabriel Trintinalia <gabriel.trintinalia@gmail.com>

* Introduce abstract class to improve readability

Signed-off-by: Gabriel Trintinalia <gabriel.trintinalia@gmail.com>

* Add copyright

Signed-off-by: Gabriel Trintinalia <gabriel.trintinalia@gmail.com>

* Update evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java

Signed-off-by: Sally MacFarlane <macfarla.github@gmail.com>

* Update testutil/src/main/java/org/hyperledger/besu/testutil/BlockTestUtil.java

Signed-off-by: Sally MacFarlane <macfarla.github@gmail.com>

* Update testutil/src/main/java/org/hyperledger/besu/testutil/BlockTestUtil.java

Signed-off-by: Sally MacFarlane <macfarla.github@gmail.com>

Signed-off-by: Gabriel Trintinalia <gabriel.trintinalia@gmail.com>
Signed-off-by: Sally MacFarlane <macfarla.github@gmail.com>
Co-authored-by: Sally MacFarlane <macfarla.github@gmail.com>
pull/4973/head
Gabriel-Trintinalia 2 years ago committed by GitHub
parent f4a6219456
commit c4dbc58a5e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 267
      ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthCreateAccessListIntegrationTest.java
  3. 1
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java
  4. 123
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractEstimateGas.java
  5. 195
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCreateAccessList.java
  6. 93
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGas.java
  7. 42
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/CreateAccessListResult.java
  8. 8
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/EthJsonRpcMethods.java
  9. 342
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCreateAccessListTest.java
  10. 8
      evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java
  11. 67
      evm/src/main/java/org/hyperledger/besu/evm/tracing/AccessListOperationTracer.java
  12. 24
      testutil/src/main/java/org/hyperledger/besu/testutil/BlockTestUtil.java
  13. BIN
      testutil/src/main/resources/test-eth-ref-rpc-compact/chain.blocks
  14. 58
      testutil/src/main/resources/test-eth-ref-rpc-compact/genesis.json

@ -10,6 +10,7 @@
### Additions and Improvements
- Added option to evm CLI tool to allow code execution at specific forks [#4913](https://github.com/hyperledger/besu/pull/4913)
- Improve get account performance by using the world state updater cache [#4897](https://github.com/hyperledger/besu/pull/4897)
- Add implementation for eth_createAccessList RPC method [#4942](https://github.com/hyperledger/besu/pull/4942)
### Bug Fixes

@ -0,0 +1,267 @@
/*
* 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.methods.fork.frontier;
import static org.assertj.core.api.Assertions.assertThat;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.api.jsonrpc.BlockchainImporter;
import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcTestMethodsFactory;
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.JsonRpcResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.CreateAccessListResult;
import org.hyperledger.besu.evm.AccessListEntry;
import org.hyperledger.besu.testutil.BlockTestUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import com.google.common.base.Charsets;
import com.google.common.io.Resources;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.units.bigints.UInt256;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class EthCreateAccessListIntegrationTest {
private static JsonRpcTestMethodsFactory BLOCKCHAIN;
private JsonRpcMethod method;
@BeforeAll
public static void setUpOnce() throws Exception {
final String genesisJson =
Resources.toString(BlockTestUtil.getEthRefTestResources().getGenesisURL(), Charsets.UTF_8);
BLOCKCHAIN =
new JsonRpcTestMethodsFactory(
new BlockchainImporter(
BlockTestUtil.getEthRefTestResources().getBlocksURL(), genesisJson));
}
@BeforeEach
public void setUp() {
final Map<String, JsonRpcMethod> methods = BLOCKCHAIN.methods();
method = methods.get("eth_createAccessList");
}
@Test
public void shouldSucceedWhenCreateAccessListMultipleReads() {
final long expectedGasUsed = 0x6b0e;
final List<AccessListEntry> expectedAccessListEntryList =
List.of(
new AccessListEntry(
Address.fromHexString("0xbb00000000000000000000000000000000000000"),
List.of(
UInt256.fromHexString(
"0x0000000000000000000000000000000000000000000000000000000000000001"),
UInt256.fromHexString(
"0x0000000000000000000000000000000000000000000000000000000000000003"))));
final JsonCallParameter callParameter =
createAccessListJsonCallParameters(
"0x658bdf435d810c91414ec09147daa6db62406379",
"0xbb00000000000000000000000000000000000000",
null);
assertAccessListExpectedResult(callParameter, expectedAccessListEntryList, expectedGasUsed);
}
@Test
public void shouldSucceedWhenCreateAccessListMultipleReads_withAccessListParam() {
final long expectedGasUsed = 0x6b0e;
final List<AccessListEntry> expectedAccessListEntryList =
List.of(
new AccessListEntry(
Address.fromHexString("0xbb00000000000000000000000000000000000000"),
List.of(
UInt256.fromHexString(
"0x0000000000000000000000000000000000000000000000000000000000000001"),
UInt256.fromHexString(
"0x0000000000000000000000000000000000000000000000000000000000000003"))));
final JsonCallParameter callParameter =
createAccessListJsonCallParameters(
"0x658bdf435d810c91414ec09147daa6db62406379",
"0xbb00000000000000000000000000000000000000",
expectedAccessListEntryList);
assertAccessListExpectedResult(callParameter, expectedAccessListEntryList, expectedGasUsed);
}
@Test
public void shouldSucceedWhenCreateAccessListSimpleTransfer() {
final long expectedGasUsed = 0x5208;
final List<AccessListEntry> expectedAccessListEntryList = new ArrayList<>();
final JsonCallParameter callParameter =
createAccessListJsonCallParameters(
"0x658bdf435d810c91414ec09147daa6db62406379",
"0x0100000000000000000000000000000000000000",
expectedAccessListEntryList);
assertAccessListExpectedResult(callParameter, expectedAccessListEntryList, expectedGasUsed);
}
@Test
public void shouldSucceedWhenCreateAccessListSimpleContract() {
final long expectedGasUsed = 0x520b;
final List<AccessListEntry> expectedAccessListEntryList = new ArrayList<>();
final JsonCallParameter callParameter =
createAccessListJsonCallParameters(
"0x658bdf435d810c91414ec09147daa6db62406379",
"0xaa00000000000000000000000000000000000000",
null);
assertAccessListExpectedResult(callParameter, expectedAccessListEntryList, expectedGasUsed);
}
@Test
public void shouldReturnExpectedValueForEmptyCallParameter() {
final JsonCallParameter callParameter =
new JsonCallParameter(null, null, null, null, null, null, null, null, null, null);
final JsonRpcRequestContext request = requestWithParams(callParameter);
final JsonRpcResponse expectedResponse =
new JsonRpcSuccessResponse(null, new CreateAccessListResult(new ArrayList<>(), 0xcf08));
final JsonRpcResponse response = method.response(request);
assertThat(response).usingRecursiveComparison().isEqualTo(expectedResponse);
}
@Test
public void shouldReturnExpectedValueForTransfer() {
final JsonCallParameter callParameter =
new JsonCallParameter(
Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"),
Address.fromHexString("0x8888f1f195afa192cfee860698584c030f4c9db1"),
null,
null,
null,
null,
Wei.ZERO,
null,
null,
null);
final JsonRpcRequestContext request = requestWithParams(callParameter);
final JsonRpcResponse expectedResponse =
new JsonRpcSuccessResponse(null, new CreateAccessListResult(new ArrayList<>(), 0x5208));
final JsonRpcResponse response = method.response(request);
assertThat(response).usingRecursiveComparison().isEqualTo(expectedResponse);
}
@Test
public void shouldReturnExpectedValueForContractDeploy() {
final JsonCallParameter callParameter =
new JsonCallParameter(
Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"),
null,
null,
null,
null,
null,
null,
Bytes.fromHexString(
"0x608060405234801561001057600080fd5b50610157806100206000396000f30060806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633bdab8bf146100515780639ae97baa14610068575b600080fd5b34801561005d57600080fd5b5061006661007f565b005b34801561007457600080fd5b5061007d6100b9565b005b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60016040518082815260200191505060405180910390a1565b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60026040518082815260200191505060405180910390a17fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60036040518082815260200191505060405180910390a15600a165627a7a7230582010ddaa52e73a98c06dbcd22b234b97206c1d7ed64a7c048e10c2043a3d2309cb0029"),
null,
null);
final JsonRpcRequestContext request = requestWithParams(callParameter);
final JsonRpcResponse expectedResponse =
new JsonRpcSuccessResponse(null, new CreateAccessListResult(new ArrayList<>(), 0x1f081));
final JsonRpcResponse response = method.response(request);
assertThat(response).usingRecursiveComparison().isEqualTo(expectedResponse);
}
@Test
public void shouldIgnoreSenderBalanceAccountWhenStrictModeDisabledAndReturnExpectedValue() {
final JsonCallParameter callParameter =
new JsonCallParameter(
Address.fromHexString("0x0000000000000000000000000000000000000000"),
null,
1L,
Wei.fromHexString("0x9999999999"),
null,
null,
null,
Bytes.fromHexString(
"0x608060405234801561001057600080fd5b50610157806100206000396000f30060806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633bdab8bf146100515780639ae97baa14610068575b600080fd5b34801561005d57600080fd5b5061006661007f565b005b34801561007457600080fd5b5061007d6100b9565b005b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60016040518082815260200191505060405180910390a1565b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60026040518082815260200191505060405180910390a17fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60036040518082815260200191505060405180910390a15600a165627a7a7230582010ddaa52e73a98c06dbcd22b234b97206c1d7ed64a7c048e10c2043a3d2309cb0029"),
false,
null);
final JsonRpcRequestContext request = requestWithParams(callParameter);
final JsonRpcResponse expectedResponse =
new JsonRpcSuccessResponse(null, new CreateAccessListResult(new ArrayList<>(), 0x1f081));
final JsonRpcResponse response = method.response(request);
assertThat(response).usingRecursiveComparison().isEqualTo(expectedResponse);
}
@Test
public void shouldReturnExpectedValueForInsufficientGas() {
final JsonCallParameter callParameter =
new JsonCallParameter(null, null, 1L, null, null, null, null, null, null, null);
final JsonRpcRequestContext request = requestWithParams(callParameter);
final JsonRpcResponse expectedResponse =
new JsonRpcSuccessResponse(null, new CreateAccessListResult(new ArrayList<>(), 0xcf08));
final JsonRpcResponse response = method.response(request);
assertThat(response).usingRecursiveComparison().isEqualTo(expectedResponse);
}
private void assertAccessListExpectedResult(
final JsonCallParameter callParameter,
final List<AccessListEntry> accessList,
final long gasUsed) {
final JsonRpcRequestContext request = requestWithParams(callParameter);
final JsonRpcResponse expectedResponse =
new JsonRpcSuccessResponse(null, new CreateAccessListResult(accessList, gasUsed));
final JsonRpcResponse response = method.response(request);
assertThat(response).usingRecursiveComparison().isEqualTo(expectedResponse);
}
private JsonCallParameter createAccessListJsonCallParameters(
final String from, final String to, final List<AccessListEntry> accessList) {
return new JsonCallParameter(
Address.fromHexString(from),
Address.fromHexString(to),
null,
null,
null,
null,
null,
null,
null,
accessList);
}
private JsonRpcRequestContext requestWithParams(final Object... params) {
return new JsonRpcRequestContext(new JsonRpcRequest("2.0", "eth_createAccessList", params));
}
}

@ -86,6 +86,7 @@ public enum RpcMethod {
ETH_CHAIN_ID("eth_chainId"),
ETH_COINBASE("eth_coinbase"),
ETH_ESTIMATE_GAS("eth_estimateGas"),
ETH_CREATE_ACCESS_LIST("eth_createAccessList"),
ETH_FEE_HISTORY("eth_feeHistory"),
ETH_GAS_PRICE("eth_gasPrice"),
ETH_GET_BALANCE("eth_getBalance"),

@ -0,0 +1,123 @@
/*
* 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 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.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.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.mainnet.ValidationResult;
import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult;
import org.hyperledger.besu.ethereum.transaction.CallParameter;
import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason;
import org.hyperledger.besu.ethereum.transaction.TransactionSimulator;
import org.hyperledger.besu.ethereum.transaction.TransactionSimulatorResult;
import org.hyperledger.besu.evm.tracing.EstimateGasOperationTracer;
import java.util.Optional;
public abstract class AbstractEstimateGas implements JsonRpcMethod {
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;
this.transactionSimulator = transactionSimulator;
}
protected BlockHeader blockHeader() {
final long headBlockNumber = blockchainQueries.headBlockNumber();
return blockchainQueries.getBlockchain().getBlockHeader(headBlockNumber).orElse(null);
}
protected CallParameter overrideGasLimitAndPrice(
final JsonCallParameter callParams, final long gasLimit) {
return new CallParameter(
callParams.getFrom(),
callParams.getTo(),
gasLimit,
Optional.ofNullable(callParams.getGasPrice()).orElse(Wei.ZERO),
callParams.getMaxPriorityFeePerGas(),
callParams.getMaxFeePerGas(),
callParams.getValue(),
callParams.getPayload(),
callParams.getAccessList());
}
/**
* Estimate gas by adding minimum gas remaining for some operation and the necessary gas for sub
* calls
*
* @param result transaction simulator result
* @param operationTracer estimate gas operation tracer
* @return estimate gas
*/
protected long processEstimateGas(
final TransactionSimulatorResult result, final EstimateGasOperationTracer operationTracer) {
// no more than 63/64s of the remaining gas can be passed to the sub calls
final double subCallMultiplier =
Math.pow(SUB_CALL_REMAINING_GAS_RATIO, operationTracer.getMaxDepth());
// and minimum gas remaining is necessary for some operation (additionalStipend)
final long gasStipend = operationTracer.getStipendNeeded();
final long gasUsedByTransaction = result.getResult().getEstimateGasUsedByTransaction();
return ((long) ((gasUsedByTransaction + gasStipend) * subCallMultiplier));
}
protected JsonCallParameter validateAndGetCallParams(final JsonRpcRequestContext request) {
final JsonCallParameter callParams = request.getRequiredParameter(0, JsonCallParameter.class);
if (callParams.getGasPrice() != null
&& (callParams.getMaxFeePerGas().isPresent()
|| callParams.getMaxPriorityFeePerGas().isPresent())) {
throw new InvalidJsonRpcParameters("gasPrice cannot be used with baseFee or maxFeePerGas");
}
return callParams;
}
protected JsonRpcErrorResponse errorResponse(
final JsonRpcRequestContext request, final TransactionSimulatorResult result) {
final JsonRpcError jsonRpcError;
final ValidationResult<TransactionInvalidReason> validationResult =
result.getValidationResult();
if (validationResult != null && !validationResult.isValid()) {
jsonRpcError =
JsonRpcErrorConverter.convertTransactionInvalidReason(
validationResult.getInvalidReason());
} else {
final TransactionProcessingResult resultTrx = result.getResult();
if (resultTrx != null && resultTrx.getRevertReason().isPresent()) {
jsonRpcError = JsonRpcError.REVERT_ERROR;
jsonRpcError.setData(resultTrx.getRevertReason().get().toHexString());
} else {
jsonRpcError = JsonRpcError.INTERNAL_ERROR;
}
}
return errorResponse(request, jsonRpcError);
}
protected JsonRpcErrorResponse errorResponse(
final JsonRpcRequestContext request, final JsonRpcError jsonRpcError) {
return new JsonRpcErrorResponse(request.getRequest().getId(), jsonRpcError);
}
}

@ -0,0 +1,195 @@
/*
* 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 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.JsonRpcError;
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.results.CreateAccessListResult;
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.mainnet.ImmutableTransactionValidationParams;
import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams;
import org.hyperledger.besu.ethereum.transaction.CallParameter;
import org.hyperledger.besu.ethereum.transaction.TransactionSimulator;
import org.hyperledger.besu.ethereum.transaction.TransactionSimulatorResult;
import org.hyperledger.besu.evm.AccessListEntry;
import org.hyperledger.besu.evm.tracing.AccessListOperationTracer;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
public class EthCreateAccessList extends AbstractEstimateGas {
public EthCreateAccessList(
final BlockchainQueries blockchainQueries, final TransactionSimulator transactionSimulator) {
super(blockchainQueries, transactionSimulator);
}
@Override
public String getName() {
return RpcMethod.ETH_CREATE_ACCESS_LIST.getMethodName();
}
@Override
public JsonRpcResponse response(final JsonRpcRequestContext requestContext) {
final JsonCallParameter jsonCallParameter = validateAndGetCallParams(requestContext);
final BlockHeader blockHeader = blockHeader();
final Optional<JsonRpcError> jsonRpcError = validateBlockHeader(blockHeader);
if (jsonRpcError.isPresent()) {
return errorResponse(requestContext, jsonRpcError.get());
}
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())) {
final AccessListSimulatorResult result =
processTransactionWithAccessListOverride(
jsonCallParameter, blockHeader, maybeResult.getTracer().getAccessList());
return createResponse(requestContext, result);
} else {
return createResponse(requestContext, maybeResult);
}
}
private Optional<JsonRpcError> validateBlockHeader(final BlockHeader blockHeader) {
if (blockHeader == null) {
return Optional.of(JsonRpcError.INTERNAL_ERROR);
}
if (!blockchainQueries
.getWorldStateArchive()
.isWorldStateAvailable(blockHeader.getStateRoot(), blockHeader.getHash())) {
return Optional.of(JsonRpcError.WORLD_STATE_UNAVAILABLE);
}
return Optional.empty();
}
private JsonRpcResponse createResponse(
final JsonRpcRequestContext requestContext, final AccessListSimulatorResult result) {
return result
.getResult()
.map(createResponse(requestContext, result.getTracer()))
.orElse(errorResponse(requestContext, JsonRpcError.INTERNAL_ERROR));
}
private TransactionValidationParams transactionValidationParams(
final boolean isAllowExceedingBalance) {
return ImmutableTransactionValidationParams.builder()
.from(TransactionValidationParams.transactionSimulator())
.isAllowExceedingBalance(isAllowExceedingBalance)
.build();
}
private boolean shouldProcessWithAccessListOverride(
final JsonCallParameter parameters, final AccessListOperationTracer tracer) {
// if empty, transaction did not access any storage, does not need to reprocess
if (tracer.getAccessList().isEmpty()) {
return false;
}
// if empty, call did not include accessList, should reprocess
if (parameters.getAccessList().isEmpty()) {
return true;
}
// If call included access list, compare it with tracer result and return true if different
return !Objects.equals(tracer.getAccessList(), parameters.getAccessList().get());
}
private Function<TransactionSimulatorResult, JsonRpcResponse> createResponse(
final JsonRpcRequestContext request, final AccessListOperationTracer operationTracer) {
return result ->
result.isSuccessful()
? new JsonRpcSuccessResponse(
request.getRequest().getId(),
new CreateAccessListResult(
operationTracer.getAccessList(), processEstimateGas(result, operationTracer)))
: errorResponse(request, result);
}
private AccessListSimulatorResult processTransaction(
final JsonCallParameter jsonCallParameter, final BlockHeader blockHeader) {
final TransactionValidationParams transactionValidationParams =
transactionValidationParams(!jsonCallParameter.isMaybeStrict().orElse(Boolean.FALSE));
final CallParameter callParams =
overrideGasLimitAndPrice(jsonCallParameter, blockHeader.getGasLimit());
final AccessListOperationTracer tracer = AccessListOperationTracer.create();
final Optional<TransactionSimulatorResult> result =
transactionSimulator.process(
callParams, transactionValidationParams, tracer, blockHeader.getNumber());
return new AccessListSimulatorResult(result, tracer);
}
private AccessListSimulatorResult processTransactionWithAccessListOverride(
final JsonCallParameter jsonCallParameter,
final BlockHeader blockHeader,
final List<AccessListEntry> accessList) {
final TransactionValidationParams transactionValidationParams =
transactionValidationParams(!jsonCallParameter.isMaybeStrict().orElse(Boolean.FALSE));
final AccessListOperationTracer tracer = AccessListOperationTracer.create();
final CallParameter callParameter =
overrideAccessList(jsonCallParameter, blockHeader.getGasLimit(), accessList);
final Optional<TransactionSimulatorResult> result =
transactionSimulator.process(
callParameter, transactionValidationParams, tracer, blockHeader.getNumber());
return new AccessListSimulatorResult(result, tracer);
}
protected CallParameter overrideAccessList(
final JsonCallParameter callParams,
final long gasLimit,
final List<AccessListEntry> accessListEntries) {
return new CallParameter(
callParams.getFrom(),
callParams.getTo(),
gasLimit,
Optional.ofNullable(callParams.getGasPrice()).orElse(Wei.ZERO),
callParams.getMaxPriorityFeePerGas(),
callParams.getMaxFeePerGas(),
callParams.getValue(),
callParams.getPayload(),
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;
}
}
}

@ -14,14 +14,10 @@
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcErrorConverter;
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.exception.InvalidJsonRpcParameters;
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.api.jsonrpc.internal.results.Quantity;
@ -29,28 +25,18 @@ import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.mainnet.ImmutableTransactionValidationParams;
import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams;
import org.hyperledger.besu.ethereum.mainnet.ValidationResult;
import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult;
import org.hyperledger.besu.ethereum.transaction.CallParameter;
import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason;
import org.hyperledger.besu.ethereum.transaction.TransactionSimulator;
import org.hyperledger.besu.ethereum.transaction.TransactionSimulatorResult;
import org.hyperledger.besu.evm.tracing.EstimateGasOperationTracer;
import java.util.Optional;
import java.util.function.Function;
public class EthEstimateGas implements JsonRpcMethod {
private static final double SUB_CALL_REMAINING_GAS_RATIO = 65D / 64D;
private final BlockchainQueries blockchainQueries;
private final TransactionSimulator transactionSimulator;
public class EthEstimateGas extends AbstractEstimateGas {
public EthEstimateGas(
final BlockchainQueries blockchainQueries, final TransactionSimulator transactionSimulator) {
this.blockchainQueries = blockchainQueries;
this.transactionSimulator = transactionSimulator;
super(blockchainQueries, transactionSimulator);
}
@Override
@ -90,25 +76,6 @@ public class EthEstimateGas implements JsonRpcMethod {
.orElse(errorResponse(requestContext, JsonRpcError.INTERNAL_ERROR));
}
private BlockHeader blockHeader() {
final long headBlockNumber = blockchainQueries.headBlockNumber();
return blockchainQueries.getBlockchain().getBlockHeader(headBlockNumber).orElse(null);
}
private CallParameter overrideGasLimitAndPrice(
final JsonCallParameter callParams, final long gasLimit) {
return new CallParameter(
callParams.getFrom(),
callParams.getTo(),
gasLimit,
Optional.ofNullable(callParams.getGasPrice()).orElse(Wei.ZERO),
callParams.getMaxPriorityFeePerGas(),
callParams.getMaxFeePerGas(),
callParams.getValue(),
callParams.getPayload(),
callParams.getAccessList());
}
private Function<TransactionSimulatorResult, JsonRpcResponse> gasEstimateResponse(
final JsonRpcRequestContext request, final EstimateGasOperationTracer operationTracer) {
return result ->
@ -118,60 +85,4 @@ public class EthEstimateGas implements JsonRpcMethod {
Quantity.create(processEstimateGas(result, operationTracer)))
: errorResponse(request, result);
}
/**
* Estimate gas by adding minimum gas remaining for some operation and the necessary gas for sub
* calls
*
* @param result transaction simulator result
* @param operationTracer estimate gas operation tracer
* @return estimate gas
*/
private long processEstimateGas(
final TransactionSimulatorResult result, final EstimateGasOperationTracer operationTracer) {
// no more than 63/64s of the remaining gas can be passed to the sub calls
final double subCallMultiplier =
Math.pow(SUB_CALL_REMAINING_GAS_RATIO, operationTracer.getMaxDepth());
// and minimum gas remaining is necessary for some operation (additionalStipend)
final long gasStipend = operationTracer.getStipendNeeded();
final long gasUsedByTransaction = result.getResult().getEstimateGasUsedByTransaction();
return ((long) ((gasUsedByTransaction + gasStipend) * subCallMultiplier));
}
private JsonRpcErrorResponse errorResponse(
final JsonRpcRequestContext request, final TransactionSimulatorResult result) {
final JsonRpcError jsonRpcError;
final ValidationResult<TransactionInvalidReason> validationResult =
result.getValidationResult();
if (validationResult != null && !validationResult.isValid()) {
jsonRpcError =
JsonRpcErrorConverter.convertTransactionInvalidReason(
validationResult.getInvalidReason());
} else {
final TransactionProcessingResult resultTrx = result.getResult();
if (resultTrx != null && resultTrx.getRevertReason().isPresent()) {
jsonRpcError = JsonRpcError.REVERT_ERROR;
jsonRpcError.setData(resultTrx.getRevertReason().get().toHexString());
} else {
jsonRpcError = JsonRpcError.INTERNAL_ERROR;
}
}
return errorResponse(request, jsonRpcError);
}
private JsonRpcErrorResponse errorResponse(
final JsonRpcRequestContext request, final JsonRpcError jsonRpcError) {
return new JsonRpcErrorResponse(request.getRequest().getId(), jsonRpcError);
}
private JsonCallParameter validateAndGetCallParams(final JsonRpcRequestContext request) {
final JsonCallParameter callParams = request.getRequiredParameter(0, JsonCallParameter.class);
if (callParams.getGasPrice() != null
&& (callParams.getMaxFeePerGas().isPresent()
|| callParams.getMaxPriorityFeePerGas().isPresent())) {
throw new InvalidJsonRpcParameters("gasPrice cannot be used with baseFee or maxFeePerGas");
}
return callParams;
}
}

@ -0,0 +1,42 @@
/*
* 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.results;
import org.hyperledger.besu.evm.AccessListEntry;
import java.util.Collection;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonGetter;
public class CreateAccessListResult {
List<AccessListEntry> accessListList;
String gasUsed;
public CreateAccessListResult(final List<AccessListEntry> accessListEntries, final long gasUsed) {
this.accessListList = accessListEntries;
this.gasUsed = Quantity.create(gasUsed);
}
@JsonGetter(value = "accessList")
public Collection<AccessListEntry> getAccessList() {
return accessListList;
}
@JsonGetter(value = "gasUsed")
public String getGasUsed() {
return gasUsed;
}
}

@ -21,6 +21,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthBlockNumber
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthCall;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthChainId;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthCoinbase;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthCreateAccessList;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthEstimateGas;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthFeeHistory;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthGasPrice;
@ -161,6 +162,13 @@ public class EthJsonRpcMethods extends ApiGroupJsonRpcMethods {
blockchainQueries.getWorldStateArchive(),
protocolSchedule,
privacyParameters)),
new EthCreateAccessList(
blockchainQueries,
new TransactionSimulator(
blockchainQueries.getBlockchain(),
blockchainQueries.getWorldStateArchive(),
protocolSchedule,
privacyParameters)),
new EthMining(miningCoordinator),
new EthCoinbase(miningCoordinator),
new EthProtocolVersion(supportedCapabilities),

@ -0,0 +1,342 @@
/*
* 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.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.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Wei;
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.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.api.jsonrpc.internal.results.CreateAccessListResult;
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.processing.TransactionProcessingResult;
import org.hyperledger.besu.ethereum.transaction.CallParameter;
import org.hyperledger.besu.ethereum.transaction.TransactionSimulator;
import org.hyperledger.besu.ethereum.transaction.TransactionSimulatorResult;
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
import org.hyperledger.besu.evm.AccessListEntry;
import org.hyperledger.besu.evm.tracing.AccessListOperationTracer;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.assertj.core.api.Assertions;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class EthCreateAccessListTest {
private final String METHOD = "eth_createAccessList";
private EthCreateAccessList method;
@Mock private BlockHeader blockHeader;
@Mock private Blockchain blockchain;
@Mock private BlockchainQueries blockchainQueries;
@Mock private TransactionSimulator transactionSimulator;
@Mock private WorldStateArchive worldStateArchive;
@Before
public void setUp() {
when(blockchainQueries.headBlockNumber()).thenReturn(1L);
when(blockchainQueries.getBlockchain()).thenReturn(blockchain);
when(blockchainQueries.getWorldStateArchive()).thenReturn(worldStateArchive);
when(blockchain.getBlockHeader(eq(1L))).thenReturn(Optional.of(blockHeader));
when(blockHeader.getGasLimit()).thenReturn(Long.MAX_VALUE);
when(blockHeader.getNumber()).thenReturn(1L);
when(worldStateArchive.isWorldStateAvailable(any(), any())).thenReturn(true);
method = new EthCreateAccessList(blockchainQueries, transactionSimulator);
}
@Test
public void shouldReturnCorrectMethodName() {
assertThat(method.getName()).isEqualTo(METHOD);
}
private JsonRpcRequestContext ethCreateAccessListRequest(final CallParameter callParameter) {
return new JsonRpcRequestContext(
new JsonRpcRequest("2.0", METHOD, new Object[] {callParameter}));
}
@Test
public void shouldReturnGasEstimateWhenTransientLegacyTransactionProcessorReturnsResultSuccess() {
final JsonRpcRequestContext request =
ethCreateAccessListRequest(legacyTransactionCallParameter(Wei.ZERO));
mockTransactionSimulatorResult(true, false, 1L);
final JsonRpcResponse expectedResponse =
new JsonRpcSuccessResponse(null, new CreateAccessListResult(new ArrayList<>(), 1L));
Assertions.assertThat(method.response(request))
.usingRecursiveComparison()
.isEqualTo(expectedResponse);
}
@Test
public void shouldUseGasPriceParameterWhenIsPresent() {
final Wei gasPrice = Wei.of(1000);
final JsonRpcRequestContext request =
ethCreateAccessListRequest(legacyTransactionCallParameter(gasPrice));
mockTransactionSimulatorResult(true, false, 1L);
final JsonRpcResponse expectedResponse =
new JsonRpcSuccessResponse(null, new CreateAccessListResult(new ArrayList<>(), 1L));
Assertions.assertThat(method.response(request))
.usingRecursiveComparison()
.isEqualTo(expectedResponse);
}
@Test
public void shouldReturnGasEstimateErrorWhenGasPricePresentForEip1559Transaction() {
final JsonRpcRequestContext request =
ethCreateAccessListRequest(eip1559TransactionCallParameter(Optional.of(Wei.of(10))));
mockTransactionSimulatorResult(false, false, 1L);
Assertions.assertThatThrownBy(() -> method.response(request))
.isInstanceOf(InvalidJsonRpcParameters.class)
.hasMessageContaining("gasPrice cannot be used with baseFee or maxFeePerGas");
}
@Test
public void shouldReturnErrorWhenWorldStateIsNotAvailable() {
when(worldStateArchive.isWorldStateAvailable(any(), any())).thenReturn(false);
final JsonRpcRequestContext request =
ethCreateAccessListRequest(legacyTransactionCallParameter(Wei.ZERO));
mockTransactionSimulatorResult(false, false, 1L);
final JsonRpcResponse expectedResponse =
new JsonRpcErrorResponse(null, JsonRpcError.WORLD_STATE_UNAVAILABLE);
Assertions.assertThat(method.response(request))
.usingRecursiveComparison()
.isEqualTo(expectedResponse);
}
@Test
public void shouldReturnErrorWhenTransactionReverted() {
final JsonRpcRequestContext request =
ethCreateAccessListRequest(legacyTransactionCallParameter(Wei.ZERO));
mockTransactionSimulatorResult(false, true, 1L);
final JsonRpcResponse expectedResponse =
new JsonRpcErrorResponse(null, JsonRpcError.REVERT_ERROR);
Assertions.assertThat(method.response(request))
.usingRecursiveComparison()
.isEqualTo(expectedResponse);
}
@Test
public void shouldReturnEmptyAccessListIfNoParameterAndWithoutAccessedStorage() {
final List<AccessListEntry> expectedAccessList = new ArrayList<>();
final JsonRpcResponse expectedResponse =
new JsonRpcSuccessResponse(null, new CreateAccessListResult(expectedAccessList, 1L));
final JsonRpcRequestContext request =
ethCreateAccessListRequest(eip1559TransactionCallParameter());
mockTransactionSimulatorResult(true, false, 1L);
Assertions.assertThat(method.response(request))
.usingRecursiveComparison()
.isEqualTo(expectedResponse);
verify(transactionSimulator, times(1)).process(any(), any(), any(), anyLong());
}
@Test
public void shouldReturnAccessListIfNoParameterAndWithAccessedStorage() {
// Create a 1559 call without access lists
final JsonRpcRequestContext request =
ethCreateAccessListRequest(eip1559TransactionCallParameter());
// Create a list with one access list entry
final List<AccessListEntry> expectedAccessList = createAccessList();
// expect a list with the mocked access list
final JsonRpcResponse expectedResponse =
new JsonRpcSuccessResponse(null, new CreateAccessListResult(expectedAccessList, 1L));
final AccessListOperationTracer tracer = createMockTracer(expectedAccessList);
// Set TransactionSimulator.process response
mockTransactionSimulatorResult(true, false, 1L);
Assertions.assertThat(responseWithMockTracer(request, tracer))
.usingRecursiveComparison()
.isEqualTo(expectedResponse);
verify(transactionSimulator, times(2)).process(any(), any(), any(), anyLong());
}
@Test
public void shouldReturnEmptyAccessListIfNoAccessedStorage() {
// Create a list with one enter
final List<AccessListEntry> accessListParam = createAccessList();
// expect empty list
final JsonRpcResponse expectedResponse =
new JsonRpcSuccessResponse(null, new CreateAccessListResult(new ArrayList<>(), 1L));
// create a request using the accessListParam
final JsonRpcRequestContext request =
ethCreateAccessListRequest(eip1559TransactionCallParameter(accessListParam));
// Set TransactionSimulator.process response
mockTransactionSimulatorResult(true, false, 1L);
Assertions.assertThat(method.response(request))
.usingRecursiveComparison()
.isEqualTo(expectedResponse);
verify(transactionSimulator, times(1)).process(any(), any(), any(), anyLong());
}
@Test
public void shouldReturnAccessListIfParameterAndSameAccessedStorage() {
// Create a list with one access list entry
final List<AccessListEntry> expectedAccessList = createAccessList();
// Create a 1559 call with the expected access lists
final JsonRpcRequestContext request =
ethCreateAccessListRequest(eip1559TransactionCallParameter(expectedAccessList));
// expect a list with the mocked access list
final JsonRpcResponse expectedResponse =
new JsonRpcSuccessResponse(null, new CreateAccessListResult(expectedAccessList, 1L));
final AccessListOperationTracer tracer = createMockTracer(expectedAccessList);
// Set TransactionSimulator.process response
mockTransactionSimulatorResult(true, false, 1L);
Assertions.assertThat(responseWithMockTracer(request, tracer))
.usingRecursiveComparison()
.isEqualTo(expectedResponse);
verify(transactionSimulator, times(1)).process(any(), any(), any(), anyLong());
}
@Test
public void shouldReturnAccessListIfWithParameterAndDifferentAccessedStorage() {
// Create a list with one access list entry
final List<AccessListEntry> accessListParam = createAccessList();
// Create a 1559 call with the accessListParam
final JsonRpcRequestContext request =
ethCreateAccessListRequest(eip1559TransactionCallParameter(accessListParam));
// Create a list with one access list entry
final List<AccessListEntry> expectedAccessList = createAccessList();
// expect a list with the mocked access list
final JsonRpcResponse expectedResponse =
new JsonRpcSuccessResponse(null, new CreateAccessListResult(expectedAccessList, 1L));
final AccessListOperationTracer tracer = createMockTracer(expectedAccessList);
// Set TransactionSimulator.process response
mockTransactionSimulatorResult(true, false, 1L);
Assertions.assertThat(responseWithMockTracer(request, tracer))
.usingRecursiveComparison()
.isEqualTo(expectedResponse);
verify(transactionSimulator, times(2)).process(any(), any(), any(), anyLong());
}
private JsonRpcResponse responseWithMockTracer(
final JsonRpcRequestContext request, final AccessListOperationTracer tracer) {
try (final MockedStatic<AccessListOperationTracer> tracerMockedStatic =
Mockito.mockStatic(AccessListOperationTracer.class)) {
tracerMockedStatic.when(AccessListOperationTracer::create).thenReturn(tracer);
return method.response(request);
}
}
private AccessListOperationTracer createMockTracer(
final List<AccessListEntry> accessListEntries) {
final AccessListOperationTracer tracer = mock(AccessListOperationTracer.class);
when(tracer.getAccessList()).thenReturn(accessListEntries);
return tracer;
}
private void mockTransactionSimulatorResult(
final boolean isSuccessful, final boolean isReverted, final long estimateGas) {
final TransactionSimulatorResult mockTxSimResult = mock(TransactionSimulatorResult.class);
when(transactionSimulator.process(any(), any(), any(), anyLong()))
.thenReturn(Optional.of(mockTxSimResult));
final TransactionProcessingResult mockResult = mock(TransactionProcessingResult.class);
when(mockResult.getEstimateGasUsedByTransaction()).thenReturn(estimateGas);
when(mockResult.getRevertReason())
.thenReturn(isReverted ? Optional.of(Bytes.of(0)) : Optional.empty());
when(mockTxSimResult.getResult()).thenReturn(mockResult);
when(mockTxSimResult.isSuccessful()).thenReturn(isSuccessful);
}
private JsonCallParameter legacyTransactionCallParameter(final Wei gasPrice) {
return new JsonCallParameter(
Address.fromHexString("0x0"),
Address.fromHexString("0x0"),
0L,
gasPrice,
null,
null,
Wei.ZERO,
Bytes.EMPTY,
false,
null);
}
private CallParameter eip1559TransactionCallParameter() {
return eip1559TransactionCallParameter(Optional.empty(), null);
}
private CallParameter eip1559TransactionCallParameter(final Optional<Wei> gasPrice) {
return eip1559TransactionCallParameter(gasPrice, null);
}
private CallParameter eip1559TransactionCallParameter(
final List<AccessListEntry> accessListEntries) {
return eip1559TransactionCallParameter(Optional.empty(), accessListEntries);
}
private JsonCallParameter eip1559TransactionCallParameter(
final Optional<Wei> gasPrice, final List<AccessListEntry> accessListEntries) {
return new JsonCallParameter(
Address.fromHexString("0x0"),
Address.fromHexString("0x0"),
null,
gasPrice.orElse(null),
Wei.fromHexString("0x10"),
Wei.fromHexString("0x10"),
Wei.ZERO,
Bytes.EMPTY,
false,
accessListEntries);
}
private List<AccessListEntry> createAccessList() {
return List.of(
new AccessListEntry(Address.wrap(Bytes.random(Address.SIZE)), List.of(Bytes32.random())));
}
}

@ -1279,6 +1279,14 @@ public class MessageFrame {
this.currentOperation = currentOperation;
}
/**
* Gets warmedUp Storage.
*
* @return the warmed up storage
*/
public Multimap<Address, Bytes32> getWarmedUpStorage() {
return warmedUpStorage;
}
/**
* Gets maybe updated memory.
*

@ -0,0 +1,67 @@
/*
* 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.evm.tracing;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.evm.AccessListEntry;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.operation.Operation.OperationResult;
import java.util.ArrayList;
import java.util.List;
import com.google.common.collect.Multimap;
import org.apache.tuweni.bytes.Bytes32;
/** The Access List Operation Tracer. */
public class AccessListOperationTracer extends EstimateGasOperationTracer {
private Multimap<Address, Bytes32> warmedUpStorage;
@Override
public void tracePostExecution(final MessageFrame frame, final OperationResult operationResult) {
super.tracePostExecution(frame, operationResult);
warmedUpStorage = frame.getWarmedUpStorage();
}
@Override
public void tracePreExecution(final MessageFrame frame) {}
/**
* Get the access list.
*
* @return the access list
*/
public List<AccessListEntry> getAccessList() {
final List<AccessListEntry> list = new ArrayList<>();
if (warmedUpStorage != null) {
warmedUpStorage
.asMap()
.forEach(
(address, storageKeys) ->
list.add(new AccessListEntry(address, new ArrayList<>(storageKeys))));
}
return list;
}
/**
* Create a AccessListOperationTracer.
*
* @return the AccessListOperationTracer
*/
public static AccessListOperationTracer create() {
return new AccessListOperationTracer();
}
}

@ -44,6 +44,8 @@ public final class BlockTestUtil {
Suppliers.memoize(BlockTestUtil::supplyOutdatedForkResources);
private static final Supplier<ChainResources> forkUpgradedSupplier =
Suppliers.memoize(BlockTestUtil::supplyUpgradedForkResources);
private static final Supplier<ChainResources> testRpcCompactChainSupplier =
Suppliers.memoize(BlockTestUtil::supplyTestRpcCompactResources);
/**
* Gets test blockchain url.
@ -129,6 +131,14 @@ public final class BlockTestUtil {
public static ChainResources getUpgradedForkResources() {
return forkUpgradedSupplier.get();
}
/**
* Gets Eth Ref Test resources.
*
* @return the Eth Ref Test resources.
*/
public static ChainResources getEthRefTestResources() {
return testRpcCompactChainSupplier.get();
}
private static ChainResources supplyTestChainResources() {
final URL genesisURL =
@ -199,6 +209,20 @@ public final class BlockTestUtil {
return new ChainResources(genesisURL, blocksURL);
}
private static ChainResources supplyTestRpcCompactResources() {
final URL genesisURL =
ensureFileUrl(
BlockTestUtil.class
.getClassLoader()
.getResource("test-eth-ref-rpc-compact/genesis.json"));
final URL blocksURL =
ensureFileUrl(
BlockTestUtil.class
.getClassLoader()
.getResource("test-eth-ref-rpc-compact/chain.blocks"));
return new ChainResources(genesisURL, blocksURL);
}
/** Take a resource URL and if needed copy it to a temp file and return that URL. */
private static URL ensureFileUrl(final URL resource) {
Preconditions.checkNotNull(resource);

@ -0,0 +1,58 @@
{
"config": {
"chainId": 1337,
"homesteadBlock": 0,
"eip150Block": 0,
"eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"eip155Block": 0,
"eip158Block": 0,
"byzantiumBlock": 0,
"constantinopleBlock": 0,
"petersburgBlock": 0,
"istanbulBlock": 0,
"muirGlacierBlock": 0,
"berlinBlock": 0,
"londonBlock": 0,
"arrowGlacierBlock": 0,
"grayGlacierBlock": 0,
"ethash": {}
},
"nonce": "0x0",
"timestamp": "0x0",
"extraData": "0x",
"gasLimit": "0x4c4b40",
"difficulty": "0x1",
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"coinbase": "0x0000000000000000000000000000000000000000",
"alloc": {
"658bdf435d810c91414ec09147daa6db62406379": {
"balance": "0x487a9a304539440000"
},
"aa00000000000000000000000000000000000000": {
"code": "0x6042",
"storage": {
"0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000000",
"0x0100000000000000000000000000000000000000000000000000000000000000": "0x0100000000000000000000000000000000000000000000000000000000000000",
"0x0200000000000000000000000000000000000000000000000000000000000000": "0x0200000000000000000000000000000000000000000000000000000000000000",
"0x0300000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000303"
},
"balance": "0x1",
"nonce": "0x1"
},
"bb00000000000000000000000000000000000000": {
"code": "0x600154600354",
"storage": {
"0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000000",
"0x0100000000000000000000000000000000000000000000000000000000000000": "0x0100000000000000000000000000000000000000000000000000000000000000",
"0x0200000000000000000000000000000000000000000000000000000000000000": "0x0200000000000000000000000000000000000000000000000000000000000000",
"0x0300000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000303"
},
"balance": "0x2",
"nonce": "0x1"
}
},
"number": "0x0",
"gasUsed": "0x0",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"baseFeePerGas": "0x3b9aca00"
}
Loading…
Cancel
Save