Regtest for reorg.
This commit is contained in:
parent
0f9e13c7d4
commit
423eb31992
564
regtest/reorg.js
564
regtest/reorg.js
@ -1,37 +1,68 @@
|
||||
'use strict';
|
||||
|
||||
var expect = require('chai').expect;
|
||||
var net = require('net');
|
||||
var spawn = require('child_process').spawn;
|
||||
var rimraf = require('rimraf');
|
||||
var mkdirp = require('mkdirp');
|
||||
var fs = require('fs');
|
||||
var p2p = require('bitcore-p2p');
|
||||
var bitcore = require('bitcore-lib');
|
||||
var Networks = bitcore.Networks;
|
||||
var BlockHeader = bitcore.BlockHeader;
|
||||
var Block = bitcore.Block;
|
||||
var BcoinBlock = require('bcoin').block;
|
||||
var async = require('async');
|
||||
var RPC = require('bitcoind-rpc');
|
||||
var http = require('http');
|
||||
var bitcore = require('bitcore-lib');
|
||||
|
||||
Networks.enableRegtest();
|
||||
var messages = new p2p.Messages({ network: Networks.get('regtest'), Block: BcoinBlock, BlockHeader: BlockHeader });
|
||||
var server;
|
||||
var rawBlocks = require('./data/blocks.json');
|
||||
var rawReorgBlocks = require('./data/blocks_reorg.json')[0];
|
||||
/*
|
||||
What this test does:
|
||||
|
||||
var reorgBlock = BcoinBlock.fromRaw(rawReorgBlocks, 'hex');
|
||||
1. start 2 bitcoind in regtest mode
|
||||
2. generate 10 blocks on the 1st bitcoind
|
||||
3. ensure that the 2nd bitcoind syncs those blocks
|
||||
4. start up bitcore and let it sync the 10 blocks
|
||||
5. shut down the first bitcoind
|
||||
6. start up the second bitcoind
|
||||
7. generate 100 blocks on the second bitcoind
|
||||
8. let bitcore sync the additional 100 blocks, height should be 110
|
||||
9. shutdown bitcore
|
||||
10. start up the first bitcoind again
|
||||
11. generate 1 block
|
||||
12. start up bitcore again, bitcore should reorg, removing 100 blocks and adding the one new block
|
||||
|
||||
var blocks = rawBlocks.map(function(rawBlock) {
|
||||
return new Block(new Buffer(rawBlock, 'hex'));
|
||||
});
|
||||
*/
|
||||
|
||||
var headers = blocks.map(function(block) {
|
||||
return block.header;
|
||||
});
|
||||
var blocksGenerated = 0;
|
||||
|
||||
var rpcConfig = {
|
||||
protocol: 'http',
|
||||
user: 'local',
|
||||
pass: 'localtest',
|
||||
host: '127.0.0.1',
|
||||
port: 58332,
|
||||
rejectUnauthorized: false
|
||||
};
|
||||
|
||||
var rpc1 = new RPC(rpcConfig);
|
||||
rpcConfig.port++;
|
||||
var rpc2 = new RPC(rpcConfig);
|
||||
var debug = true;
|
||||
var bitcoreDataDir = '/tmp/bitcore';
|
||||
var bitcoinDir1 = '/tmp/bitcoin1';
|
||||
var bitcoinDir2 = '/tmp/bitcoin2';
|
||||
var bitcoinDataDirs = [ bitcoinDir1, bitcoinDir2 ];
|
||||
|
||||
var bitcoin = {
|
||||
args: {
|
||||
datadir: null,
|
||||
listen: 1,
|
||||
regtest: 1,
|
||||
server: 1,
|
||||
rpcuser: 'local',
|
||||
rpcpassword: 'localtest',
|
||||
//printtoconsole: 1,
|
||||
rpcport: 58332,
|
||||
},
|
||||
datadir: null,
|
||||
exec: 'bitcoind', //if this isn't on your PATH, then provide the absolute path, e.g. /usr/local/bin/bitcoind
|
||||
processes: []
|
||||
};
|
||||
|
||||
var bitcore = {
|
||||
configFile: {
|
||||
@ -72,71 +103,187 @@ var bitcore = {
|
||||
},
|
||||
opts: { cwd: bitcoreDataDir },
|
||||
datadir: bitcoreDataDir,
|
||||
exec: 'bitcored', // ensure this on your path or add the full, absolute path.
|
||||
exec: 'bitcored', //if this isn't on your PATH, then provide the absolute path, e.g. /usr/local/bin/bitcored
|
||||
args: ['start'],
|
||||
process: null
|
||||
};
|
||||
|
||||
var request = function(httpOpts, callback) {
|
||||
|
||||
var blockIndex = 0;
|
||||
var tcpSocket;
|
||||
var request = http.request(httpOpts, function(res) {
|
||||
|
||||
var startFakeNode = function() {
|
||||
server = net.createServer(function(socket) {
|
||||
if (res.statusCode !== 200 && res.statusCode !== 201) {
|
||||
return callback('Error from bitcore-node webserver: ' + res.statusCode);
|
||||
}
|
||||
|
||||
tcpSocket = socket;
|
||||
socket.on('end', function() {
|
||||
console.log('bitcore-node has ended the connection');
|
||||
var resError;
|
||||
var resData = '';
|
||||
|
||||
res.on('error', function(e) {
|
||||
resError = e;
|
||||
});
|
||||
|
||||
socket.on('data', function(data) {
|
||||
res.on('data', function(data) {
|
||||
resData += data;
|
||||
});
|
||||
|
||||
var command = data.slice(4, 16).toString('hex');
|
||||
var message;
|
||||
res.on('end', function() {
|
||||
|
||||
if (command === '76657273696f6e0000000000') { //version
|
||||
message = messages.Version();
|
||||
}
|
||||
|
||||
if (command === '76657261636b000000000000') { //verack
|
||||
message = messages.VerAck();
|
||||
}
|
||||
|
||||
if (command === '676574686561646572730000') { //getheaders
|
||||
message = messages.Headers(headers);
|
||||
}
|
||||
|
||||
if (command === '676574626c6f636b73000000') { //getblocks
|
||||
var block = blocks[blockIndex];
|
||||
if (!block) {
|
||||
return;
|
||||
}
|
||||
var blockHash = block.hash;
|
||||
var inv = p2p.Inventory.forBlock(blockHash);
|
||||
message = messages.Inventory([inv]);
|
||||
}
|
||||
|
||||
if (command === '676574646174610000000000') { //getdata
|
||||
var raw = rawBlocks[blockIndex++];
|
||||
var blk = BcoinBlock.fromRaw(raw, 'hex');
|
||||
message = messages.Block(blk, { Block: BcoinBlock });
|
||||
}
|
||||
|
||||
if (message) {
|
||||
socket.write(message.toBuffer());
|
||||
if (resError) {
|
||||
return callback(resError);
|
||||
}
|
||||
var data = JSON.parse(resData);
|
||||
callback(null, data);
|
||||
|
||||
});
|
||||
|
||||
socket.pipe(socket);
|
||||
});
|
||||
|
||||
server.listen(18444, '127.0.0.1');
|
||||
request.on('error', function(err) {
|
||||
callback(err);
|
||||
});
|
||||
|
||||
if (httpOpts.body) {
|
||||
request.write(httpOpts.body);
|
||||
} else {
|
||||
request.write('');
|
||||
}
|
||||
request.end();
|
||||
};
|
||||
|
||||
var waitForBlocksGenerated = function(callback) {
|
||||
|
||||
var httpOpts = {
|
||||
hostname: 'localhost',
|
||||
port: 53001,
|
||||
path: '/api/status',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
async.retry({ interval: 1000, times: 100 }, function(next) {
|
||||
|
||||
request(httpOpts, function(err, data) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
if (data.info.blocks !== blocksGenerated) {
|
||||
return next(data);
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
}, callback);
|
||||
};
|
||||
|
||||
var resetDirs = function(dirs, callback) {
|
||||
|
||||
async.each(dirs, function(dir, next) {
|
||||
|
||||
rimraf(dir, function(err) {
|
||||
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
mkdirp(dir, next);
|
||||
|
||||
});
|
||||
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
var startBitcoind = function(callback) {
|
||||
|
||||
var args = bitcoin.args;
|
||||
var argList = Object.keys(args).map(function(key) {
|
||||
return '-' + key + '=' + args[key];
|
||||
});
|
||||
|
||||
var bitcoinProcess = spawn(bitcoin.exec, argList, bitcoin.opts);
|
||||
bitcoin.processes.push(bitcoinProcess);
|
||||
|
||||
bitcoinProcess.stdout.on('data', function(data) {
|
||||
|
||||
if (debug) {
|
||||
process.stdout.write(data.toString());
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
bitcoinProcess.stderr.on('data', function(data) {
|
||||
|
||||
if (debug) {
|
||||
process.stderr.write(data.toString());
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
callback();
|
||||
};
|
||||
|
||||
|
||||
var shutdownFakeNode = function() {
|
||||
server.close();
|
||||
var reportBitcoindsStarted = function() {
|
||||
var pids = bitcoin.processes.map(function(process) {
|
||||
return process.pid;
|
||||
});
|
||||
|
||||
console.log(pids.length + ' bitcoind\'s started at pid(s): ' + pids);
|
||||
};
|
||||
|
||||
var startBitcoinds = function(datadirs, callback) {
|
||||
|
||||
var listenCount = 0;
|
||||
async.eachSeries(datadirs, function(datadir, next) {
|
||||
|
||||
bitcoin.datadir = datadir;
|
||||
bitcoin.args.datadir = datadir;
|
||||
|
||||
if (listenCount++ > 0) {
|
||||
bitcoin.args.listen = 0;
|
||||
bitcoin.args.rpcport = bitcoin.args.rpcport + 1;
|
||||
bitcoin.args.connect = '127.0.0.1';
|
||||
}
|
||||
|
||||
startBitcoind(next);
|
||||
|
||||
}, function(err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
reportBitcoindsStarted();
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
var waitForBitcoinReady = function(rpc, callback) {
|
||||
async.retry({ interval: 1000, times: 1000 }, function(next) {
|
||||
rpc.getInfo(function(err) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
next();
|
||||
});
|
||||
}, function(err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
setTimeout(callback, 2000);
|
||||
});
|
||||
};
|
||||
|
||||
var shutdownBitcoind = function(callback) {
|
||||
var process;
|
||||
do {
|
||||
process = bitcoin.processes.shift();
|
||||
if (process) {
|
||||
process.kill();
|
||||
}
|
||||
} while(process);
|
||||
setTimeout(callback, 3000);
|
||||
};
|
||||
|
||||
var shutdownBitcore = function(callback) {
|
||||
@ -146,132 +293,205 @@ var shutdownBitcore = function(callback) {
|
||||
callback();
|
||||
};
|
||||
|
||||
var writeBitcoreConf = function() {
|
||||
fs.writeFileSync(bitcore.configFile.file, JSON.stringify(bitcore.configFile.conf));
|
||||
};
|
||||
|
||||
var startBitcore = function(callback) {
|
||||
|
||||
rimraf(bitcoreDataDir, function(err) {
|
||||
var args = bitcore.args;
|
||||
bitcore.process = spawn(bitcore.exec, args, bitcore.opts);
|
||||
|
||||
if(err) {
|
||||
return callback(err);
|
||||
bitcore.process.stdout.on('data', function(data) {
|
||||
|
||||
if (debug) {
|
||||
process.stdout.write(data.toString());
|
||||
}
|
||||
|
||||
mkdirp(bitcoreDataDir, function(err) {
|
||||
});
|
||||
bitcore.process.stderr.on('data', function(data) {
|
||||
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
fs.writeFileSync(bitcore.configFile.file, JSON.stringify(bitcore.configFile.conf));
|
||||
|
||||
var args = bitcore.args;
|
||||
bitcore.process = spawn(bitcore.exec, args, bitcore.opts);
|
||||
|
||||
bitcore.process.stdout.on('data', function(data) {
|
||||
|
||||
if (debug) {
|
||||
process.stdout.write(data.toString());
|
||||
}
|
||||
|
||||
});
|
||||
bitcore.process.stderr.on('data', function(data) {
|
||||
|
||||
if (debug) {
|
||||
process.stderr.write(data.toString());
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
callback();
|
||||
});
|
||||
if (debug) {
|
||||
process.stderr.write(data.toString());
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
waitForBlocksGenerated(callback);
|
||||
|
||||
|
||||
};
|
||||
|
||||
var performTest = function(callback) {
|
||||
async.series([
|
||||
|
||||
// 0. reset the test directories
|
||||
function(next) {
|
||||
console.log('step 0: setting up directories.');
|
||||
var dirs = bitcoinDataDirs.concat([bitcoreDataDir]);
|
||||
resetDirs(dirs, function(err) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
writeBitcoreConf();
|
||||
console.log('done');
|
||||
next();
|
||||
});
|
||||
},
|
||||
// 1. start 2 bitcoinds in regtest mode
|
||||
function(next) {
|
||||
console.log('step 1: starting 2 bitcoinds.');
|
||||
startBitcoinds(bitcoinDataDirs, function(err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
waitForBitcoinReady(rpc1, next);
|
||||
});
|
||||
},
|
||||
// 2. ensure that both bitcoind's are connected
|
||||
function(next) {
|
||||
console.log('done');
|
||||
console.log('step 2: checking to see if bitcoind\'s are connected to each other.');
|
||||
rpc1.getInfo(function(err, res) {
|
||||
if (err || res.result.connections !== 1) {
|
||||
next(err || new Error('bitcoind\'s not connected to each other.'));
|
||||
}
|
||||
console.log('bitcoind\'s are connected.');
|
||||
next();
|
||||
});
|
||||
},
|
||||
// 3. generate 10 blocks on the 1st bitcoind
|
||||
function(next) {
|
||||
blocksGenerated += 10;
|
||||
console.log('step 3: generating 10 blocks on bitcoin 1.');
|
||||
rpc1.generate(10, next);
|
||||
},
|
||||
// 4. ensure that the 2nd bitcoind syncs those blocks
|
||||
function(next) {
|
||||
console.log('done');
|
||||
console.log('step 4: checking for synced blocks.');
|
||||
async.retry(function(next) {
|
||||
rpc2.getInfo(function(err, res) {
|
||||
if (err || res.result.blocks < 10) {
|
||||
return next(1);
|
||||
}
|
||||
console.log('bitcoin 2 has synced the blocks generated on bitcoin 1.');
|
||||
next();
|
||||
});
|
||||
}, next);
|
||||
},
|
||||
// 5. start up bitcore and let it sync the 10 blocks
|
||||
function(next) {
|
||||
console.log('step 5: starting bitcore...');
|
||||
startBitcore(next);
|
||||
},
|
||||
function(next) {
|
||||
// 6. shut down both bitcoind's
|
||||
console.log('bitcore is running and sync\'ed.');
|
||||
console.log('step 6: shutting down all bitcoind\'s.');
|
||||
shutdownBitcoind(next);
|
||||
},
|
||||
// 7. change the config for the second bitcoind to listen for p2p, start bitcoin 2
|
||||
function(next) {
|
||||
console.log('step 7: changing config of bitcoin 2 and restarting it.');
|
||||
bitcoin.datadir = bitcoinDataDirs[1];
|
||||
bitcoin.args.datadir = bitcoinDataDirs[1];
|
||||
bitcoin.args.listen = 1;
|
||||
startBitcoind(function(err) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
reportBitcoindsStarted();
|
||||
waitForBitcoinReady(rpc2, next);
|
||||
});
|
||||
},
|
||||
// 8. generate 100 blocks on the second bitcoind
|
||||
function(next) {
|
||||
console.log('step 8: generating 100 blocks on bitcoin 2.');
|
||||
blocksGenerated += 100;
|
||||
console.log('generating 100 blocks on bitcoin 2.');
|
||||
rpc2.generate(100, next);
|
||||
},
|
||||
// 9. let bitcore connect and sync those 100 blocks
|
||||
function(next) {
|
||||
console.log('step 9: syncing 100 blocks to bitcore.');
|
||||
waitForBlocksGenerated(next);
|
||||
},
|
||||
// 10. shutdown the second bitcoind
|
||||
function(next) {
|
||||
console.log('100 more blocks synced to bitcore.');
|
||||
console.log('step 10: shutting down bitcoin 2.');
|
||||
shutdownBitcoind(next);
|
||||
},
|
||||
// 11. start up the first bitcoind
|
||||
function(next) {
|
||||
console.log('bitcoin 2 shut down.');
|
||||
console.log('step 11: starting up bitcoin 1');
|
||||
bitcoin.args.rpcport = bitcoin.args.rpcport - 1;
|
||||
bitcoin.datadir = bitcoinDataDirs[0];
|
||||
bitcoin.args.datadir = bitcoinDataDirs[0];
|
||||
startBitcoind(function(err) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
reportBitcoindsStarted();
|
||||
waitForBitcoinReady(rpc1, next);
|
||||
});
|
||||
},
|
||||
// 12. generate one block
|
||||
function(next) {
|
||||
console.log('step 12: generating one block');
|
||||
// resetting height to 11
|
||||
blocksGenerated = 11;
|
||||
rpc1.generate(1, next);
|
||||
},
|
||||
// 13. let bitcore sync that block and reorg back to it
|
||||
function(next) {
|
||||
console.log('step 13: Waiting for bitcore to reorg to block height 11.');
|
||||
waitForBlocksGenerated(next);
|
||||
}
|
||||
], function(err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
callback();
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
describe('Reorg', function() {
|
||||
// 1. spin up bitcore-node and have it connect to our custom tcp socket
|
||||
// 2. feed it a few headers
|
||||
// 3. feed it a few blocks
|
||||
// 4. feed it a block that reorgs
|
||||
|
||||
this.timeout(60000);
|
||||
|
||||
before(function(done) {
|
||||
startFakeNode();
|
||||
startBitcore(done);
|
||||
after(function(done) {
|
||||
shutdownBitcoind(done);
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
shutdownFakeNode();
|
||||
shutdownBitcore(function() {
|
||||
setTimeout(done, 3000);
|
||||
it('should reorg correctly when starting and a reorg happen whilst shutdown', function(done) {
|
||||
|
||||
performTest(function(err) {
|
||||
return done();
|
||||
var httpOpts = {
|
||||
hostname: 'localhost',
|
||||
port: 53001,
|
||||
path: 'http://localhost:53001/api/block/' + reorgBlock,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
request(httpOpts, function(err, data) {
|
||||
|
||||
if(err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
console.log(data);
|
||||
done();
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
it('should reorg correctly when already synced', function(done) {
|
||||
|
||||
// at this point we have a fully synced chain at height 7....
|
||||
// we now want to send a new block number 7 whose prev hash is block 6 (it should be block 7)
|
||||
// we then should reorg back to block 6 then back up to the new block 7
|
||||
|
||||
setTimeout(function() {
|
||||
|
||||
console.log('From Test: reorging to block: ' + reorgBlock.rhash());
|
||||
|
||||
// send the reorg block
|
||||
rawBlocks.push(rawReorgBlocks);
|
||||
var blockHash = reorgBlock.rhash();
|
||||
var inv = p2p.Inventory.forBlock(blockHash);
|
||||
var msg = messages.Inventory([inv]);
|
||||
tcpSocket.write(msg.toBuffer());
|
||||
|
||||
// wait 5 secs until the reorg happens, if it takes any longer the test ought to fail anyway
|
||||
setTimeout(function() {
|
||||
var error;
|
||||
var request = http.request('http://localhost:53001/api/block/' + reorgBlock.rhash(), function(res) {
|
||||
|
||||
if (res.statusCode !== 200 && res.statusCode !== 201) {
|
||||
if (error) {
|
||||
return;
|
||||
}
|
||||
return done('Error from bitcore-node webserver: ' + res.statusCode);
|
||||
}
|
||||
|
||||
var resError;
|
||||
var resData = '';
|
||||
|
||||
res.on('error', function(e) {
|
||||
resError = e;
|
||||
});
|
||||
|
||||
res.on('data', function(data) {
|
||||
resData += data;
|
||||
});
|
||||
|
||||
res.on('end', function() {
|
||||
if (error) {
|
||||
return;
|
||||
}
|
||||
var data = JSON.parse(resData);
|
||||
expect(data.height).to.equal(7);
|
||||
expect(data.hash).to.equal(reorgBlock.rhash());
|
||||
done(resError, resData);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
request.on('error', function(e) {
|
||||
error = e;
|
||||
done(error);
|
||||
});
|
||||
|
||||
request.write('');
|
||||
request.end();
|
||||
}, 5000);
|
||||
}, 2000);
|
||||
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user