mirror of https://github.com/hyperledger/besu
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
parent
f4a6219456
commit
c4dbc58a5e
@ -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)); |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
@ -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()))); |
||||
} |
||||
} |
@ -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(); |
||||
} |
||||
} |
Binary file not shown.
@ -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…
Reference in new issue