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

Commit

Permalink
Fix forked transaction trace storage so it returns the correct data a…
Browse files Browse the repository at this point in the history
…nd doesn't modify the root trie (#398)
  • Loading branch information
davidmurdoch committed Apr 5, 2019
1 parent 903f1a0 commit df0578a
Show file tree
Hide file tree
Showing 4 changed files with 178 additions and 180 deletions.
140 changes: 46 additions & 94 deletions lib/utils/forkedblockchain.js
Expand Up @@ -60,22 +60,15 @@ ForkedBlockchain.prototype.initialize = function(accounts, callback) {

self.forkVersion = version;

BlockchainDouble.prototype.initialize.call(self, accounts, function(err) {
if (err) {
return callback(err);
}

self.patchVM(self.vm);

callback();
});
BlockchainDouble.prototype.initialize.call(self, accounts, callback);
});
};
ForkedBlockchain.prototype.patchVM = function(vm) {
const trie = vm.stateManager._trie;
const lookupAccount = this.getLookupAccount(trie);
// Unfortunately forking requires a bit of monkey patching, but it gets the job done.
vm.stateManager._lookupStorageTrie = this.lookupStorageTrie.bind(this);
vm.stateManager._cache._lookupAccount = this.getLookupAccount(trie);
vm.stateManager._cache._lookupAccount = lookupAccount;
vm.stateManager._lookupStorageTrie = this.getLookupStorageTrie(trie, lookupAccount);
vm.stateManager.getContractCode = this.getCode.bind(this);
};

