Skip to content

Commit

Permalink
Update eth_call handling of account balance (#1834)
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
shemnon authored Jan 27, 2021
1 parent ce104c0 commit e7a5b1c
Show file tree
Hide file tree
Showing 8 changed files with 146 additions and 58 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ public void shouldReturnErrorWithGasLimitTooLow() {
}

@Test
public void shouldReturnErrorWithGasPriceTooHigh() {
public void shouldReturnErrorWithGasPriceTooHighAndStrict() {
final JsonCallParameter callParameter =
new JsonCallParameter(
Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"),
Expand All @@ -167,7 +167,7 @@ public void shouldReturnErrorWithGasPriceTooHigh() {
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);
Expand All @@ -177,6 +177,29 @@ public void shouldReturnErrorWithGasPriceTooHigh() {
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 =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,16 @@
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;
Expand All @@ -55,9 +59,17 @@ protected BlockParameterOrBlockHash blockParameterOrBlockHash(
@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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,12 @@ public void shouldReturnNullWhenProcessorReturnsEmpty() {
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
Expand All @@ -130,7 +130,7 @@ public void shouldAcceptRequestWhenMissingOptionalFields() {
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
Expand All @@ -146,7 +146,7 @@ public void shouldReturnExecutionResultWhenExecutionIsSuccessful() {
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
Expand All @@ -155,33 +155,36 @@ public void shouldUseCorrectBlockNumberWhenLatest() {
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
public void shouldUseCorrectBlockNumberWhenSpecified() {
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() {
Expand Down Expand Up @@ -209,6 +212,6 @@ private void mockTransactionProcessorSuccessResult(final Bytes output) {
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));
}
}
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
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.
Expand Down Expand Up @@ -104,7 +105,7 @@ public Optional<TransactionSimulatorResult> processAtHead(final CallParameter ca
blockchain.getChainHeadHeader());
}

private Optional<TransactionSimulatorResult> process(
public Optional<TransactionSimulatorResult> process(
final CallParameter callParams,
final TransactionValidationParams transactionValidationParams,
final OperationTracer operationTracer,
Expand All @@ -131,7 +132,7 @@ private Optional<TransactionSimulatorResult> process(
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 =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
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;
Expand Down Expand Up @@ -149,7 +150,7 @@ public void shouldIncreaseBalanceAccountWhenExceedingBalanceAllowed() {
OperationTracer.NO_TRACING,
1L);

verify(mutableAccount).incrementBalance(Wei.of(Long.MAX_VALUE));
verify(mutableAccount).setBalance(Wei.of(UInt256.MAX_VALUE));
}

@Test
Expand Down

0 comments on commit e7a5b1c

Please sign in to comment.