From 395cc1050d745c61d069afe33b437c83ceb2ae03 Mon Sep 17 00:00:00 2001 From: David Murdoch Date: Fri, 13 Sep 2019 16:36:56 -0400 Subject: [PATCH] Fix: forking value and code deletion/destruction (#482) * WIP * Fix: forked deletion now works (at least at head of tree) * Make sure we handle deleted contracts, too * Remove log and fix typo --- lib/utils/forkedblockchain.js | 28 +++++++---- lib/utils/forkedstoragetrie.js | 78 ++++++++++++++++++++--------- test/block-tags.js | 4 +- test/contracts/examples/Example.sol | 4 ++ test/forking.js | 23 +++++++++ test/requests.js | 6 +-- 6 files changed, 103 insertions(+), 40 deletions(-) diff --git a/lib/utils/forkedblockchain.js b/lib/utils/forkedblockchain.js index c4fe41fe9e..8bdd231108 100644 --- a/lib/utils/forkedblockchain.js +++ b/lib/utils/forkedblockchain.js @@ -306,17 +306,25 @@ ForkedBlockchain.prototype.getCode = function(address, number, callback) { if (exists && number > to.number(self.forkBlockNumber)) { BlockchainDouble.prototype.getCode.call(self, address, number, callback); } else { - // 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"; - } - - self.fetchCodeFromFallback(address, number, function(err, code) { - if (code) { - code = utils.toBuffer(code); + self.stateTrie.keyIsDeleted(address, (err, deleted) => { + if (err) { + return callback(err); + } + if (deleted) { + return callback(null, Buffer.allocUnsafe(0)); } - callback(err, code); + // 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"; + } + + self.fetchCodeFromFallback(address, number, function(err, code) { + if (code) { + code = utils.toBuffer(code); + } + callback(err, code); + }); }); } }); diff --git a/lib/utils/forkedstoragetrie.js b/lib/utils/forkedstoragetrie.js index f816acd873..7947bc5dd4 100644 --- a/lib/utils/forkedstoragetrie.js +++ b/lib/utils/forkedstoragetrie.js @@ -1,3 +1,4 @@ +const Sublevel = require("level-sublevel"); const MerklePatriciaTree = require("merkle-patricia-tree"); const BaseTrie = require("merkle-patricia-tree/baseTrie"); const checkpointInterface = require("merkle-patricia-tree/checkpoint-interface"); @@ -10,6 +11,7 @@ inherits(ForkedStorageBaseTrie, BaseTrie); function ForkedStorageBaseTrie(db, root, options) { BaseTrie.call(this, db, root); + this._deleted = Sublevel(this.db).sublevel("deleted"); this.options = options; this.address = options.address; @@ -45,45 +47,71 @@ ForkedStorageBaseTrie.prototype.get = function(key, blockNumber, callback) { callback(err, r); }); } else { - // If this is the main trie, get the whole account. - if (self.address == null) { - self.blockchain.fetchAccountFromFallback(key, blockNumber, function(err, account) { - if (err) { - return callback(err); - } - - callback(null, account.serialize()); - }); - } else { - if (to.number(blockNumber) > to.number(self.forkBlockNumber)) { - blockNumber = self.forkBlockNumber; + self.keyIsDeleted(key, (err, deleted) => { + if (err) { + return callback(err); } - self.web3.eth.getStorageAt(to.rpcDataHexString(self.address), to.rpcDataHexString(key), blockNumber, function( - err, - value - ) { - if (err) { - return callback(err); - } - value = utils.rlp.encode(value); + if (deleted) { + // it was deleted. return nothing. + callback(null, Buffer.allocUnsafe(0)); + return; + } - callback(null, value); - }); - } + // If this is the main trie, get the whole account. + if (self.address == null) { + self.blockchain.fetchAccountFromFallback(key, blockNumber, function(err, account) { + if (err) { + return callback(err); + } + + callback(null, account.serialize()); + }); + } else { + if (to.number(blockNumber) > to.number(self.forkBlockNumber)) { + blockNumber = self.forkBlockNumber; + } + self.web3.eth.getStorageAt(to.rpcDataHexString(self.address), to.rpcDataHexString(key), blockNumber, function( + err, + value + ) { + if (err) { + return callback(err); + } + + value = utils.rlp.encode(value); + + callback(null, value); + }); + } + }); } }); }; ForkedStorageBaseTrie.prototype.keyExists = function(key, callback) { key = utils.toBuffer(key); - - this.findPath(key, function(err, node, remainder, stack) { + this.findPath(key, (err, node, remainder, stack) => { const exists = node && remainder.length === 0; callback(err, exists); }); }; +ForkedStorageBaseTrie.prototype.keyIsDeleted = function(key, callback) { + const rpcKey = to.rpcDataHexString(key); + this._deleted.get(rpcKey, (_, result) => { + callback(null, result === 1); + }); +}; + +const originalDelete = ForkedStorageBaseTrie.prototype.del; +ForkedStorageBaseTrie.prototype.del = function(key, callback) { + const rpcKey = to.rpcDataHexString(key); + this._deleted.put(rpcKey, 1, () => { + originalDelete.call(this, key, callback); + }); +}; + ForkedStorageBaseTrie.prototype.copy = function() { return new ForkedStorageBaseTrie(this.db, this.root, this.options); }; diff --git a/test/block-tags.js b/test/block-tags.js index a61fa0fa5d..e10853545c 100644 --- a/test/block-tags.js +++ b/test/block-tags.js @@ -93,12 +93,12 @@ describe("Block Tags", function() { assert.notStrictEqual(block.transactionsRoot, block.receiptsRoot, "Trie roots should not be equal."); assert.strictEqual( block.transactionsRoot, - "0x474793ee3373c6d26f28a1f2d2f3250bdabb45cecb1363878faaf741e452fc6e", + "0xac9fd78357964d268cecfafaab179473c0f02dda08edb172bf446fbe9c4aafc2", "Should produce correct transactionsRoot" ); assert.strictEqual( block.receiptsRoot, - "0xc892acfe66c3eccdc78fba6505871bc47a64fceb076d8aff440fb3545ef4a285", + "0x281d7cb7302acfc7cc33b1f2d06cad99650882ff1b0abebbdd32f77c14c9b98e", "Should produce correct receiptsRoot" ); }); diff --git a/test/contracts/examples/Example.sol b/test/contracts/examples/Example.sol index beaf3e649e..702b1d5a3c 100644 --- a/test/contracts/examples/Example.sol +++ b/test/contracts/examples/Example.sol @@ -13,4 +13,8 @@ contract Example { value = val; emit ValueSet(val); } + + function destruct() public { + selfdestruct(msg.sender); + } } diff --git a/test/forking.js b/test/forking.js index 1274900891..43ac0a6c93 100644 --- a/test/forking.js +++ b/test/forking.js @@ -478,6 +478,29 @@ describe("Forking", function() { assert.strictEqual(mainNetwork, forkedNetwork); }); + it("should be able to delete data", async() => { + const from = mainAccounts[0]; + const example = new mainWeb3.eth.Contract(contract.abi, contractAddress); + + // delete the data from our fork + await example.methods.setValue(0).send({ from }); + const result = await example.methods.value().call(); + assert.strictEqual(result, "0"); + await example.methods.setValue(7).send({ from }); + const result2 = await example.methods.value().call(); + assert.strictEqual(result2, "7"); + }); + + it("should be able to selfdestruct a contract", async() => { + const from = mainAccounts[0]; + const example = new mainWeb3.eth.Contract(contract.abi, contractAddress); + + // delete the contract from our fork + await example.methods.destruct().send({ from }); + const code = await mainWeb3.eth.getCode(contractAddress); + assert.strictEqual(code, "0x"); + }); + describe("Can debug a transaction", function() { let send; before("generate send", function() { diff --git a/test/requests.js b/test/requests.js index 493584d006..b2d41a54a2 100644 --- a/test/requests.js +++ b/test/requests.js @@ -959,7 +959,7 @@ const tests = function(web3) { const startingBlockNumber = await web3.eth.getBlockNumber(); const gasEstimate = await web3.eth.estimateGas(txData); - assert.strictEqual(gasEstimate, 27773); + assert.strictEqual(gasEstimate, 27795); const blockNumber = await web3.eth.getBlockNumber(); @@ -976,7 +976,7 @@ const tests = function(web3) { txData.from = "0x1234567890123456789012345678901234567890"; const result = await web3.eth.estimateGas(txData); - assert.strictEqual(result, 27773); + assert.strictEqual(result, 27795); }); it("should estimate gas when no account is listed (eth_estimateGas)", async function() { @@ -985,7 +985,7 @@ const tests = function(web3) { delete txData.from; const result = await web3.eth.estimateGas(txData); - assert.strictEqual(result, 27773); + assert.strictEqual(result, 27795); }); it("should send a state changing transaction (eth_sendTransaction)", async function() {