Expand Down Expand Up @@ -108,7 +101,7 @@ ForkedBlockchain.prototype.createGenesisBlock = function(callback) {

// Update the relevant block numbers
self.forkBlockNumber = self.options.fork_block_number = blockNumber;
self.stateTrie.forkBlockNumber = blockNumber;
self.stateTrie.forkBlockNumber = self.stateTrie.options.forkBlockNumber = blockNumber;

self.createBlock(function(err, block) {
if (err) {
Expand All @@ -123,30 +116,20 @@ ForkedBlockchain.prototype.createGenesisBlock = function(callback) {
});
};

ForkedBlockchain.prototype.createForkedStorageTrie = function(address) {
address = to.hex(address);

var trie = new ForkedStorageTrie(this.data.trie_db, null, {
address: address,
stateTrie: this.stateTrie,
blockchain: this,
fork: this.fork,
forkBlockNumber: this.forkBlockNumber
});

this.storageTrieCache[address] = trie;

return trie;
};

ForkedBlockchain.prototype.lookupStorageTrie = function(address, callback) {
address = to.hex(address);

if (this.storageTrieCache[address] != null) {
return callback(null, this.storageTrieCache[address]);
}
ForkedBlockchain.prototype.getLookupStorageTrie = function(stateTrie, lookupAccount) {
lookupAccount = lookupAccount || this.getLookupAccount(stateTrie);
return (address, callback) => {
const storageTrie = stateTrie.copy();
storageTrie.address = address;
lookupAccount(address, (err, account) => {
if (err) {
return callback(err);
}

callback(null, this.createForkedStorageTrie(address));
storageTrie.root = account.stateRoot;
callback(null, storageTrie);
});
};
};

ForkedBlockchain.prototype.isFallbackBlock = function(value, callback) {
Expand All @@ -173,6 +156,10 @@ ForkedBlockchain.prototype.isFallbackBlockHash = function(value, callback) {
return callback(null, false);
}

if (Buffer.isBuffer(value)) {
value = to.hex(value);
}

self.data.blockHashes.get(value, function(err, blockIndex) {
if (err) {
if (err.notFound) {
Expand Down Expand Up @@ -229,52 +216,46 @@ ForkedBlockchain.prototype.getFallbackBlock = function(numberOrHash, cb) {
};

ForkedBlockchain.prototype.getBlock = function(number, callback) {
var self = this;

if (Buffer.isBuffer(number)) {
number = to.hex(number);
}

if (this.isBlockHash(number)) {
this.isFallbackBlockHash(number, handle);
let checkFn;
const isBlockHash = this.isBlockHash(number);
if (isBlockHash) {
checkFn = this.isFallbackBlockHash;
} else {
self.isFallbackBlock(number, handle);
checkFn = this.isFallbackBlock;
}

function handle(err, isFallback) {
checkFn.call(this, number, (err, isFallback) => {
if (err) {
return callback(err);
}
if (isFallback) {
return self.getFallbackBlock(number, callback);
return this.getFallbackBlock(number, callback);
}

getBlockReference(number, function(err, blockReference) {
if (err) {
return callback(err);
}

BlockchainDouble.prototype.getBlock.call(self, blockReference, callback);
});
}

// If we don't have string-based a block hash, turn what we do have into a number
// before sending it to getBlock.
function getBlockReference(value, callback) {
if (!self.isBlockHash(value)) {
self.getRelativeBlockNumber(value, callback);
const getBlock = BlockchainDouble.prototype.getBlock.bind(this);
if (isBlockHash) {
getBlock(number, callback);
} else {
callback(null, value);
this.getRelativeBlockNumber(number, (err, number) => {
if (err) {
return callback(err);
}
getBlock(number, callback);
});
}
}
});
};

ForkedBlockchain.prototype.getStorage = function(address, key, number, callback) {
this.lookupStorageTrie(address, function(err, trie) {
this.getLookupStorageTrie(this.stateTrie)(address, (err, trie) => {
if (err) {
return callback(err);
}
trie.get(key, callback);
this.getEffectiveBlockNumber(number, (err, number) => {
if (err) {
return callback(err);
}
trie.get(key, number, callback);
});
});
};

Expand Down Expand Up @@ -657,33 +638,4 @@ ForkedBlockchain.prototype.getBlockLogs = function(number, callback) {
});
};

ForkedBlockchain.prototype._checkpointTrie = function() {
var self = this;

BlockchainDouble.prototype._checkpointTrie.call(this);

Object.keys(this.storageTrieCache).forEach(function(address) {
var trie = self.storageTrieCache[address];
trie.customCheckpoint();
});
};

ForkedBlockchain.prototype._revertTrie = function() {
var self = this;

BlockchainDouble.prototype._revertTrie.call(this);

Object.keys(this.storageTrieCache).forEach(function(address) {
var trie = self.storageTrieCache[address];

// We're trying to revert to a point before this trie was created.
// Let's just remove the trie.
if (trie.checkpoints.length === 0) {
delete self.storageTrieCache[address];
} else {
trie.customRevert();
}
});
};

module.exports = ForkedBlockchain;
66 changes: 35 additions & 31 deletions lib/utils/forkedstoragetrie.js
@@ -1,38 +1,26 @@
var MerklePatriciaTree = require("merkle-patricia-tree");
const MerklePatriciaTree = require("merkle-patricia-tree");
const BaseTrie = require("merkle-patricia-tree/baseTrie");
const checkpointInterface = require("merkle-patricia-tree/checkpoint-interface");
var utils = require("ethereumjs-util");
var inherits = require("util").inherits;
var Web3 = require("web3");
var to = require("./to.js");

inherits(ForkedStorageTrie, MerklePatriciaTree);
inherits(ForkedStorageBaseTrie, BaseTrie);

function ForkedStorageTrie(db, root, options) {
MerklePatriciaTree.call(this, db, root);
function ForkedStorageBaseTrie(db, root, options) {
BaseTrie.call(this, db, root);

this.options = options;
this.address = options.address;

this.fork = options.fork;
this.forkBlockNumber = options.forkBlockNumber;

this.blockchain = options.blockchain;

this.web3 = new Web3();
this.web3.setProvider(this.fork);

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

ForkedStorageTrie.prototype.keyExists = function(key, callback) {
key = utils.toBuffer(key);

this.findPath(key, function(err, node, remainder, stack) {
const exists = node && remainder.length === 0;
callback(err, exists);
});
};

// Note: This overrides a standard method whereas the other methods do not.
ForkedStorageTrie.prototype.get = function(key, blockNumber, callback) {
ForkedStorageBaseTrie.prototype.get = function(key, blockNumber, callback) {
var self = this;

// Allow an optional blockNumber
Expand All @@ -53,6 +41,9 @@ ForkedStorageTrie.prototype.get = function(key, blockNumber, callback) {
}

if (exists) {
// TODO: just because we have the key doesn't mean we're at the right
// block number/root to send it. We need to check the block number
// before using the data in our own trie.
MerklePatriciaTree.prototype.get.call(self, key, function(err, r) {
callback(err, r);
});
Expand All @@ -67,12 +58,14 @@ ForkedStorageTrie.prototype.get = function(key, blockNumber, callback) {
callback(null, account.serialize());
});
} else {
if (to.number(blockNumber) > to.number(self.forkBlockNumber)) {
blockNumber = self.forkBlockNumber;
}
self.web3.eth.getStorageAt(to.hex(self.address), to.hex(key), blockNumber, function(err, value) {
if (err) {
return callback(err);
}

value = utils.toBuffer(value);
value = utils.rlp.encode(value);

callback(null, value);
Expand All @@ -82,16 +75,27 @@ ForkedStorageTrie.prototype.get = function(key, blockNumber, callback) {
});
};

// I don't want checkpoints to be removed by commits.
// Note: For some reason, naming this function checkpoint()
// -- overriding the default function -- prevents it from
// being called.
ForkedStorageTrie.prototype.customCheckpoint = function() {
this.checkpoints.push(this.root);
ForkedStorageBaseTrie.prototype.keyExists = function(key, callback) {
key = utils.toBuffer(key);

this.findPath(key, function(err, node, remainder, stack) {
const exists = node && remainder.length === 0;
callback(err, exists);
});
};

ForkedStorageTrie.prototype.customRevert = function() {
this.root = this.checkpoints.pop();
ForkedStorageBaseTrie.prototype.copy = function() {
return new ForkedStorageBaseTrie(this.db, this.root, this.options);
};

inherits(ForkedStorageTrie, ForkedStorageBaseTrie);

function ForkedStorageTrie(db, root, options) {
ForkedStorageBaseTrie.call(this, db, root, options);
checkpointInterface(this);
}

ForkedStorageTrie.prove = MerklePatriciaTree.prove;
ForkedStorageTrie.verifyProof = MerklePatriciaTree.verifyProof;

module.exports = ForkedStorageTrie;

0 comments on commit df0578a

Please sign in to comment.