Skip to content
This repository has been archived by the owner on Feb 26, 2024. It is now read-only.

fix: eth_call and eth_estimate gas limits (mostly) #449

Merged
merged 3 commits into from
Jul 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 35 additions & 57 deletions lib/blockchain_double.js
Original file line number Diff line number Diff line change
Expand Up @@ -481,93 +481,71 @@ BlockchainDouble.prototype.sortByPriceAndNonce = function() {
self.pending_transactions = sortedTransactions;
};

BlockchainDouble.prototype.processCall = function(tx, blockNumber, callback) {
var self = this;

var runCall = function(tx, err, parentBlock) {
BlockchainDouble.prototype.readyCall = function(tx, blockNumber, callback) {
const readyCall = (tx, err, parentBlock) => {
if (err) {
return callback(err);
}

// create a fake block with this fake transaction
self.createBlock(parentBlock, function(err, newBlock) {
this.createBlock(parentBlock, (err, newBlock) => {
if (err) {
return callback(err);
}

newBlock.transactions.push(tx);

var runArgs = {
// gas estimates and eth_calls aren't subject to block gas limits
newBlock.header.gasLimit = tx.gasLimit;

const runArgs = {
tx: tx,
block: newBlock,
skipBalance: true,
skipNonce: true
};

var stateTrie = self.createStateTrie(self.data.trie_db, parentBlock.header.stateRoot);
var vm = self.createVMFromStateTrie(stateTrie);

vm.runTx(runArgs, function(vmerr, result) {
// This is a check that has been in there for awhile. I'm unsure if it's required, but it can't hurt.
if (vmerr && vmerr instanceof Error === false) {
vmerr = new Error("VM error: " + vmerr);
}

// If we're given an error back directly, it's worse than a runtime error. Expose it and get out.
if (vmerr) {
return callback(vmerr, err);
}

// If no error, check for a runtime error. This can return null if no runtime error.
vmerr = RuntimeError.fromResults([tx], { results: [result] });

callback(vmerr, result);
});
const stateTrie = this.createStateTrie(this.data.trie_db, parentBlock.header.stateRoot);
const vm = this.createVMFromStateTrie(stateTrie);
callback(null, vm, runArgs);
});
};

// Delegate block selection
if (blockNumber === "latest") {
self.latestBlock(runCall.bind(null, tx));
this.latestBlock(readyCall.bind(null, tx));
} else {
self.getBlock(blockNumber, runCall.bind(null, tx));
this.getBlock(blockNumber, readyCall.bind(null, tx));
}
};

BlockchainDouble.prototype.estimateGas = function(tx, blockNumber, callback) {
var self = this;

var runCall = function(tx, err, parentBlock) {
BlockchainDouble.prototype.processCall = function(tx, blockNumber, callback) {
this.readyCall(tx, blockNumber, (err, vm, runArgs) => {
if (err) {
return callback(err);
callback(err);
return;
}

// create a fake block with this fake transaction
self.createBlock(parentBlock, function(err, newBlock) {
vm.runTx(runArgs, (err, result) => {
// If we're given an error back directly, it's worse than a runtime error. Expose it and get out.
if (err) {
return callback(err);
callback(err);
return;
}
newBlock.transactions.push(tx);

var runArgs = {
tx: tx,
block: newBlock,
skipBalance: true,
skipNonce: true
};

var stateTrie = self.createStateTrie(self.data.trie_db, parentBlock.header.stateRoot);
var vm = self.createVMFromStateTrie(stateTrie);

estimateGas(vm, runArgs, err, callback);
// If no vm error, check for a runtime error. This can return null if no runtime error.
const runtimeErr = RuntimeError.fromResults([tx], { results: [result] });
callback(runtimeErr, result);
});
};
});
};

// Delegate block selection
if (blockNumber === "latest") {
self.latestBlock(runCall.bind(null, tx));
} else {
self.getBlock(blockNumber, runCall.bind(null, tx));
}
BlockchainDouble.prototype.estimateGas = function(tx, blockNumber, callback) {
this.readyCall(tx, blockNumber, (err, vm, runArgs) => {
if (err) {
callback(err);
return;
}

estimateGas(vm, runArgs, err, callback);
});
};

/**
Expand Down
11 changes: 7 additions & 4 deletions lib/statemanager.js
Original file line number Diff line number Diff line change
Expand Up @@ -324,16 +324,16 @@ StateManager.prototype.queueTransaction = function(method, txJsonRpc, blockNumbe
let tx;
try {
tx = Transaction.fromJSON(txJsonRpc, type);
this._setTransactionDefaults(tx);
this._setTransactionDefaults(tx, method === "eth_sendTransaction");
} catch (e) {
callback(e);
return;
}
this._queueTransaction(method, tx, from, blockNumber, callback);
};

StateManager.prototype._setTransactionDefaults = function(tx) {
if (tx.gasLimit.length === 0) {
StateManager.prototype._setTransactionDefaults = function(tx, isTransaction) {
if (isTransaction && tx.gasLimit.length === 0) {
tx.gasLimit = utils.toBuffer(this.blockchain.defaultTransactionGasLimit);
}

Expand All @@ -356,7 +356,10 @@ StateManager.prototype._queueTransaction = function(method, tx, from, blockNumbe
}

// If the transaction has a higher gas limit than the block gas limit, error.
if (to.number(tx.gasLimit) > to.number(this.blockchain.blockGasLimit)) {
if (
(method === "eth_sendRawTransaction" || method === "eth_sendTransaction") &&
to.number(tx.gasLimit) > to.number(this.blockchain.blockGasLimit)
) {
return callback(new TXRejectedError("Exceeds block gas limit"));
}

Expand Down
28 changes: 20 additions & 8 deletions lib/subproviders/geth_api_double.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ const { BlockOutOfRangeError } = require("../utils/errorhelper");

var Subprovider = require("web3-provider-engine/subproviders/subprovider.js");

const maxUInt64 = "0x" + Number.MAX_SAFE_INTEGER.toString(16);

inherits(GethApiDouble, Subprovider);

function GethApiDouble(options, provider) {
Expand Down Expand Up @@ -333,18 +335,28 @@ GethApiDouble.prototype.eth_sendRawTransaction = function(rawTx, callback) {
this.state.queueRawTransaction(data, callback);
};

GethApiDouble.prototype.eth_call = function(txData, blockNumber, callback) {
if (!txData.gas) {
txData.gas = this.state.blockchain.blockGasLimit;
GethApiDouble.prototype._setCallGasLimit = function(txData) {
// if the caller didn't specify a gas limit make sure we set one
if (!txData.gas && !txData.gasLimit) {
const globalCallGasLimit = this.options.callGasLimit;
if (globalCallGasLimit != null) {
// if the user configured a global `callGasLimit` use it
txData.gas = globalCallGasLimit;
} else {
// otherwise, set a very high gas limit. We'd use Infinity, or some VM flag to ignore gasLimit checks like
// geth does, but the VM doesn't currently support that for `runTx`.
// https://github.com/ethereumjs/ethereumjs-vm/blob/4bbb6e394a344717890d618a6be1cf67b8e5b74d/lib/runTx.ts#L71
txData.gas = maxUInt64;
}
}

this.state.queueTransaction("eth_call", txData, blockNumber, callback); // :(
};
GethApiDouble.prototype.eth_call = function(txData, blockNumber, callback) {
this._setCallGasLimit(txData);
this.state.queueTransaction("eth_call", txData, blockNumber, callback);
};

GethApiDouble.prototype.eth_estimateGas = function(txData, blockNumber, callback) {
if (!txData.gas) {
txData.gas = this.state.blockchain.blockGasLimit;
}
this._setCallGasLimit(txData);
this.state.queueTransaction("eth_estimateGas", txData, blockNumber, callback);
};

Expand Down
32 changes: 25 additions & 7 deletions test/call.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,45 @@ const assert = require("assert");
const bootstrap = require("./helpers/contract/bootstrap");

describe("eth_call", function() {
let context;

before("Setting up web3 and contract", async function() {
this.timeout(10000);
it("should use the call gas limit if no call gas limit is specified in the call", async function() {
const contractRef = {
contractFiles: ["EstimateGas"],
contractSubdirectory: "gas"
};
let context;

context = await bootstrap(contractRef);
context = await bootstrap(contractRef, {
callGasLimit: "0x6691b7"
});
const { accounts, instance } = context;

const name = "0x54696d"; // Byte code for "Tim"
const description = "0x4120677265617420677579"; // Byte code for "A great guy"
const value = 5;

// this call uses more than the default transaction gas limit and will
// therefore fail if the block gas limit isn't used for calls
const status = await instance.methods.add(name, description, value).call({ from: accounts[0] });

assert.strictEqual(status, true);
});

it("should use the block gas limit if no gas limit is specified", async function() {
it("should use maxUInt64 call gas limit if no gas limit is specified in the provider or the call", async function() {
const contractRef = {
contractFiles: ["EstimateGas"],
contractSubdirectory: "gas"
};
let context;

context = await bootstrap(contractRef);
const { accounts, instance } = context;

const name = "0x54696d"; // Byte code for "Tim"
const description = "0x4120677265617420677579"; // Byte code for "A great guy"
const value = 5;

// this call uses more than the default transaction gas limit and will
// therefore fail if the block gas limit isn't used for calls
// therefore fail if the maxUInt64 limit isn't used for calls
const status = await instance.methods.add(name, description, value).call({ from: accounts[0] });

assert.strictEqual(status, true);
Expand Down