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

Mine n blocks, skip blocks #102

Closed
wants to merge 2 commits into from
Closed
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
50 changes: 48 additions & 2 deletions lib/blockchain_double.js
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,26 @@ BlockchainDouble.prototype.getEffectiveBlockNumber = function(number, callback)
}
};

BlockchainDouble.prototype.getRealBlockNumber = function(number, callback) {
if (typeof number != "string") {
number = to.hex(number);
}

// If we have a hex number
if (number.indexOf("0x") >= 0) {
return callback(null, to.number(number));
} else {
if (number == "latest" || number == "pending") {
return this.data.blocks.length(function(err, number) {
if (err) return callback(err);
callback(null, number - 1);
});
} else if (number == "earliest") {
return callback(null, 0);
}
}
};

// number accepts number (integer, hex), tag (e.g., "latest") or block hash
// This function is used by ethereumjs-vm
BlockchainDouble.prototype.getBlock = function(number, callback) {
Expand Down Expand Up @@ -245,6 +265,14 @@ BlockchainDouble.prototype.putBlock = function(block, logs, receipts, callback)
});
};

BlockchainDouble.prototype.skipBlocks = function(numBlocks, callback) {
this.data.blocks.last((err, block) => {
if (err) return callback(err);
const fakeBlock = this.createFakeBlock(numBlocks, block);
this.putBlock(fakeBlock, [], [], callback);
})
};

BlockchainDouble.prototype.popBlock = function(callback) {
var self = this;

Expand Down Expand Up @@ -350,6 +378,24 @@ BlockchainDouble.prototype.createBlock = function(parent, callback) {
});
};

BlockchainDouble.prototype.createFakeBlock = function(numBlocks, parent) {
var block = new Block();

var parentNumber = parent != null ? to.number(parent.header.number) : -1;

block.header.gasLimit = this.blockGasLimit;

// Ensure we have the right block number for the VM.
block.header.number = to.hex(parentNumber + numBlocks);

// Set the timestamp before processing txs
block.header.timestamp = to.hex(this.currentTime());

block.header.parentHash = to.hex(parent.hash());

return block;
};

