Update eth_call handling of account balance (#1834)

When calling `eth_call` by default account balances will be ignored when
executing the call. If the user wants the gas balance to be a
consideration in the call a new `strict` param in the call params can be
set to true, which will enforce the balance rules.  This is the same
behavior as is observed in `eth_estimateGas`.

Addresses #502

Signed-off-by: Danno Ferrin <danno.ferrin@gmail.com>
pull/1841/head
Danno Ferrin 4 years ago committed by GitHub
parent ce104c0ec5
commit e7a5b1cd4a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      CHANGELOG.md
  2. 27
      ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/EthCallIntegrationTest.java
  3. 14
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCall.java
  4. 25
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCallTest.java
  5. 63
      ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_gasPriceTooHigh_block_8.json
  6. 63
      ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_valueTooHigh_block_8.json
  7. 5
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulator.java
  8. 3
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulatorTest.java

@ -5,6 +5,7 @@
### 21.2.0 Breaking Changes
* `--skip-pow-validation-enabled` is now an error with `block import --format JSON`. This is because the JSON format doesn't include the nonce so the proof of work must be calculated.
* `eth_call` will not return a JSON-RPC result if the call fails, but will return an error instead. If it was for a revert the revert reason will be included.
* `eth_call` will not fail for account balance issues by default. An parameter `"strict": true` can be added to the call parameters (with `to` and `from`) to enforce balance checks.
### Additions and Improvements
* Removed unused flags in default genesis configs [\#1812](https://github.com/hyperledger/besu/pull/1812)
@ -13,7 +14,8 @@
* Return the revert reason from `eth_call` JSON-RPC api calls when the contract causes a revert. [\#1829](https://github.com/hyperledger/besu/pull/1829)
### Bug Fixes
* Ethereum classic heights will no longer be reported in mainnet metrics. Issue [\#1751]((https://github.com/hyperledger/besu/pull/1751) Fix [\#1820](https://github.com/hyperledger/besu/pull/1820)
* Ethereum classic heights will no longer be reported in mainnet metrics. Issue [\#1751]((https://github.com/hyperledger/besu/pull/1751) Fix [\#1820](https://github.com/hyperledger/besu/pull/1820)
* Don't enforce balance checks in `eth_call` unless explicitly requested. Issue [\#502]((https://github.com/hyperledger/besu/pull/502) Fix [\#1834](https://github.com/hyperledger/besu/pull/1834)
### Early Access Features

@ -156,7 +156,7 @@ public class EthCallIntegrationTest {
}
@Test
public void shouldReturnErrorWithGasPriceTooHigh() {
public void shouldReturnErrorWithGasPriceTooHighAndStrict() {
final JsonCallParameter callParameter =
new JsonCallParameter(
Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"),
@ -167,7 +167,7 @@ public class EthCallIntegrationTest {
null,
null,
Bytes.fromHexString("0x12a7b914"),
null);
true);
final JsonRpcRequestContext request = requestWithParams(callParameter, "latest");
final JsonRpcResponse expectedResponse =
new JsonRpcErrorResponse(null, JsonRpcError.TRANSACTION_UPFRONT_COST_EXCEEDS_BALANCE);
@ -177,6 +177,29 @@ public class EthCallIntegrationTest {
assertThat(response).isEqualToComparingFieldByField(expectedResponse);
}
@Test
public void shouldReturnSuccessWithGasPriceTooHighNotStrict() {
final JsonCallParameter callParameter =
new JsonCallParameter(
Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"),
Address.fromHexString("0x6295ee1b4f6dd65047762f924ecd367c17eabf8f"),
null,
Wei.fromHexString("0x10000000000000"),
null,
null,
null,
Bytes.fromHexString("0x12a7b914"),
false);
final JsonRpcRequestContext request = requestWithParams(callParameter, "latest");
final JsonRpcResponse expectedResponse =
new JsonRpcSuccessResponse(
null, "0x0000000000000000000000000000000000000000000000000000000000000001");
final JsonRpcResponse response = method.response(request);
assertThat(response).isEqualToComparingFieldByField(expectedResponse);
}
@Test
public void shouldReturnEmptyHashResultForCallWithOnlyToField() {
final JsonCallParameter callParameter =

@ -25,12 +25,16 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorR
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.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.Hash;
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.TransactionInvalidReason;
import org.hyperledger.besu.ethereum.transaction.TransactionSimulator;
import org.hyperledger.besu.ethereum.transaction.TransactionSimulatorResult;
import org.hyperledger.besu.ethereum.vm.OperationTracer;
public class EthCall extends AbstractBlockParameterOrBlockHashMethod {
private final TransactionSimulator transactionSimulator;
@ -55,9 +59,17 @@ public class EthCall extends AbstractBlockParameterOrBlockHashMethod {
@Override
protected Object resultByBlockHash(final JsonRpcRequestContext request, final Hash blockHash) {
final JsonCallParameter callParams = validateAndGetCallParams(request);
final BlockHeader header = blockchainQueries.get().getBlockHeaderByHash(blockHash).orElse(null);
return transactionSimulator
.process(callParams, blockHash)
.process(
callParams,
ImmutableTransactionValidationParams.builder()
.from(TransactionValidationParams.transactionSimulator())
.isAllowExceedingBalance(!callParams.isStrict())
.build(),
OperationTracer.NO_TRACING,
header)
.map(
result ->
result

@ -105,12 +105,12 @@ public class EthCallTest {
when(blockchainQueries.getBlockchain()).thenReturn(blockchain);
when(blockchainQueries.getBlockchain().getChainHead()).thenReturn(chainHead);
when(blockchainQueries.getBlockchain().getChainHead().getHash()).thenReturn(Hash.ZERO);
when(transactionSimulator.process(any(), any())).thenReturn(Optional.empty());
when(transactionSimulator.process(any(), any(), any(), any())).thenReturn(Optional.empty());
final JsonRpcResponse response = method.response(request);
assertThat(response).usingRecursiveComparison().isEqualTo(expectedResponse);
verify(transactionSimulator).process(any(), any());
verify(transactionSimulator).process(any(), any(), any(), any());
}
@Test
@ -130,7 +130,7 @@ public class EthCallTest {
final JsonRpcResponse response = method.response(request);
assertThat(response).usingRecursiveComparison().isEqualTo(expectedResponse);
verify(transactionSimulator).process(eq(callParameter), any());
verify(transactionSimulator).process(eq(callParameter), any(), any(), any());
}
@Test
@ -146,7 +146,7 @@ public class EthCallTest {
final JsonRpcResponse response = method.response(request);
assertThat(response).usingRecursiveComparison().isEqualTo(expectedResponse);
verify(transactionSimulator).process(eq(callParameter()), any());
verify(transactionSimulator).process(eq(callParameter()), any(), any(), any());
}
@Test
@ -155,21 +155,23 @@ public class EthCallTest {
when(blockchainQueries.getBlockchain()).thenReturn(blockchain);
when(blockchainQueries.getBlockchain().getChainHead()).thenReturn(chainHead);
when(blockchainQueries.getBlockchain().getChainHead().getHash()).thenReturn(Hash.ZERO);
when(transactionSimulator.process(any(), any())).thenReturn(Optional.empty());
when(transactionSimulator.process(any(), any(), any(), any())).thenReturn(Optional.empty());
method.response(request);
verify(transactionSimulator).process(any(), eq(Hash.ZERO));
verify(blockchainQueries).getBlockHeaderByHash(eq(Hash.ZERO));
verify(transactionSimulator).process(any(), any(), any(), any());
}
@Test
public void shouldUseCorrectBlockNumberWhenEarliest() {
final JsonRpcRequestContext request = ethCallRequest(callParameter(), "earliest");
when(blockchainQueries.getBlockHashByNumber(anyLong())).thenReturn(Optional.of(Hash.ZERO));
when(transactionSimulator.process(any(), any())).thenReturn(Optional.empty());
when(transactionSimulator.process(any(), any(), any(), any())).thenReturn(Optional.empty());
method.response(request);
verify(transactionSimulator).process(any(), eq(Hash.ZERO));
verify(blockchainQueries).getBlockHeaderByHash(eq(Hash.ZERO));
verify(transactionSimulator).process(any(), any(), any(), any());
}
@Test
@ -177,11 +179,12 @@ public class EthCallTest {
final JsonRpcRequestContext request = ethCallRequest(callParameter(), Quantity.create(13L));
when(blockchainQueries.headBlockNumber()).thenReturn(14L);
when(blockchainQueries.getBlockHashByNumber(anyLong())).thenReturn(Optional.of(Hash.ZERO));
when(transactionSimulator.process(any(), any())).thenReturn(Optional.empty());
when(transactionSimulator.process(any(), any(), any(), any())).thenReturn(Optional.empty());
method.response(request);
verify(transactionSimulator).process(any(), eq(Hash.ZERO));
verify(blockchainQueries).getBlockHeaderByHash(eq(Hash.ZERO));
verify(transactionSimulator).process(any(), any(), any(), any());
}
private JsonCallParameter callParameter() {
@ -209,6 +212,6 @@ public class EthCallTest {
when(result.isSuccessful()).thenReturn(true);
when(result.getValidationResult()).thenReturn(ValidationResult.valid());
when(result.getOutput()).thenReturn(output);
when(transactionSimulator.process(any(), any())).thenReturn(Optional.of(result));
when(transactionSimulator.process(any(), any(), any(), any())).thenReturn(Optional.of(result));
}
}

@ -1,24 +1,47 @@
{
"request": {
"id": 4,
"jsonrpc": "2.0",
"method": "eth_call",
"params": [
{
"to": "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f",
"from": "a94f5374fce5edbc8e2a8697c15331677e6ebf0b",
"gasPrice": "0x10000000000000"
},
"0x8"
]
},
"response": {
"jsonrpc": "2.0",
"id": 4,
"error" : {
"code" : -32004,
"message" : "Upfront cost exceeds account balance"
"request": [
{
"id": 3,
"jsonrpc": "2.0",
"method": "eth_call",
"params": [
{
"to": "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f",
"from": "a94f5374fce5edbc8e2a8697c15331677e6ebf0b",
"gasPrice": "0x10000000000000"
},
"0x8"
]
},
{
"id": 4,
"jsonrpc": "2.0",
"method": "eth_call",
"params": [
{
"to": "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f",
"from": "a94f5374fce5edbc8e2a8697c15331677e6ebf0b",
"gasPrice": "0x10000000000000",
"strict": true
},
"0x8"
]
}
},
],
"response": [
{
"jsonrpc": "2.0",
"id": 3,
"result": "0x"
},
{
"jsonrpc": "2.0",
"id": 4,
"error": {
"code": -32004,
"message": "Upfront cost exceeds account balance"
}
}
],
"statusCode": 200
}

@ -1,24 +1,47 @@
{
"request": {
"id": 4,
"jsonrpc": "2.0",
"method": "eth_call",
"params": [
{
"to": "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f",
"from": "a94f5374fce5edbc8e2a8697c15331677e6ebf0b",
"value": "0x340ab63a021fc9aa"
},
"0x8"
]
},
"response": {
"jsonrpc": "2.0",
"id": 4,
"error" : {
"code" : -32004,
"message" : "Upfront cost exceeds account balance"
"request": [
{
"id": 3,
"jsonrpc": "2.0",
"method": "eth_call",
"params": [
{
"to": "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f",
"from": "a94f5374fce5edbc8e2a8697c15331677e6ebf0b",
"value": "0x340ab63a021fc9aa"
},
"0x8"
]
},
{
"id": 4,
"jsonrpc": "2.0",
"method": "eth_call",
"params": [
{
"to": "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f",
"from": "a94f5374fce5edbc8e2a8697c15331677e6ebf0b",
"value": "0x340ab63a021fc9aa",
"strict": true
},
"0x8"
]
}
},
],
"response": [
{
"jsonrpc": "2.0",
"id": 3,
"result": "0x"
},
{
"jsonrpc": "2.0",
"id": 4,
"error": {
"code": -32004,
"message": "Upfront cost exceeds account balance"
}
}
],
"statusCode": 200
}

@ -37,6 +37,7 @@ import org.hyperledger.besu.plugin.data.TransactionType;
import java.util.Optional;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.units.bigints.UInt256;
/*
* Used to process transactions for eth_call and eth_estimateGas.
@ -104,7 +105,7 @@ public class TransactionSimulator {
blockchain.getChainHeadHeader());
}
private Optional<TransactionSimulatorResult> process(
public Optional<TransactionSimulatorResult> process(
final CallParameter callParams,
final TransactionValidationParams transactionValidationParams,
final OperationTracer operationTracer,
@ -131,7 +132,7 @@ public class TransactionSimulator {
final WorldUpdater updater = worldState.updater();
if (transactionValidationParams.isAllowExceedingBalance()) {
updater.getOrCreate(senderAddress).getMutable().incrementBalance(Wei.of(Long.MAX_VALUE));
updater.getOrCreate(senderAddress).getMutable().setBalance(Wei.of(UInt256.MAX_VALUE));
}
final Transaction.Builder transactionBuilder =

@ -47,6 +47,7 @@ import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
import java.util.Optional;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.units.bigints.UInt256;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -149,7 +150,7 @@ public class TransactionSimulatorTest {
OperationTracer.NO_TRACING,
1L);
verify(mutableAccount).incrementBalance(Wei.of(Long.MAX_VALUE));
verify(mutableAccount).setBalance(Wei.of(UInt256.MAX_VALUE));
}
@Test

Loading…
Cancel
Save