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

Feat: update ganache-core to support the Istanbul hardfork #517

Merged
merged 17 commits into from
Dec 10, 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
47 changes: 37 additions & 10 deletions lib/blockchain_double.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ var Block = require("ethereumjs-block");
var Log = require("./utils/log");
var Receipt = require("./utils/receipt");
var VM = require("ethereumjs-vm").default;
var Common = require("ethereumjs-common").default;
var RuntimeError = require("./utils/runtimeerror");
var Trie = require("merkle-patricia-tree");
var utils = require("ethereumjs-util");
var async = require("async");
var Heap = require("heap");
var Database = require("./database");
var EventEmitter = require("events");
var estimateGas = require("./utils/gasEstimation");
var estimateGas = require("./utils/gas/estimateGas");
var _ = require("lodash");
var promisify = require("util").promisify;
const BN = utils.BN;
Expand Down Expand Up @@ -43,7 +44,7 @@ const defaultOptions = {
defaultTransactionGasLimit: "0x15f90",
time: null,
debug: false,
hardfork: "petersburg",
hardfork: "istanbul",
allowUnlimitedContractSize: false
};

Expand Down Expand Up @@ -127,8 +128,21 @@ BlockchainDouble.prototype.initialize = function(accounts, callback) {

BlockchainDouble.prototype.createVMFromStateTrie = function(state, activatePrecompiles) {
const self = this;
const common = Common.forCustomChain(
"mainnet", // TODO needs to match chain id
{
name: "ganache",
networkId: self.options.network_id || self.forkVersion,
chainId: self.options._chainId,
comment: "Local test network",
bootstrapNodes: []
},
self.options.hardfork
);

const vm = new VM({
state: state,
common,
blockchain: {
// EthereumJS VM needs a blockchain object in order to get block information.
// When calling getBlock() it will pass a number that's of a Buffer type.
Expand All @@ -149,7 +163,6 @@ BlockchainDouble.prototype.createVMFromStateTrie = function(state, activatePreco
}
},
activatePrecompiles: activatePrecompiles || false,
hardfork: self.options.hardfork,
allowUnlimitedContractSize: self.options.allowUnlimitedContractSize
});

Expand Down Expand Up @@ -491,7 +504,7 @@ BlockchainDouble.prototype.sortByPriceAndNonce = function() {
self.pending_transactions = sortedTransactions;
};

BlockchainDouble.prototype.readyCall = function(tx, emulateParent, blockNumber, callback) {
BlockchainDouble.prototype.getReadyCall = function(tx, emulateParent, blockNumber, callback) {
const readyCall = (tx, err, parentBlock) => {
if (err) {
return callback(err);
Expand All @@ -513,9 +526,8 @@ BlockchainDouble.prototype.readyCall = function(tx, emulateParent, blockNumber,
skipBalance: true,
skipNonce: true
};
const stateTrie = this.createStateTrie(this.data.trie_db, parentBlock.header.stateRoot);
const vm = this.createVMFromStateTrie(stateTrie);
callback(null, vm, runArgs);

callback(null, parentBlock.header.stateRoot, runArgs);
});
};
// Delegate block selection
Expand All @@ -526,6 +538,18 @@ BlockchainDouble.prototype.readyCall = function(tx, emulateParent, blockNumber,
}
};

BlockchainDouble.prototype.readyCall = function(tx, emulateParent, blockNumber, callback) {
this.getReadyCall(tx, emulateParent, blockNumber, (err, stateRoot, runArgs) => {
if (err) {
callback(err);
return;
}
const stateTrie = this.createStateTrie(this.data.trie_db, stateRoot);
const vm = this.createVMFromStateTrie(stateTrie);
callback(null, vm, runArgs);
});
};

BlockchainDouble.prototype.processCall = function(tx, blockNumber, callback) {
this.readyCall(tx, true, blockNumber, async(err, vm, runArgs) => {
if (err) {
Expand Down Expand Up @@ -553,13 +577,16 @@ BlockchainDouble.prototype.processCall = function(tx, blockNumber, callback) {
};

BlockchainDouble.prototype.estimateGas = function(tx, blockNumber, callback) {
this.readyCall(tx, false, blockNumber, (err, vm, runArgs) => {
this.getReadyCall(tx, false, blockNumber, (err, stateRoot, runArgs) => {
if (err) {
callback(err);
return;
}

estimateGas(vm, runArgs, callback);
const generateVM = () => {
const stateTrie = this.createStateTrie(this.data.trie_db, stateRoot);
return this.createVMFromStateTrie(stateTrie);
};
estimateGas(generateVM, runArgs, callback);
});
};

Expand Down
29 changes: 28 additions & 1 deletion lib/database/txserializer.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// var to = require("../utils/to");
var Transaction = require("../utils/transaction");
var Common = require("ethereumjs-common").default;
var ethUtil = require("ethereumjs-util");

const decode = function(json, done) {
const options = {
Expand All @@ -16,11 +18,36 @@ const decode = function(json, done) {
s: json.s
};

const sigV = ethUtil.bufferToInt(options.v);
let chainId = Math.floor((sigV - 35) / 2);
if (chainId < 0) {
chainId = 0;
}

const commonOptions = {
name: "ganache",
chainId: 1,
networkId: 1,
comment: "Local test network"
};

let hardfork = "istanbul";
if (json._options) {
hardfork = json._options.hardfork;
commonOptions.chainId = json._options.chainId;
commonOptions.networkId = json._options.networkId;
}

const common = Common.forCustomChain(
"mainnet", // TODO needs to match chain id
commonOptions,
hardfork
);
// databases generated before ganache-core@2.3.2 didn't have a `_type` and
// and were always fake signed. So if _type is undefined it is "fake" (even
// if we have a valid signature that can generate the tx's `from`).
const type = json._type === undefined ? Transaction.types.fake : json._type;
const tx = Transaction.fromJSON(options, type);
const tx = Transaction.fromJSON(options, type, common);

// Commenting this out because we don't want to throw if the json.hash we
// put in is different that the tx.hash() calculation we now have. There
Expand Down
1 change: 1 addition & 0 deletions lib/provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ function Provider(options) {
}

const defaultOptions = {
_chainId: 1,
vmErrorsOnRPCResponse: true,
verbose: false,
asyncRequestProcessing: false,
Expand Down
6 changes: 3 additions & 3 deletions lib/statemanager.js
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ StateManager.prototype.getCode = function(address, number, callback) {
};

StateManager.prototype.queueRawTransaction = function(data, callback) {
const tx = new Transaction(data, Transaction.types.signed);
const tx = new Transaction(data, Transaction.types.signed, this.blockchain.vm.opts.common);
// use toLowerCase() to properly handle from addresses meant to be validated.
const from = to.hex(tx.from).toLowerCase();
this._queueTransaction("eth_sendRawTransaction", tx, from, null, callback);
Expand Down Expand Up @@ -336,7 +336,7 @@ StateManager.prototype.queueTransaction = function(method, txJsonRpc, blockNumbe

let tx;
try {
tx = Transaction.fromJSON(txJsonRpc, type);
tx = Transaction.fromJSON(txJsonRpc, type, this.blockchain.vm.opts.common);
this._setTransactionDefaults(tx, method === "eth_sendTransaction");
} catch (e) {
callback(e);
Expand Down Expand Up @@ -673,7 +673,7 @@ StateManager.prototype.processGasEstimate = function(from, tx, blockNumber, call
}
var result = "0x";
if (!results.error) {
result = results.gasRefund ? to.hex(results.gasEstimate.add(results.gasRefund)) : to.hex(results.gasEstimate);
result = to.hex(results.gasEstimate);
} else {
self.logger.log(`Error calculating gas estimate: ${results.error}`);
}
Expand Down
1 change: 0 additions & 1 deletion lib/subproviders/geth_api_double.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,6 @@ GethApiDouble.prototype.eth_blockNumber = function(callback) {
};

GethApiDouble.prototype.eth_chainId = function(callback) {
// chainId of 1337 is the default for private networks as per EIP-155
callback(null, to.hex(1337));
};

Expand Down
8 changes: 5 additions & 3 deletions lib/utils/forkedblockchain.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ function ForkedBlockchain(options) {
if (!protocolReg.test(options.fork)) {
// we don't have a protocol at all, assume ws
options.fork = "ws://" + options.fork;
fork = new Web3.providers.HttpProvider(options.fork);
fork = new Web3.providers.WebsocketProvider(options.fork);
} else if (validProtocolReg.test(options.fork)) {
if (httpReg.test(options.fork)) {
fork = new Web3.providers.HttpProvider(options.fork);
Expand Down Expand Up @@ -244,7 +244,9 @@ ForkedBlockchain.prototype.getFallbackBlock = function(numberOrHash, cb) {
block.header.extraData = utils.toBuffer(json.extraData);

(json.transactions || []).forEach(function(txJson, index) {
block.transactions.push(Transaction.fromJSON(txJson, Transaction.types.real));
block.transactions.push(
Transaction.fromJSON(txJson, Transaction.types.real, null, self.forkVersion, self.options.hardfork)
);
});

// Fake block. Let's do the worst.
Expand Down Expand Up @@ -425,7 +427,7 @@ ForkedBlockchain.prototype.getTransaction = function(hash, callback) {
}

if (result) {
result = Transaction.fromJSON(result, Transaction.types.signed);
result = Transaction.fromJSON(result, Transaction.types.signed, null, self.forkVersion, self.options.hardfork);
}

callback(null, result);
Expand Down
38 changes: 38 additions & 0 deletions lib/utils/gas/binSearch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
const { BN } = require("ethereumjs-util");
const hexToBn = (val = 0) => new BN(parseInt("0x" + val.toString("hex"), 16));
const MULTIPLE = 64 / 63;

module.exports = async function binSearch(generateVM, runArgs, result, callback) {
const MAX = hexToBn(runArgs.block.header.gasLimit);
const gasRefund = result.execResult.gasRefund;
const startingGas = gasRefund ? result.gasEstimate.add(gasRefund) : result.gasEstimate;
const range = { lo: startingGas, hi: startingGas };
const isEnoughGas = async(gas) => {
const vm = generateVM(); // Generate fresh VM
runArgs.tx.gasLimit = gas.toBuffer();
const result = await vm.runTx(runArgs).catch((vmerr) => ({ vmerr }));
return !result.vmerr && !result.execResult.exceptionError;
};

if (!(await isEnoughGas(range.hi))) {
do {
range.hi = range.hi.muln(MULTIPLE);
} while (!(await isEnoughGas(range.hi)));
while (range.lo.addn(1).lt(range.hi)) {
const mid = range.lo.add(range.hi).divn(2);
if (await isEnoughGas(mid)) {
range.hi = mid;
} else {
range.lo = mid;
}
}
if (range.hi.gte(MAX)) {
if (!(await isEnoughGas(range.hi))) {
return callback(new Error("gas required exceeds allowance or always failing transaction"));
}
}
}

result.gasEstimate = range.hi;
callback(null, result);
};
14 changes: 14 additions & 0 deletions lib/utils/gas/estimateGas.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const estimateGas = require("./guestimation");
const binSearch = require("./binSearch");

module.exports = async(generateVM, runArgs, callback) => {
const vm = generateVM();
estimateGas(vm, runArgs, async(err, result) => {
if (err) {
callback(err);
return;
}

await binSearch(generateVM, runArgs, result, callback);
});
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const RuntimeError = require("./runtimeerror");
const RuntimeError = require("../runtimeerror");

const { BN } = require("ethereumjs-util");
const bn = (val = 0) => new BN(val);
Expand Down
Loading