From 423eb319920fd3d54c959bd739fb34ad3e47218d Mon Sep 17 00:00:00 2001 From: Chris Kleeschulte Date: Tue, 19 Sep 2017 08:50:12 -0400 Subject: [PATCH] Regtest for reorg. --- regtest/reorg.js | 564 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 392 insertions(+), 172 deletions(-) diff --git a/regtest/reorg.js b/regtest/reorg.js index 1740789..2204874 100644 --- a/regtest/reorg.js +++ b/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); - - - }); - }); -