'use strict'; // These tests require bitcore-node Bitcoin Core bindings to be compiled with // the environment variable BITCORENODE_ENV=test. This enables the use of regtest // functionality by including the wallet in the build. // To run the tests: $ mocha -R spec integration/regtest.js var index = require('..'); var log = index.log; if (process.env.BITCORENODE_ENV !== 'test') { log.info('Please set the environment variable BITCORENODE_ENV=test and make sure bindings are compiled for testing'); process.exit(); } var chai = require('chai'); var bitcore = require('bitcore-lib'); var BN = bitcore.crypto.BN; var async = require('async'); var rimraf = require('rimraf'); var bitcoind; /* jshint unused: false */ var should = chai.should(); var assert = chai.assert; var sinon = require('sinon'); var BitcoinRPC = require('bitcoind-rpc'); var transactionData = []; var blockHashes = []; var utxos; var client; var coinbasePrivateKey; var privateKey = bitcore.PrivateKey(); var destKey = bitcore.PrivateKey(); describe('Daemon Binding Functionality', function() { before(function(done) { this.timeout(30000); // Add the regtest network bitcore.Networks.remove(bitcore.Networks.testnet); bitcore.Networks.add({ name: 'regtest', alias: 'regtest', pubkeyhash: 0x6f, privatekey: 0xef, scripthash: 0xc4, xpubkey: 0x043587cf, xprivkey: 0x04358394, networkMagic: 0xfabfb5da, port: 18444, dnsSeeds: [ ] }); var datadir = __dirname + '/data'; rimraf(datadir + '/regtest', function(err) { if (err) { throw err; } bitcoind = require('../').services.Bitcoin({ node: { datadir: datadir, network: { name: 'regtest' } } }); bitcoind.on('error', function(err) { log.error('error="%s"', err.message); }); log.info('Waiting for Bitcoin Core to initialize...'); bitcoind.start(function() { log.info('Bitcoind started'); client = new BitcoinRPC({ protocol: 'https', host: '127.0.0.1', port: 18332, user: 'bitcoin', pass: 'local321', rejectUnauthorized: false }); log.info('Generating 100 blocks...'); // Generate enough blocks so that the initial coinbase transactions // can be spent. setImmediate(function() { client.generate(150, function(err, response) { if (err) { throw err; } blockHashes = response.result; log.info('Preparing test data...'); // Get all of the unspent outputs client.listUnspent(0, 150, function(err, response) { utxos = response.result; async.mapSeries(utxos, function(utxo, next) { async.series([ function(finished) { // Load all of the transactions for later testing client.getTransaction(utxo.txid, function(err, txresponse) { if (err) { throw err; } // add to the list of transactions for testing later transactionData.push(txresponse.result.hex); finished(); }); }, function(finished) { // Get the private key for each utxo client.dumpPrivKey(utxo.address, function(err, privresponse) { if (err) { throw err; } utxo.privateKeyWIF = privresponse.result; finished(); }); } ], next); }, function(err) { if (err) { throw err; } done(); }); }); }); }); }); }); }); after(function(done) { this.timeout(20000); bitcoind.stop(function(err, result) { done(); }); }); describe('get blocks by hash', function() { [0,1,2,3,5,6,7,8,9].forEach(function(i) { it('generated block ' + i, function(done) { bitcoind.getBlock(blockHashes[i], function(err, response) { if (err) { throw err; } should.exist(response); var block = bitcore.Block.fromBuffer(response); block.hash.should.equal(blockHashes[i]); done(); }); }); }); }); describe('get blocks by height', function() { [0,1,2,3,4,5,6,7,8,9].forEach(function(i) { it('generated block ' + i, function(done) { // add the genesis block var height = i + 1; bitcoind.getBlock(i + 1, function(err, response) { if (err) { throw err; } should.exist(response); var block = bitcore.Block.fromBuffer(response); block.hash.should.equal(blockHashes[i]); done(); }); }); }); it('will get error with number greater than tip', function(done) { bitcoind.getBlock(1000000000, function(err, response) { should.exist(err); done(); }); }); }); describe('get transactions by hash', function() { [0,1,2,3,4,5,6,7,8,9].forEach(function(i) { it('for tx ' + i, function(done) { var txhex = transactionData[i]; var tx = new bitcore.Transaction(); tx.fromString(txhex); bitcoind.getTransaction(tx.hash, true, function(err, response) { if (err) { throw err; } assert(response.toString('hex') === txhex, 'incorrect tx data result'); done(); }); }); }); it('will return null if the transaction does not exist', function(done) { var txid = '6226c407d0e9705bdd7158e60983e37d0f5d23529086d6672b07d9238d5aa618'; bitcoind.getTransaction(txid, true, function(err, response) { if (err) { throw err; } should.not.exist(response); done(); }); }); }); describe('get block index', function() { var expectedWork = new BN(6); [1,2,3,4,5,6,7,8,9].forEach(function(i) { it('generate block ' + i, function() { var blockIndex = bitcoind.getBlockIndex(blockHashes[i]); should.exist(blockIndex); should.exist(blockIndex.chainWork); var work = new BN(blockIndex.chainWork, 'hex'); work.cmp(expectedWork).should.equal(0); expectedWork = expectedWork.add(new BN(2)); should.exist(blockIndex.prevHash); blockIndex.hash.should.equal(blockHashes[i]); blockIndex.prevHash.should.equal(blockHashes[i - 1]); blockIndex.height.should.equal(i + 1); }); }); it('will get null prevHash for the genesis block', function() { var blockIndex = bitcoind.getBlockIndex(0); should.exist(blockIndex); should.equal(blockIndex.prevHash, null); }); }); describe('get block index by height', function() { var expectedWork = new BN(6); [2,3,4,5,6,7,8,9].forEach(function(i) { it('generate block ' + i, function() { var blockIndex = bitcoind.getBlockIndex(i); should.exist(blockIndex); should.exist(blockIndex.chainWork); var work = new BN(blockIndex.chainWork, 'hex'); work.cmp(expectedWork).should.equal(0); expectedWork = expectedWork.add(new BN(2)); should.exist(blockIndex.prevHash); blockIndex.hash.should.equal(blockHashes[i - 1]); blockIndex.prevHash.should.equal(blockHashes[i - 2]); blockIndex.height.should.equal(i); }); }); it('will get null with number greater than tip', function(done) { var index = bitcoind.getBlockIndex(100000); should.equal(index, null); done(); }); }); describe('isMainChain', function() { [1,2,3,4,5,6,7,8,9].forEach(function(i) { it('block ' + i + ' is on the main chain', function() { bitcoind.isMainChain(blockHashes[i]).should.equal(true); }); }); }); describe('send transaction functionality', function() { it('will not error and return the transaction hash', function() { // create and sign the transaction var tx = bitcore.Transaction(); tx.from(utxos[0]); tx.change(privateKey.toAddress()); tx.to(destKey.toAddress(), utxos[0].amount * 1e8 - 1000); tx.sign(bitcore.PrivateKey.fromWIF(utxos[0].privateKeyWIF)); // test sending the transaction var hash = bitcoind.sendTransaction(tx.serialize()); hash.should.equal(tx.hash); }); it('will throw an error if an unsigned transaction is sent', function() { var tx = bitcore.Transaction(); tx.from(utxos[1]); tx.change(privateKey.toAddress()); tx.to(destKey.toAddress(), utxos[1].amount * 1e8 - 1000); (function() { bitcoind.sendTransaction(tx.uncheckedSerialize()); }).should.throw('\x10: mandatory-script-verify-flag-failed (Operation not valid with the current stack size)'); }); it('will throw an error for unexpected types', function() { var garbage = new Buffer('abcdef', 'hex'); (function() { bitcoind.sendTransaction(garbage); }).should.throw('TX decode failed'); var num = 23; (function() { bitcoind.sendTransaction(num); }).should.throw('TX decode failed'); }); it('will emit "tx" events', function(done) { var tx = bitcore.Transaction(); tx.from(utxos[2]); tx.change(privateKey.toAddress()); tx.to(destKey.toAddress(), utxos[2].amount * 1e8 - 1000); tx.sign(bitcore.PrivateKey.fromWIF(utxos[2].privateKeyWIF)); var serialized = tx.serialize(); bitcoind.once('tx', function(result) { result.buffer.toString('hex').should.equal(serialized); result.hash.should.equal(tx.hash); result.mempool.should.equal(true); done(); }); bitcoind.sendTransaction(serialized); }); }); describe('fee estimation', function() { it('will estimate fees', function() { var fees = bitcoind.estimateFee(); fees.should.equal(-1); }); }); describe('tip updates', function() { it('will get an event when the tip is new', function(done) { this.timeout(4000); bitcoind.on('tip', function(height) { if (height == 151) { height.should.equal(151); done(); } }); client.generate(1, function(err, response) { if (err) { throw err; } }); }); }); describe('transactions leaving the mempool', function() { it('receive event when transaction leaves', function(done) { // add transaction to build a new block var tx = bitcore.Transaction(); tx.from(utxos[4]); tx.change(privateKey.toAddress()); tx.to(destKey.toAddress(), utxos[4].amount * 1e8 - 1000); tx.sign(bitcore.PrivateKey.fromWIF(utxos[4].privateKeyWIF)); bitcoind.sendTransaction(tx.serialize()); bitcoind.once('txleave', function(txInfo) { txInfo.hash.should.equal(tx.hash); done(); }); client.generate(1, function(err, response) { if (err) { throw err; } }); }); }); describe('mempool functionality', function() { var fromAddress = 'mszYqVnqKoQx4jcTdJXxwKAissE3Jbrrc1'; var utxo1 = { address: fromAddress, txId: 'a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458', outputIndex: 0, script: bitcore.Script.buildPublicKeyHashOut(fromAddress).toString(), satoshis: 100000 }; var toAddress = 'mrU9pEmAx26HcbKVrABvgL7AwA5fjNFoDc'; var changeAddress = 'mgBCJAsvzgT2qNNeXsoECg2uPKrUsZ76up'; var changeAddressP2SH = '2N7T3TAetJrSCruQ39aNrJvYLhG1LJosujf'; var privateKey1 = 'cSBnVM4xvxarwGQuAfQFwqDg9k5tErHUHzgWsEfD4zdwUasvqRVY'; var private1 = '6ce7e97e317d2af16c33db0b9270ec047a91bff3eff8558afb5014afb2bb5976'; var private2 = 'c9b26b0f771a0d2dad88a44de90f05f416b3b385ff1d989343005546a0032890'; var tx = new bitcore.Transaction(); tx.from(utxo1); tx.to(toAddress, 50000); tx.change(changeAddress); tx.sign(privateKey1); var tx2; var tx2Key; before(function() { tx2 = bitcore.Transaction(); tx2.from(utxos[3]); tx2.change(privateKey.toAddress()); tx2.to(destKey.toAddress(), utxos[3].amount * 1e8 - 1000); tx2Key = bitcore.PrivateKey.fromWIF(utxos[3].privateKeyWIF); tx2.sign(tx2Key); }); it('will add an unchecked transaction', function() { var added = bitcoind.addMempoolUncheckedTransaction(tx.serialize()); added.should.equal(true); bitcoind.getTransaction(tx.hash, true, function(err, txBuffer) { if(err) { throw err; } var expected = tx.toBuffer().toString('hex'); txBuffer.toString('hex').should.equal(expected); }); }); it('get one transaction', function() { var transactions = bitcoind.getMempoolTransactions(); transactions[0].toString('hex').should.equal(tx.serialize()); }); it('get multiple transactions', function() { bitcoind.sendTransaction(tx2.serialize()); var transactions = bitcoind.getMempoolTransactions(); var expected = [tx.serialize(), tx2.serialize()]; expected.should.contain(transactions[0].toString('hex')); expected.should.contain(transactions[1].toString('hex')); }); }); describe('get transaction with block info', function() { it('should include tx buffer, height and timestamp', function(done) { bitcoind.getTransactionWithBlockInfo(utxos[0].txid, true, function(err, data) { should.not.exist(err); should.exist(data.height); data.height.should.be.a('number'); should.exist(data.timestamp); should.exist(data.buffer); done(); }); }); }); describe('get next block hash', function() { it('will get next block hash', function() { var nextBlockHash = bitcoind.getNextBlockHash(blockHashes[0]); nextBlockHash.should.equal(blockHashes[1]); var nextnextBlockHash = bitcoind.getNextBlockHash(nextBlockHash); nextnextBlockHash.should.equal(blockHashes[2]); }); it('will get a null response if the tip hash is provided', function() { var bestBlockHash = bitcoind.getBestBlockHash(); var nextBlockHash = bitcoind.getNextBlockHash(bestBlockHash); should.not.exist(nextBlockHash); }); }); describe('#getInfo', function() { it('will get information', function() { var info = bitcoind.getInfo(); should.exist(info); should.exist(info.version); should.exist(info.blocks); should.exist(info.timeoffset); should.exist(info.connections); should.exist(info.difficulty); should.exist(info.testnet); should.exist(info.relayfee); should.exist(info.errors); }); }); });