Skip to content

Commit

Permalink
eth_call overrides (hyperledger#7801)
Browse files Browse the repository at this point in the history
* add state and account overrides

Signed-off-by: Sally MacFarlane <[email protected]>

---------

Signed-off-by: Sally MacFarlane <[email protected]>
Co-authored-by: Fabio Di Fabio <[email protected]>
  • Loading branch information
macfarla and fab-10 authored Nov 19, 2024
1 parent 2384c09 commit 8b7ca20
Show file tree
Hide file tree
Showing 12 changed files with 647 additions and 21 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
- Update Java dependencies [#7786](https://github.com/hyperledger/besu/pull/7786)
- Add a method to get all the transaction in the pool, to the `TransactionPoolService`, to easily access the transaction pool content from plugins [#7813](https://github.com/hyperledger/besu/pull/7813)
- Add a method to check if a metric category is enabled to the plugin API [#7832](https://github.com/hyperledger/besu/pull/7832)
- Add account and state overrides to `eth_call` and `eth_estimateGas` [#7801](https://github.com/hyperledger/besu/pull/7801)

### Bug fixes
- Fix registering new metric categories from plugins [#7825](https://github.com/hyperledger/besu/pull/7825)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
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.exception.InvalidJsonRpcRequestException;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameterOrBlockHash;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonCallParameter;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException;
Expand All @@ -40,8 +41,13 @@
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.ethereum.util.AccountOverrideMap;
import org.hyperledger.besu.evm.tracing.OperationTracer;

import java.util.Optional;

import com.google.common.annotations.VisibleForTesting;

public class EthCall extends AbstractBlockParameterOrBlockHashMethod {
private final TransactionSimulator transactionSimulator;

Expand Down Expand Up @@ -81,10 +87,13 @@ protected Object resultByBlockHash(final JsonRpcRequestContext request, final Ha
protected Object resultByBlockHeader(
final JsonRpcRequestContext request, final BlockHeader header) {
JsonCallParameter callParams = JsonCallParameterUtil.validateAndGetCallParams(request);
Optional<AccountOverrideMap> maybeStateOverrides = getAddressAccountOverrideMap(request);
// TODO implement for block overrides

return transactionSimulator
.process(
callParams,
maybeStateOverrides,
buildTransactionValidationParams(header, callParams),
OperationTracer.NO_TRACING,
(mutableWorldState, transactionSimulatorResult) ->
Expand All @@ -108,6 +117,17 @@ protected Object resultByBlockHeader(
.orElse(errorResponse(request, INTERNAL_ERROR));
}

@VisibleForTesting
protected Optional<AccountOverrideMap> getAddressAccountOverrideMap(
final JsonRpcRequestContext request) {
try {
return request.getOptionalParameter(2, AccountOverrideMap.class);
} catch (JsonRpcParameterException e) {
throw new InvalidJsonRpcRequestException(
"Invalid account overrides parameter (index 2)", RpcErrorType.INVALID_CALL_PARAMS, e);
}
}

@Override
public JsonRpcResponse response(final JsonRpcRequestContext requestContext) {
return (JsonRpcResponse) handleParamTypes(requestContext);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,12 @@ private JsonNode getSingleCallResult(
new DebugOperationTracer(buildTraceOptions(traceTypes), false);
final Optional<TransactionSimulatorResult> maybeSimulatorResult =
transactionSimulator.processWithWorldUpdater(
callParameter, buildTransactionValidationParams(), tracer, header, worldUpdater);
callParameter,
Optional.empty(),
buildTransactionValidationParams(),
tracer,
header,
worldUpdater);

LOG.trace("Executing {} call for transaction {}", traceTypeParameter, callParameter);
if (maybeSimulatorResult.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@
import org.hyperledger.besu.ethereum.transaction.PreCloseStateHandler;
import org.hyperledger.besu.ethereum.transaction.TransactionSimulator;
import org.hyperledger.besu.ethereum.transaction.TransactionSimulatorResult;
import org.hyperledger.besu.ethereum.util.AccountOverride;
import org.hyperledger.besu.ethereum.util.AccountOverrideMap;

import java.util.Optional;

Expand Down Expand Up @@ -92,14 +94,41 @@ public void shouldReturnCorrectMethodName() {
assertThat(method.getName()).isEqualTo("eth_call");
}

@Test
public void noAccountOverrides() {
final JsonRpcRequestContext request = ethCallRequest(callParameter(), "latest");
Optional<AccountOverrideMap> overrideMap = method.getAddressAccountOverrideMap(request);
assertThat(overrideMap.isPresent()).isFalse();
}

@Test
public void someAccountOverrides() {
AccountOverrideMap expectedOverrides = new AccountOverrideMap();
AccountOverride override = new AccountOverride.Builder().withNonce(88L).build();
final Address address = Address.fromHexString("0xd9c9cd5f6779558b6e0ed4e6acf6b1947e7fa1f3");
expectedOverrides.put(address, override);

final JsonRpcRequestContext request =
ethCallRequestWithStateOverrides(callParameter(), "latest", expectedOverrides);

Optional<AccountOverrideMap> maybeOverrideMap = method.getAddressAccountOverrideMap(request);
assertThat(maybeOverrideMap.isPresent()).isTrue();
AccountOverrideMap overrideMap = maybeOverrideMap.get();
assertThat(overrideMap.keySet()).hasSize(1);
assertThat(overrideMap.values()).hasSize(1);

assertThat(overrideMap).containsKey(address);
assertThat(overrideMap).containsValue(override);
}

@Test
public void shouldReturnInternalErrorWhenProcessorReturnsEmpty() {
final JsonRpcRequestContext request = ethCallRequest(callParameter(), "latest");
final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, INTERNAL_ERROR);

when(blockchainQueries.getBlockchain()).thenReturn(blockchain);
when(blockchain.getChainHead()).thenReturn(chainHead);
when(transactionSimulator.process(any(), any(), any(), any(), any()))
when(transactionSimulator.process(any(), any(), any(), any(), any(), any()))
.thenReturn(Optional.empty());

final BlockHeader blockHeader = mock(BlockHeader.class);
Expand All @@ -109,7 +138,7 @@ public void shouldReturnInternalErrorWhenProcessorReturnsEmpty() {
final JsonRpcResponse response = method.response(request);

assertThat(response).usingRecursiveComparison().isEqualTo(expectedResponse);
verify(transactionSimulator).process(any(), any(), any(), any(), any());
verify(transactionSimulator).process(any(), any(), any(), any(), any(), any());
}

@Test
Expand All @@ -130,12 +159,13 @@ public void shouldAcceptRequestWhenMissingOptionalFields() {
when(result.isSuccessful()).thenReturn(true);
when(result.getValidationResult()).thenReturn(ValidationResult.valid());
when(result.getOutput()).thenReturn(Bytes.of());
verify(transactionSimulator).process(any(), any(), any(), mapperCaptor.capture(), any());
verify(transactionSimulator)
.process(
eq(callParameter), eq(Optional.empty()), any(), any(), mapperCaptor.capture(), any());
assertThat(mapperCaptor.getValue().apply(mock(MutableWorldState.class), Optional.of(result)))
.isEqualTo(Optional.of(expectedResponse));

assertThat(response).usingRecursiveComparison().isEqualTo(expectedResponse);
verify(transactionSimulator).process(eq(callParameter), any(), any(), any(), any());
}

@Test
Expand All @@ -158,7 +188,8 @@ public void shouldReturnExecutionResultWhenExecutionIsSuccessful() {
when(result.getValidationResult()).thenReturn(ValidationResult.valid());
when(result.getOutput()).thenReturn(Bytes.of(1));
verify(transactionSimulator)
.process(eq(callParameter()), any(), any(), mapperCaptor.capture(), any());
.process(
eq(callParameter()), eq(Optional.empty()), any(), any(), mapperCaptor.capture(), any());
assertThat(mapperCaptor.getValue().apply(mock(MutableWorldState.class), Optional.of(result)))
.isEqualTo(Optional.of(expectedResponse));

Expand Down Expand Up @@ -196,7 +227,8 @@ public void shouldReturnBasicExecutionRevertErrorWithoutReason() {
when(result.isSuccessful()).thenReturn(false);
when(result.getValidationResult()).thenReturn(ValidationResult.valid());
when(result.result()).thenReturn(processingResult);
verify(transactionSimulator).process(any(), any(), any(), mapperCaptor.capture(), any());
verify(transactionSimulator)
.process(any(), eq(Optional.empty()), any(), any(), mapperCaptor.capture(), any());
assertThat(mapperCaptor.getValue().apply(mock(MutableWorldState.class), Optional.of(result)))
.isEqualTo(Optional.of(expectedResponse));

Expand Down Expand Up @@ -235,7 +267,8 @@ public void shouldReturnExecutionRevertErrorWithABIParseError() {
when(result.isSuccessful()).thenReturn(false);
when(result.getValidationResult()).thenReturn(ValidationResult.valid());
when(result.result()).thenReturn(processingResult);
verify(transactionSimulator).process(any(), any(), any(), mapperCaptor.capture(), any());
verify(transactionSimulator)
.process(any(), eq(Optional.empty()), any(), any(), mapperCaptor.capture(), any());
assertThat(mapperCaptor.getValue().apply(mock(MutableWorldState.class), Optional.of(result)))
.isEqualTo(Optional.of(expectedResponse));

Expand Down Expand Up @@ -277,7 +310,8 @@ public void shouldReturnExecutionRevertErrorWithParsedABI() {
when(result.getValidationResult()).thenReturn(ValidationResult.valid());
when(result.result()).thenReturn(processingResult);

verify(transactionSimulator).process(any(), any(), any(), mapperCaptor.capture(), any());
verify(transactionSimulator)
.process(any(), eq(Optional.empty()), any(), any(), mapperCaptor.capture(), any());
assertThat(mapperCaptor.getValue().apply(mock(MutableWorldState.class), Optional.of(result)))
.isEqualTo(Optional.of(expectedResponse));

Expand All @@ -291,7 +325,7 @@ public void shouldUseCorrectBlockNumberWhenLatest() {
final JsonRpcRequestContext request = ethCallRequest(callParameter(), "latest");
when(blockchainQueries.getBlockchain()).thenReturn(blockchain);
when(blockchain.getChainHead()).thenReturn(chainHead);
when(transactionSimulator.process(any(), any(), any(), any(), any()))
when(transactionSimulator.process(any(), eq(Optional.empty()), any(), any(), any(), any()))
.thenReturn(Optional.empty());

final BlockHeader blockHeader = mock(BlockHeader.class);
Expand All @@ -301,7 +335,7 @@ public void shouldUseCorrectBlockNumberWhenLatest() {
method.response(request);

verify(blockchainQueries, atLeastOnce()).getBlockchain();
verify(transactionSimulator).process(any(), any(), any(), any(), any());
verify(transactionSimulator).process(any(), eq(Optional.empty()), any(), any(), any(), any());
}

@Test
Expand All @@ -315,35 +349,35 @@ public void shouldUseCorrectBlockNumberWhenEarliest() {
method.response(request);

verify(blockchainQueries).getBlockHeaderByHash(eq(Hash.ZERO));
verify(transactionSimulator).process(any(), any(), any(), any(), any());
verify(transactionSimulator).process(any(), eq(Optional.empty()), any(), any(), any(), any());
}

@Test
public void shouldUseCorrectBlockNumberWhenSafe() {
final JsonRpcRequestContext request = ethCallRequest(callParameter(), "safe");
when(blockchainQueries.getBlockHeaderByHash(Hash.ZERO)).thenReturn(Optional.of(blockHeader));
when(blockchainQueries.safeBlockHeader()).thenReturn(Optional.of(blockHeader));
when(transactionSimulator.process(any(), any(), any(), any(), any()))
when(transactionSimulator.process(any(), eq(Optional.empty()), any(), any(), any(), any()))
.thenReturn(Optional.empty());
method.response(request);

verify(blockchainQueries).getBlockHeaderByHash(Hash.ZERO);
verify(blockchainQueries).safeBlockHeader();
verify(transactionSimulator).process(any(), any(), any(), any(), any());
verify(transactionSimulator).process(any(), eq(Optional.empty()), any(), any(), any(), any());
}

@Test
public void shouldUseCorrectBlockNumberWhenFinalized() {
final JsonRpcRequestContext request = ethCallRequest(callParameter(), "finalized");
when(blockchainQueries.getBlockHeaderByHash(Hash.ZERO)).thenReturn(Optional.of(blockHeader));
when(blockchainQueries.finalizedBlockHeader()).thenReturn(Optional.of(blockHeader));
when(transactionSimulator.process(any(), any(), any(), any(), any()))
when(transactionSimulator.process(any(), eq(Optional.empty()), any(), any(), any(), any()))
.thenReturn(Optional.empty());
method.response(request);

verify(blockchainQueries).getBlockHeaderByHash(Hash.ZERO);
verify(blockchainQueries).finalizedBlockHeader();
verify(transactionSimulator).process(any(), any(), any(), any(), any());
verify(transactionSimulator).process(any(), eq(Optional.empty()), any(), any(), any(), any());
}

@Test
Expand All @@ -353,13 +387,13 @@ public void shouldUseCorrectBlockNumberWhenSpecified() {
when(blockchainQueries.getBlockHashByNumber(anyLong())).thenReturn(Optional.of(Hash.ZERO));
when(blockchainQueries.getBlockHeaderByHash(Hash.ZERO))
.thenReturn(Optional.of(mock(BlockHeader.class)));
when(transactionSimulator.process(any(), any(), any(), any(), any()))
when(transactionSimulator.process(any(), eq(Optional.empty()), any(), any(), any(), any()))
.thenReturn(Optional.empty());

method.response(request);

verify(blockchainQueries).getBlockHeaderByHash(eq(Hash.ZERO));
verify(transactionSimulator).process(any(), any(), any(), any(), any());
verify(transactionSimulator).process(any(), eq(Optional.empty()), any(), any(), any(), any());
}

@Test
Expand Down Expand Up @@ -431,7 +465,7 @@ private void internalAutoSelectIsAllowedExceedingBalance(
.build();

verify(transactionSimulator)
.process(any(), eq(transactionValidationParams), any(), any(), any());
.process(any(), eq(Optional.empty()), eq(transactionValidationParams), any(), any(), any());
}

private JsonCallParameter callParameter() {
Expand All @@ -458,8 +492,17 @@ private JsonRpcRequestContext ethCallRequest(
new JsonRpcRequest("2.0", "eth_call", new Object[] {callParameter, blockNumberInHex}));
}

private JsonRpcRequestContext ethCallRequestWithStateOverrides(
final CallParameter callParameter,
final String blockNumberInHex,
final AccountOverrideMap overrides) {
return new JsonRpcRequestContext(
new JsonRpcRequest(
"2.0", "eth_call", new Object[] {callParameter, blockNumberInHex, overrides}));
}

private void mockTransactionProcessorSuccessResult(final JsonRpcResponse jsonRpcResponse) {
when(transactionSimulator.process(any(), any(), any(), any(), any()))
when(transactionSimulator.process(any(), eq(Optional.empty()), any(), any(), any(), any()))
.thenReturn(Optional.of(jsonRpcResponse));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"request": {
"id": 3,
"jsonrpc": "2.0",
"method": "eth_call",
"params": [
{
"to": "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f",
"from": "a94f5374fce5edbc8e2a8697c15331677e6ebf0b",
"data": "0x12a7b914"
},
"latest",
{
"a94f5374fce5edbc8e2a8697c15331677e6ebf0b": {}
}
]
},
"response": {
"jsonrpc": "2.0",
"id": 3,
"result": "0x0000000000000000000000000000000000000000000000000000000000000001"
},
"statusCode": 200
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"request": {
"id": 3,
"jsonrpc": "2.0",
"method": "eth_call",
"params": [
{
"to": "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f",
"from": "a94f5374fce5edbc8e2a8697c15331677e6ebf0b",
"data": "0x12a7b914"
},
"latest",
{
"a94f5374fce5edbc8e2a8697c15331677e6ebf0b": {
"balance": "0xde0b6b3a7640000",
"nonce": 88
},
"0xb9741079a300Cb3B8f324CdDB847c0d1d273a05E": {
"stateDiff": {
"0x1cf7945003fc5b59d2f6736f0704557aa805c4f2844084ccd1173b8d56946962": "0x000000000000000000000000000000000000000000000000000000110ed03bf7"
}
}
}
]
},
"response": {
"jsonrpc": "2.0",
"id": 3,
"result": "0x0000000000000000000000000000000000000000000000000000000000000001"
},
"statusCode": 200
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"request": {
"id": 3,
"jsonrpc": "2.0",
"method": "eth_call",
"params": [
{
"to": "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f",
"from": "a94f5374fce5edbc8e2a8697c15331677e6ebf0b",
"value": "0x000002"
},
"latest",
{
"a94f5374fce5edbc8e2a8697c15331677e6ebf0b": {
"balance": "0x000001"
},
"0xb9741079a300Cb3B8f324CdDB847c0d1d273a05E": {
"stateDiff": {
"0x1cf7945003fc5b59d2f6736f0704557aa805c4f2844084ccd1173b8d56946962": "0x000000000000000000000000000000000000000000000000000000110ed03bf7"
}
}
}
]
},
"response": {
"jsonrpc": "2.0",
"id": 3,
"error" : {
"code" : -32004,
"message" : "Upfront cost exceeds account balance"
}
},
"statusCode": 200
}
Loading

0 comments on commit 8b7ca20

Please sign in to comment.