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

Commit

Permalink
Fix: forking value and code deletion/destruction (#482)
Browse files Browse the repository at this point in the history
* WIP

* Fix: forked deletion now works (at least at head of tree)

* Make sure we handle deleted contracts, too

* Remove log and fix typo
  • Loading branch information
davidmurdoch authored and nicholasjpaterno committed Sep 13, 2019
1 parent 9b5a54b commit 395cc10
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 40 deletions.
28 changes: 18 additions & 10 deletions lib/utils/forkedblockchain.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
}
});
Expand Down
78 changes: 53 additions & 25 deletions lib/utils/forkedstoragetrie.js
Original file line number Diff line number Diff line change
@@ -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");
Expand All @@ -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;
Expand Down Expand Up @@ -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);
};
Expand Down
4 changes: 2 additions & 2 deletions test/block-tags.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"
);
});
Expand Down
4 changes: 4 additions & 0 deletions test/contracts/examples/Example.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,8 @@ contract Example {
value = val;
emit ValueSet(val);
}

function destruct() public {
selfdestruct(msg.sender);
}
}
23 changes: 23 additions & 0 deletions test/forking.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
6 changes: 3 additions & 3 deletions test/requests.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -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() {
Expand All @@ -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() {
Expand Down

0 comments on commit 395cc10

Please sign in to comment.