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

fix(forking): fix snapshots for forking #627

Merged
merged 19 commits into from
Sep 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
2 changes: 1 addition & 1 deletion lib/blockchain_double.js
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,7 @@ BlockchainDouble.prototype.readyCall = function(tx, emulateParent, blockNumber,
callback(err);
return;
}
const stateTrie = this.createStateTrie(this.data.trie_db, stateRoot);
const stateTrie = this.createStateTrie(this.data.trie_db, stateRoot, { persist: false });
const vm = this.createVMFromStateTrie(stateTrie);
callback(null, vm, runArgs);
});
Expand Down
229 changes: 187 additions & 42 deletions lib/forking/forked_blockchain.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ var to = require("../utils/to.js");
var Transaction = require("../utils/transaction");
var async = require("async");
var LRUCache = require("lru-cache");
const Sublevel = require("level-sublevel");
const BN = utils.BN;

var inherits = require("util").inherits;
Expand Down Expand Up @@ -162,6 +163,7 @@ function ForkedBlockchain(options) {
};

this.web3 = new Web3(this.fork);
this._touchedKeys = [];
}

ForkedBlockchain.prototype.initialize = async function(accounts, callback) {
Expand Down Expand Up @@ -424,36 +426,60 @@ ForkedBlockchain.prototype.getBlock = function(number, callback) {
};

ForkedBlockchain.prototype.getStorage = function(address, key, number, callback) {
this.getLookupStorageTrie(this.stateTrie)(address, (err, trie) => {
var self = this;

this.getEffectiveBlockNumber(number, (err, blockNumber) => {
if (err) {
return callback(err);
}
this.getEffectiveBlockNumber(number, (err, blockNumber) => {
if (err) {
return callback(err);
}

if (blockNumber > this.forkBlockNumber) {
// only hit the ForkedStorageTrieBase if we're not looking
// for something that's on the forked chain
trie.get(utils.setLengthLeft(utils.toBuffer(key), 32), blockNumber, callback);
} else {
// we're looking for something prior to forking, so let's
// hit eth_getStorageAt
this.web3.eth.getStorageAt(to.rpcDataHexString(address), to.rpcDataHexString(key), blockNumber, function(
err,
value
) {
if (blockNumber > self.forkBlockNumber) {
// we should have this block

self.getBlock(blockNumber, function(err, block) {
if (err) {
return callback(err);
}

const trie = self.stateTrie;

// Manipulate the state root in place to maintain checkpoints
const currentStateRoot = trie.root;
self.stateTrie.root = block.header.stateRoot;

self.getLookupStorageTrie(self.stateTrie)(address, (err, trie) => {
if (err) {
return callback(err);
}

value = utils.rlp.encode(value);
trie.get(utils.setLengthLeft(utils.toBuffer(key), 32), function(err, value) {
// Finally, put the stateRoot back for good
trie.root = currentStateRoot;

if (err != null) {
return callback(err);
}

callback(null, value);
callback(null, value);
});
});
}
});
});
} else {
// we're looking for something prior to forking, so let's
// hit eth_getStorageAt
self.web3.eth.getStorageAt(to.rpcDataHexString(address), to.rpcDataHexString(key), blockNumber, function(
err,
value
) {
if (err) {
return callback(err);
}

value = utils.rlp.encode(value);

callback(null, value);
});
}
});
};

Expand All @@ -475,34 +501,23 @@ ForkedBlockchain.prototype.getCode = function(address, number, callback) {
}
number = effective;

self.stateTrie.keyExists(address, function(err, exists) {
self.stateTrie.getTouchedAt(address, (err, touchedAt) => {
if (err) {
return callback(err);
}
// If we've stored the value and we're looking at one of our stored blocks,
// get it from our stored data.
if (exists && number > to.number(self.forkBlockNumber)) {

if (typeof touchedAt !== "undefined" && touchedAt <= number) {
BlockchainDouble.prototype.getCode.call(self, address, number, callback);
} else {
self.stateTrie.keyIsDeleted(address, (err, deleted) => {
if (err) {
return callback(err);
}
if (deleted) {
return callback(null, Buffer.allocUnsafe(0));
}
// Else, we need to fetch it from web3. If our number is greater than
// the fork, let's just use "latest".
if (number > to.number(self.forkBlockNumber)) {
number = "latest";
}
if (number > to.number(self.forkBlockNumber)) {
number = "latest";
}

self.fetchCodeFromFallback(address, number, function(err, code) {
if (code) {
code = utils.toBuffer(code);
}
callback(err, code);
});
self.fetchCodeFromFallback(address, number, function(err, code) {
if (code) {
code = utils.toBuffer(code);
}
callback(err, code);
});
}
});
Expand Down Expand Up @@ -845,6 +860,136 @@ ForkedBlockchain.prototype.getBlockLogs = function(number, callback) {
});
};

ForkedBlockchain.prototype.getQueuedNonce = function(address, callback) {
var nonce = null;
var addressBuffer = to.buffer(address);
this.pending_transactions.forEach(function(tx) {
if (!tx.from.equals(addressBuffer)) {
return;
}

var pendingNonce = new BN(tx.nonce);
// If this is the first queued nonce for this address we found,
// or it's higher than the previous highest, note it.
if (nonce === null || pendingNonce.gt(nonce)) {
nonce = pendingNonce;
}
});

// If we found a queued transaction nonce, return one higher
// than the highest we found
if (nonce != null) {
return callback(null, nonce.iaddn(1).toArrayLike(Buffer));
}
this.getLookupAccount(this.stateTrie)(addressBuffer, function(err, account) {
if (err) {
return callback(err);
}

// nonces are initialized as an empty buffer, which isn't what we want.
callback(null, account.nonce.length === 0 ? Buffer.from([0]) : account.nonce);
});
};

ForkedBlockchain.prototype.processCall = function(tx, blockNumber, callback) {
const self = this;

this.getEffectiveBlockNumber(blockNumber, function(err, effectiveBlockNumber) {
if (err) {
return callback(err);
}

if (effectiveBlockNumber > self.forkBlockNumber) {
BlockchainDouble.prototype.processCall.call(self, tx, blockNumber, callback);
} else {
self.web3.eth.call({
from: to.rpcDataHexString(tx.from),
to: to.nullableRpcDataHexString(tx.to),
data: to.rpcDataHexString(tx.data)
}, effectiveBlockNumber, function(err, result) {
if (err) {
return callback(err);
}

callback(null, {
execResult: {
returnValue: result
}
});
});
}
});
};

ForkedBlockchain.prototype.processBlock = async function(vm, block, commit, callback) {
const self = this;

self._touchedKeys = [];
BlockchainDouble.prototype.processBlock.call(self, vm, block, commit, callback);
};

ForkedBlockchain.prototype.putBlock = function(block, logs, receipts, callback) {
const self = this;
const touched = Sublevel(self.data.trie_db).sublevel("touched");
const blockKey = `block-${to.number(block.header.number)}`;

BlockchainDouble.prototype.putBlock.call(self, block, logs, receipts, function(err, result) {
if (err) {
return callback(err);
}

touched.put(blockKey, JSON.stringify(self._touchedKeys), (err) => {
if (err) {
return callback(err);
}

callback(null, result);
});
});
};

ForkedBlockchain.prototype.popBlock = function(callback) {
const self = this;
const touched = Sublevel(this.data.trie_db).sublevel("touched");

this.data.blocks.last(function(err, block) {
if (err) {
return callback(err);
}
if (block == null) {
return callback(null, null);
}

const blockKey = `block-${to.number(block.header.number)}`;
touched.get(blockKey, function(err, value) {
if (err) {
return callback(err);
}

const touchedKeys = value ? JSON.parse(value) : [];
async.eachSeries(
touchedKeys,
function(touchedKey, finished) {
touched.del(touchedKey, finished);
},
function(err) {
if (err) {
return callback(err);
}

touched.del(blockKey, function(err) {
if (err) {
return callback(err);
}

BlockchainDouble.prototype.popBlock.call(self, callback);
});
}
);
});
});
};

ForkedBlockchain.prototype.close = function(callback) {
if (this.fork.disconnect) {
this.fork.disconnect();
Expand Down
Loading