BlockchainDouble.prototype.getQueuedNonce = function(address, callback) {
var nonce = null;

Expand Down Expand Up @@ -1026,9 +1072,9 @@ BlockchainDouble.prototype.getBlockLogs = function(number, callback) {
};

BlockchainDouble.prototype.getHeight = function(callback) {
this.data.blocks.length(function(err, length) {
this.data.blocks.last(function(err, block) {
if (err) return callback(err);
callback(null, length - 1);
callback(null, to.number(block.header.number));
})
};

Expand Down
120 changes: 66 additions & 54 deletions lib/statemanager.js
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,10 @@ StateManager.prototype.processBlocks = function(total_blocks, callback) {
});
};

StateManager.prototype.skipBlocks = function(numBlocks, callback) {
this.blockchain.skipBlocks(numBlocks, callback);
};

StateManager.prototype.processCall = function (from, rawTx, blockNumber, callback) {
var self = this;

Expand Down Expand Up @@ -640,73 +644,81 @@ StateManager.prototype.getBlock = function(hash_or_number, callback) {
this.blockchain.getBlock(hash_or_number, callback);
};

StateManager.prototype.getLogs = function(filter, callback) {
var self = this;

StateManager.prototype.getLogsCallback = function(filter, err, results, callback) {
var expectedAddress = filter.address;
var expectedTopics = filter.topics || [];

async.parallel({
fromBlock: this.blockchain.getEffectiveBlockNumber.bind(this.blockchain, filter.fromBlock || "latest"),
toBlock: this.blockchain.getEffectiveBlockNumber.bind(this.blockchain, filter.toBlock || "latest"),
latestBlock: this.blockchain.getEffectiveBlockNumber.bind(this.blockchain, "latest")
}, function(err, results) {
var fromBlock = results.fromBlock;
var toBlock = results.toBlock;
var latestBlock = results.latestBlock;

if (toBlock > latestBlock) {
toBlock = latestBlock;
}
var self = this;
var fromBlock = results.fromBlock;
var toBlock = results.toBlock;
var latestBlock = results.latestBlock;

var logs = [];
var current = fromBlock;

async.whilst(function() {
return current <= toBlock;
}, function(finished) {
self.blockchain.getBlockLogs(current, function(err, blockLogs) {
if (err) return finished(err);

// Filter logs that match the address
var filtered = blockLogs.filter(function(log) {
return (expectedAddress == null || log.address == expectedAddress);
});

// Now filter based on topics.
filtered = filtered.filter(function(log) {
var keep = true;
for (var i = 0; i < expectedTopics.length; i++) {
var expectedTopic = expectedTopics[i];
var logTopic = log.topics[i];
if (expectedTopic == null) continue;
var isMatch = Array.isArray(expectedTopic)
? expectedTopic.includes(logTopic)
: expectedTopic === logTopic;
if (i >= log.topics.length || !isMatch) {
keep = false;
break;
}
}
return keep;
});
if (toBlock > latestBlock) {
toBlock = latestBlock;
}

logs.push.apply(logs, filtered);
var logs = [];
var current = fromBlock;

current += 1;
finished();
async.whilst(function() {
return current <= toBlock;
}, function(finished) {
self.blockchain.getBlockLogs(current, function(err, blockLogs) {
if (err) return finished(err);

// Filter logs that match the address
var filtered = blockLogs.filter(function(log) {
return (expectedAddress == null || log.address == expectedAddress);
});
}, function(err) {
if (err) return callback(err);

logs = logs.map(function(log) {
return log.toJSON();
// Now filter based on topics.
filtered = filtered.filter(function(log) {
var keep = true;
for (var i = 0; i < expectedTopics.length; i++) {
var expectedTopic = expectedTopics[i];
var logTopic = log.topics[i];
if (expectedTopic == null) continue;
var isMatch = Array.isArray(expectedTopic)
? expectedTopic.includes(logTopic)
: expectedTopic === logTopic;
if (i >= log.topics.length || !isMatch) {
keep = false;
break;
}
}
return keep;
});

callback(err, logs);
logs.push.apply(logs, filtered);

current += 1;
finished();
});
}, function(err) {
if (err) return callback(err);

logs = logs.map(function(log) {
return log.toJSON();
});

callback(err, logs);
});
}

StateManager.prototype.getLogsWithFakeBlocks = function(filter, callback) {
async.parallel({
fromBlock: this.blockchain.getRealBlockNumber.bind(this.blockchain, filter.fromBlock || "latest"),
toBlock: this.blockchain.getRealBlockNumber.bind(this.blockchain, filter.toBlock || "latest"),
latestBlock: this.blockchain.getRealBlockNumber.bind(this.blockchain, "latest")
}, (err, results) => this.getLogsCallback(filter, err, results, callback));
};

StateManager.prototype.getLogs = function(filter, callback) {
async.parallel({
fromBlock: this.blockchain.getEffectiveBlockNumber.bind(this.blockchain, filter.fromBlock || "latest"),
toBlock: this.blockchain.getEffectiveBlockNumber.bind(this.blockchain, filter.toBlock || "latest"),
latestBlock: this.blockchain.getEffectiveBlockNumber.bind(this.blockchain, "latest")
}, (err, results) => this.getLogsCallback(filter, err, results, callback));
};

// Note: Snapshots have 1-based ids.
Expand Down
21 changes: 21 additions & 0 deletions lib/subproviders/geth_api_double.js
Original file line number Diff line number Diff line change
Expand Up @@ -473,12 +473,33 @@ GethApiDouble.prototype.evm_increaseTime = function(seconds, callback) {
callback(null, this.state.blockchain.increaseTime(seconds));
};

GethApiDouble.prototype.evm_skipBlocks = function(numBlocks, callback) {
this.state.skipBlocks(numBlocks, function(err) {
callback(err, '0x0');
});
};

GethApiDouble.prototype.evm_mineBlocks = function(numBlocks, callback) {
let n = numBlocks;
if (typeof n == "function" && callback == undefined) {
callback = n;
n = 1;
}
this.state.processBlocks(n, function(err) {
callback(err, '0x0');
});
};

GethApiDouble.prototype.evm_mine = function(callback) {
this.state.processBlocks(1, function(err) {
callback(err, '0x0');
});
};

GethApiDouble.prototype.evm_getLogsWithFakeBlocks = function(filter, callback) {
this.state.getLogsWithFakeBlocks(filter, callback);
};

GethApiDouble.prototype.debug_traceTransaction = function(tx_hash, params, callback) {
if (typeof params == "function") {
callback = params;
Expand Down
75 changes: 75 additions & 0 deletions test/mining.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,63 @@ describe("Mining", function() {
});
}

function mineMultipleBlocks(numBlocks) {
return new Promise(function(accept, reject) {
web3.currentProvider.send({
jsonrpc: "2.0",
method: "evm_mineBlocks",
params: [numBlocks],
id: new Date().getTime()
}, function(err, result) {
if (err) return reject(err);
assert.deepEqual(result.result, "0x0");
accept(result);
})
});
}

function getLogsWithFakeBlocks() {
return new Promise(function(accept, reject) {
web3.currentProvider.send({
jsonrpc: "2.0",
method: "evm_getLogsWithFakeBlocks",
params: [{}],
id: new Date().getTime()
}, function(err, result) {
if (err) return reject(err);
accept(result);
})
});
}

function getLogs() {
return new Promise(function(accept, reject) {
web3.currentProvider.send({
jsonrpc: "2.0",
method: "eth_getLogs",
params: [{}],
id: new Date().getTime()
}, function(err, result) {
if (err) return reject(err);
accept(result);
})
});
}

function skipBlocks(numBlocks) {
return new Promise(function(accept, reject) {
web3.currentProvider.send({
jsonrpc: "2.0",
method: "evm_skipBlocks",
params: [numBlocks],
id: new Date().getTime()
}, function(err, result) {
if (err) return reject(err);
accept(result);
})
});
}

function queueTransaction(from, to, gasLimit, value, data) {
return new Promise(function(accept, reject) {
web3.eth.sendTransaction({
Expand Down Expand Up @@ -432,4 +489,22 @@ describe("Mining", function() {
assert(is_mining);
});
});

it("should mine multiple blocks", function() {
const numBlocks = 10;
return mineMultipleBlocks(numBlocks).then(function() {
return getBlockNumber();
}).then(function(number) {
assert.equal(number, numBlocks);
})
});

it("should skip multiple blocks", function() {
const numBlocks = 1000;
return skipBlocks(numBlocks).then(function() {
return getBlockNumber();
}).then(function(number) {
assert.equal(number, numBlocks);
});
});
});