Regtest for reorg.

This commit is contained in:
Chris Kleeschulte 2017-09-19 08:50:12 -04:00
parent 0f9e13c7d4
commit 423eb31992
No known key found for this signature in database
GPG Key ID: 33195D27EF6BDB7F

View File

@ -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);
});
});