From 6634b4feae6300ed72fbf98acb64e13b3ca1180b Mon Sep 17 00:00:00 2001 From: Chris Kleeschulte Date: Wed, 26 Apr 2017 16:59:44 -0400 Subject: [PATCH] wip on regtest --- lib/services/address/index.js | 6 +- lib/services/db/sync.js | 2 +- lib/services/wallet-api/encoding.js | 18 +- lib/services/wallet-api/index.js | 79 +++- regtest/v4/bitcoind.js | 467 ++++++++++++++++++++++ regtest/wallet.js | 119 +++++- test/services/wallet-api/encoding.unit.js | 24 +- 7 files changed, 656 insertions(+), 59 deletions(-) create mode 100644 regtest/v4/bitcoind.js diff --git a/lib/services/address/index.js b/lib/services/address/index.js index 7a85d366..4865f3fe 100644 --- a/lib/services/address/index.js +++ b/lib/services/address/index.js @@ -160,7 +160,6 @@ AddressService.prototype.concurrentBlockHandler = function(block, connectBlock, } var address = self.getAddressString(script); - if(!address) { continue; } @@ -1172,7 +1171,7 @@ AddressService.prototype.getAddressTxidsWithHeights = function(address, options, var txids = {}; var start = self._encoding.encodeAddressIndexKey(address, opts.start || 0); //the start and end must be the same length - var end = Buffer.concat([ start.slice(0, -36), new Buffer((opts.end || 0xffffffff), 'hex') ]); + var end = Buffer.concat([ start.slice(0, -36), new Buffer((opts.end || 'ffffffff'), 'hex') ]); var stream = self.store.createKeyStream({ gte: start, @@ -1181,9 +1180,6 @@ AddressService.prototype.getAddressTxidsWithHeights = function(address, options, var streamErr = null; - stream.on('close', function(buffer) { - }); - stream.on('data', function(buffer) { var key = self._encoding.decodeAddressIndexKey(buffer); txids[key.txid] = key.height; diff --git a/lib/services/db/sync.js b/lib/services/db/sync.js index e45df76f..073fee51 100644 --- a/lib/services/db/sync.js +++ b/lib/services/db/sync.js @@ -316,7 +316,7 @@ ProcessConcurrent.prototype._transform = function(block, enc, callback) { self.blockCount++; self.operations = self.operations.concat(operations); - if(self.blockCount >= 1) { //self.operations.length >= 100) { + if(self.blockCount >= 1) { self.operations.push(self.db.getConcurrentTipOperation(block, true)); var obj = { concurrentTip: block, diff --git a/lib/services/wallet-api/encoding.js b/lib/services/wallet-api/encoding.js index 58cc6a21..52b62919 100644 --- a/lib/services/wallet-api/encoding.js +++ b/lib/services/wallet-api/encoding.js @@ -29,7 +29,7 @@ function Encoding(servicePrefix) { }; } -Encoding.prototype.encodeWalletTransactionKey = function(walletId, height) { +Encoding.prototype.encodeWalletTransactionKey = function(walletId, height, txid) { var buffers = [this.servicePrefix, this.subKeyMap.transaction.buffer]; var walletIdSizeBuffer = new Buffer(1); @@ -43,6 +43,9 @@ Encoding.prototype.encodeWalletTransactionKey = function(walletId, height) { heightBuffer.writeUInt32BE(height || 0); buffers.push(heightBuffer); + var txidBuffer = new Buffer((txid || new Array(65).join('0')), 'hex'); + buffers.push(txidBuffer); + return Buffer.concat(buffers); }; @@ -54,20 +57,15 @@ Encoding.prototype.decodeWalletTransactionKey = function(buffer) { var walletId = reader.read(walletSize).toString('utf8'); var height = reader.readUInt32BE(); + var txid = reader.read(32); + return { walletId: walletId, - height: height + height: height, + txid: txid }; }; -Encoding.prototype.encodeWalletTransactionValue = function(txid) { - return new Buffer(txid, 'hex'); -}; - -Encoding.prototype.decodeWalletTransactionValue = function(buffer) { - return buffer.toString('hex'); -}; - Encoding.prototype.encodeWalletUtxoKey = function(walletId, txid, outputIndex) { var buffers = [this.servicePrefix, this.subKeyMap.utxo.buffer]; diff --git a/lib/services/wallet-api/index.js b/lib/services/wallet-api/index.js index 3c632961..ac017a1f 100644 --- a/lib/services/wallet-api/index.js +++ b/lib/services/wallet-api/index.js @@ -257,11 +257,14 @@ WalletService.prototype.concurrentBlockHandler = function(block, connectBlock, c var operations = []; for(var i = 0; i < txs.length; i++) { + var tx = txs[i]; + var inputs = tx.inputs; var outputs = tx.outputs; for (var outputIndex = 0; outputIndex < outputs.length; outputIndex++) { + var output = outputs[outputIndex]; var script = output.script; @@ -283,8 +286,7 @@ WalletService.prototype.concurrentBlockHandler = function(block, connectBlock, c var walletId = walletIds[j]; operations.push({ type: action, - key: self._encoding.encodeWalletTransactionKey(walletId, block.__height), - value: self._encoding.encodeWalletTransactionValue(tx.id) + key: self._encoding.encodeWalletTransactionKey(walletId, block.__height, tx.id) }); } } @@ -314,8 +316,7 @@ WalletService.prototype.concurrentBlockHandler = function(block, connectBlock, c var inputWalletId = inputWalletIds[inputIndex]; operations.push({ type: action, - key: self._encoding.encodeWalletTransactionKey(inputWalletId, block.__height), - value: self._encoding.encodeWalletTransactionValue(tx.id) + key: self._encoding.encodeWalletTransactionKey(inputWalletId, block.__height, tx.id) }); } } @@ -456,6 +457,21 @@ WalletService.prototype._endpointRemoveWallet = function() { }; }; +WalletService.prototype._endpointRemoveAllWallets = function() { + var self = this; + return function(req, res) { + + self._removeAllWallets(function(err, numRecords) { + if(err) { + return utils.sendError(err, res); + } + res.status(200).jsonp({ + numberRemoved: numRecords + }); + }); + }; +}; + WalletService.prototype._endpointGetAddresses = function() { var self = this; return function(req, res) { @@ -583,7 +599,6 @@ WalletService.prototype._endpointPostAddresses = function() { WalletService.prototype._endpointGetTransactions = function() { var self = this; return function(req, res) { - var walletId = req.params.walletId; self._processStartEndOptions(req, function(err, heights) { @@ -770,9 +785,11 @@ WalletService.prototype._getTransactions = function(walletId, options, callback) } if (!self._cache.peek(key)) { - var stream = self.store.createReadStream({ - gte: self._encoding.encodeWalletTransactionKey(walletId, opts.start), - lt: self._encoding.encodeWalletTransactionKey(walletId, opts.end) + var start = self._encoding.encodeWalletTransactionKey(walletId, opts.start); + var end = self._encoding.encodeWalletTransactionKey(walletId, opts.end); + var stream = self.store.createKeyStream({ + gte: start, + lt: end }); var streamErr; @@ -781,7 +798,7 @@ WalletService.prototype._getTransactions = function(walletId, options, callback) }); stream.on('data', function(data) { - txids.push(self._encoding.decodeWalletTransactionValue(data.value)); + txids.push(self._encoding.decodeWalletTransactionKey(data).txid); }); stream.on('end', function() { @@ -845,6 +862,37 @@ WalletService.prototype._removeWallet = function(walletId, callback) { }); }; +WalletService.prototype._removeAllWallets = function(callback) { + var self = this; + var operations = []; + + var start = self._encoding.servicePrefix; + var end = new Buffer.concat([ start, new Buffer('ff', 'hex') ]); + + var stream = self.store.createKeyStream({ + gte: start, + lte: end + }); + + var streamErr = null; + stream.on('error', function(err) { + streamErr = err; + }); + + stream.on('data', function(data) { + operations.push({ type: 'del', key: data }); + }); + + stream.on('end', function() { + self.store.batch(operations, function(err) { + if(err) { + return callback(err); + } + callback(null, operations.length); + }); + }); +}; + WalletService.prototype._getAddresses = function(walletId, callback) { var self = this; var key = self._encoding.encodeWalletAddressesKey(walletId); @@ -946,6 +994,7 @@ WalletService.prototype._importAddresses = function(walletId, addresses, jobId, job.projectedendtime = now + (now - job.starttime); var operations = results[0].concat(results[1]); + operations.push({ type: 'put', key: self._encoding.encodeWalletAddressesKey(walletId), @@ -1042,11 +1091,12 @@ WalletService.prototype._getTxidIndexOperations = function(walletId, addresses, return callback(err); } + //there is a big problem when we have multiple txs for our wallet for the same height! + var operations = Object.keys(txids).map(function(txid) { return { type: 'put', - key: self._encoding.encodeWalletTransactionKey(walletId, txids[txid]), - value: self._encoding.encodeWalletTransactionValue(txid) + key: self._encoding.encodeWalletTransactionKey(walletId, txids[txid], txid) }; }); @@ -1069,7 +1119,7 @@ WalletService.prototype._storeBalance = function(walletId, balance, callback) { WalletService.prototype._processStartEndOptions = function(req, callback) { var self = this; - if (!(req.query.start && req.query.start < (500 * 1E6))) { + if (req.query.start && req.query.end) { var heights = []; self.node.services.timestamp.getBlockHeights([ @@ -1099,9 +1149,11 @@ WalletService.prototype._processStartEndOptions = function(req, callback) { }); }); } else { + setImmediate(function() { callback(null, [req.query.start, req.query.end]); }); + } }; @@ -1183,6 +1235,9 @@ WalletService.prototype.setupRoutes = function(app) { app.delete('/wallets/:walletId', s._endpointRemoveWallet() ); + app.delete('/wallets/', + s._endpointRemoveAllWallets() + ); app.put('/wallets/:walletId/addresses', s._endpointPutAddresses() ); diff --git a/regtest/v4/bitcoind.js b/regtest/v4/bitcoind.js new file mode 100644 index 00000000..1df2c507 --- /dev/null +++ b/regtest/v4/bitcoind.js @@ -0,0 +1,467 @@ +'use strict'; + +// To run the tests: $ mocha -R spec regtest/bitcoind.js + +var path = require('path');; +var index = require('../..'); +var log = index.log; + +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('Bitcoind Functionality', function() { + + before(function(done) { + this.timeout(60000); + + // Add the regtest network + bitcore.Networks.enableRegtest(); + var regtestNetwork = bitcore.Networks.get('regtest'); + var config = { rpcprotocol: 'http', rpcport: 18332, rpcuser: 'bitcoin', rpcpassword: 'local321', zmqpubrawtx: 'tcp://127.0.0.1:38332' }; + + bitcoind = require('../..').services.Bitcoin({ + connect: [ config ], + node: { + network: regtestNetwork, + getNetworkName: function() { + return 'regtest'; + } + } + }); + + bitcoind.on('error', function(err) { + log.error('error="%s"', err.message); + }); + + bitcoind.start(function() { + log.info('Bitcoind started'); + + client = new BitcoinRPC({ + protocol: config.rpcprotocol || 'http', + host: config.rpchost || '127.0.0.1', + port: config.rpcport, + user: config.rpcuser, + pass: config.rpcpassword, + rejectUnauthorized: true + }); + + 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(60000); + bitcoind.node.stopping = true; + 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, block) { + if (err) { + throw err; + } + should.exist(block); + block.hash.should.equal(blockHashes[i]); + done(); + }); + }); + }); + }); + + describe('get blocks as buffers', function() { + [0,1,2,3,5,6,7,8,9].forEach(function(i) { + it('generated block ' + i, function(done) { + bitcoind.getRawBlock(blockHashes[i], function(err, block) { + if (err) { + throw err; + } + should.exist(block); + (block instanceof Buffer).should.equal(true); + done(); + }); + }); + }); + }); + + describe('get errors as error instances', function() { + it('will wrap an rpc into a javascript error', function(done) { + bitcoind.client.getBlock(1000000000, function(err, response) { + var error = bitcoind._wrapRPCError(err); + (error instanceof Error).should.equal(true); + error.message.should.equal(err.message); + error.code.should.equal(err.code); + should.exist(error.stack); + 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, block) { + if (err) { + throw err; + } + should.exist(block); + 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); + err.code.should.equal(-8); + 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, function(err, response) { + if (err) { + throw err; + } + assert(response.toString('hex') === txhex, 'incorrect tx data result'); + done(); + }); + }); + }); + + it('will return error if the transaction does not exist', function(done) { + var txid = '6226c407d0e9705bdd7158e60983e37d0f5d23529086d6672b07d9238d5aa618'; + bitcoind.getTransaction(txid, function(err, response) { + should.exist(err); + done(); + }); + }); + }); + + describe('get transactions as buffers', 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.getRawTransaction(tx.hash, function(err, response) { + if (err) { + throw err; + } + response.should.be.instanceOf(Buffer); + assert(response.toString('hex') === txhex, 'incorrect tx data result'); + done(); + }); + }); + }); + + it('will return error if the transaction does not exist', function(done) { + var txid = '6226c407d0e9705bdd7158e60983e37d0f5d23529086d6672b07d9238d5aa618'; + bitcoind.getRawTransaction(txid, function(err, response) { + should.exist(err); + done(); + }); + }); + }); + + describe('get block header', function() { + var expectedWork = new BN(6); + [1,2,3,4,5,6,7,8,9].forEach(function(i) { + it('generate block ' + i, function(done) { + bitcoind.getBlockHeader(blockHashes[i], function(err, blockIndex) { + if (err) { + return done(err); + } + should.exist(blockIndex); + should.exist(blockIndex.chainWork); + var work = new BN(blockIndex.chainWork, 'hex'); + work.toString(16).should.equal(expectedWork.toString(16)); + 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); + done(); + }); + }); + }); + it('will get null prevHash for the genesis block', function(done) { + bitcoind.getBlockHeader(0, function(err, header) { + if (err) { + return done(err); + } + should.exist(header); + should.equal(header.prevHash, undefined); + done(); + }); + }); + it('will get error for block not found', function(done) { + bitcoind.getBlockHeader('notahash', function(err, header) { + should.exist(err); + done(); + }); + }); + }); + + 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() { + bitcoind.getBlockHeader(i, function(err, header) { + should.exist(header); + should.exist(header.chainWork); + var work = new BN(header.chainWork, 'hex'); + work.toString(16).should.equal(expectedWork.toString(16)); + expectedWork = expectedWork.add(new BN(2)); + should.exist(header.prevHash); + header.hash.should.equal(blockHashes[i - 1]); + header.prevHash.should.equal(blockHashes[i - 2]); + header.height.should.equal(i); + }); + }); + }); + it('will get error with number greater than tip', function(done) { + bitcoind.getBlockHeader(100000, function(err, header) { + should.exist(err); + done(); + }); + }); + }); + + describe('send transaction functionality', function() { + + it('will not error and return the transaction hash', function(done) { + + // 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 + bitcoind.sendTransaction(tx.serialize(), function(err, hash) { + if (err) { + return done(err); + } + hash.should.equal(tx.hash); + done(); + }); + + }); + + it('will throw an error if an unsigned transaction is sent', function(done) { + var tx = bitcore.Transaction(); + tx.from(utxos[1]); + tx.change(privateKey.toAddress()); + tx.to(destKey.toAddress(), utxos[1].amount * 1e8 - 1000); + bitcoind.sendTransaction(tx.uncheckedSerialize(), function(err, hash) { + should.exist(err); + (err instanceof Error).should.equal(true); + should.not.exist(hash); + done(); + }); + }); + + it('will throw an error for unexpected types (tx decode failed)', function(done) { + var garbage = new Buffer('abcdef', 'hex'); + bitcoind.sendTransaction(garbage, function(err, hash) { + should.exist(err); + should.not.exist(hash); + var num = 23; + bitcoind.sendTransaction(num, function(err, hash) { + should.exist(err); + (err instanceof Error).should.equal(true); + should.not.exist(hash); + done(); + }); + }); + }); + + 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(buffer) { + buffer.toString('hex').should.equal(serialized); + done(); + }); + bitcoind.sendTransaction(serialized, function(err, hash) { + if (err) { + return done(err); + } + should.exist(hash); + }); + }); + + }); + + describe('fee estimation', function() { + it('will estimate fees', function(done) { + bitcoind.estimateFee(1, function(err, fees) { + if (err) { + return done(err); + } + fees.should.equal(-1); + done(); + }); + }); + }); + + 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) { + done(); + } + }); + client.generate(1, function(err, response) { + if (err) { + throw err; + } + }); + }); + }); + + describe('get detailed transaction', function() { + it('should include details for coinbase tx', function(done) { + bitcoind.getDetailedTransaction(utxos[0].txid, function(err, tx) { + if (err) { + return done(err); + } + should.exist(tx.height); + tx.height.should.be.a('number'); + should.exist(tx.blockTimestamp); + should.exist(tx.blockHash); + tx.coinbase.should.equal(true); + tx.version.should.equal(1); + tx.hex.should.be.a('string'); + tx.locktime.should.equal(0); + tx.feeSatoshis.should.equal(0); + tx.outputSatoshis.should.equal(50 * 1e8); + tx.inputSatoshis.should.equal(0); + tx.inputs.length.should.equal(1); + tx.outputs.length.should.equal(1); + should.equal(tx.inputs[0].prevTxId, null); + should.equal(tx.inputs[0].outputIndex, null); + tx.inputs[0].script.should.be.a('string'); + should.equal(tx.inputs[0].scriptAsm, null); + should.equal(tx.inputs[0].address, null); + should.equal(tx.inputs[0].satoshis, null); + tx.outputs[0].satoshis.should.equal(50 * 1e8); + tx.outputs[0].script.should.be.a('string'); + tx.outputs[0].scriptAsm.should.be.a('string'); + tx.outputs[0].spentTxId.should.be.a('string'); + tx.outputs[0].spentIndex.should.equal(0); + tx.outputs[0].spentHeight.should.be.a('number'); + tx.outputs[0].address.should.be.a('string'); + done(); + }); + }); + }); + + describe('#getInfo', function() { + it('will get information', function(done) { + bitcoind.getInfo(function(err, info) { + if (err) { + return done(err); + } + info.network.should.equal('regtest'); + 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); + done(); + }); + }); + }); + +}); diff --git a/regtest/wallet.js b/regtest/wallet.js index 4d0576d0..3f025712 100644 --- a/regtest/wallet.js +++ b/regtest/wallet.js @@ -15,6 +15,7 @@ var BitcoinRPC = require('bitcoind-rpc'); var path = require('path'); var fs = require('fs'); var http = require('http'); +var crypto = require('crypto'); var bitcoreDataDir = '/tmp/bitcore'; var bitcoinDataDir = '/tmp/bitcoin'; @@ -81,8 +82,6 @@ var bitcore = { protocol: 'http:', hostname: 'localhost', port: 53001, - method: 'GET', - body: '' }, opts: { cwd: bitcoreDataDir }, datadir: bitcoreDataDir, @@ -100,18 +99,19 @@ var numberOfStartingTxs = 50; var walletPrivKeys = []; var initialTxs = []; var fee = 100000; +var walletId = crypto.createHash('sha256').update('test').digest('hex'); describe('Wallet Operations', function() { this.timeout(60000); - afterEach(function(done) { + after(function(done) { bitcore.process.kill(); bitcoin.process.kill(); setTimeout(done, 2000); }); - beforeEach(function(done) { + before(function(done) { async.series([ startBitcoind, waitForBitcoinReady, @@ -122,11 +122,93 @@ describe('Wallet Operations', function() { ], done); }); - it('should generate txs', function(done) { - console.log(bitcore); - done(); + it('should register wallet', function(done) { + + var httpOpts = Object.assign({ + path: '/wallet-api/wallets/' + walletId, + method: 'POST' + }, bitcore.httpOpts); + + queryBitcoreNode(httpOpts, function(err, res) { + if (err) { + return done(err); + } + res.should.deep.equal(JSON.stringify({ + walletId: '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08' + })); + done(); + }); }); + it('should upload a wallet', function(done) { + var addresses = JSON.stringify(walletPrivKeys.map(function(privKey) { + return privKey.toAddress().toString(); + })); + var httpOpts = Object.assign({ + path: '/wallet-api/wallets/' + walletId + '/addresses', + method: 'POST', + body: addresses, + headers: { + 'Content-Type': 'application/json', + 'Content-Length': addresses.length + } + }, bitcore.httpOpts); + async.waterfall([ queryBitcoreNode.bind(this, httpOpts) ], function(err, res) { + if (err) { + return done(err); + } + var job = JSON.parse(res); + + Object.keys(job).should.deep.equal(['jobId']); + + var httpOpts = Object.assign({ + path: '/wallet-api/jobs/' + job.jobId, + method: 'GET' + }, bitcore.httpOpts); + + async.retry({ times: 10, interval: 1000 }, function(next) { + queryBitcoreNode(httpOpts, function(err, res) { + if (err) { + return next(err); + } + var result = JSON.parse(res); + if (result.status === 'complete') { + return next(); + } + next(res); + }); + + }, function(err) { + if(err) { + return done(err); + } + done(); + }); + }); + }); + + it('should get a list of transactions', function(done) { + var httpOpts = Object.assign({ + path: '/wallet-api/wallets/' + walletId + '/transactions', + method: 'GET', + headers: { + 'Content-Type': 'application/json' + } + }, bitcore.httpOpts); + queryBitcoreNode(httpOpts, function(err, res) { + if(err) { + return done(err); + } + var results = res.split('\n').slice(0, -1); + results.length.should.equal(numberOfStartingTxs); + for(var i = 0; i < results.length; i++) { + var result = results[i]; + var tx = new Transaction(JSON.parse(result)); + tx.uncheckedSerialize().should.equal(initialTxs[i].serialize()); + } + done(); + }); + }); }); function writeConfigFile(fileStr, obj) { @@ -145,12 +227,10 @@ function waitForService(task, next) { } function queryBitcoreNode(httpOpts, next) { -console.log('request', httpOpts); var error; var request = http.request(httpOpts, function(res) { - if (res.statusCode !== 200) { -console.log('status code: ', error, res.statusCode); + if (res.statusCode !== 200 && res.statusCode !== 201) { if (error) { return; } @@ -169,17 +249,13 @@ console.log('status code: ', error, res.statusCode); }); res.on('end', function() { -console.log('end: ', error); if (error) { return; } if (httpOpts.errorFilter) { return next(httpOpts.errorFilter(resError, resData)); } - if (resError) { - return next(resError); - } - next(); + next(resError, resData); }); }); @@ -189,7 +265,7 @@ console.log('end: ', error); next(error); }); - request.write(''); + request.write(httpOpts.body || ''); request.end(); } @@ -208,15 +284,16 @@ function waitForBitcoreNode(next) { var httpOpts = Object.assign({ path: '/wallet-api/issynced', - errorFilter: errorFilter + errorFilter: errorFilter, + method: 'GET' }, bitcore.httpOpts); waitForService(queryBitcoreNode.bind(this, httpOpts), next); } function waitForBitcoinReady(next) { - async.retry({ times: 10, interval: 1000 }, function(next) { - rpc.generate(150, function(err, res) { + waitForService(function(next) { + rpc.generate(101, function(err, res) { if (err || (res && res.error)) { return next('keep trying'); } @@ -227,7 +304,7 @@ function waitForBitcoinReady(next) { return next(err); } next(); - }); + }, next); } function initializeAndStartService(opts, next) { @@ -335,7 +412,7 @@ function sendTx(tx, next) { if(err) { return next(err); } - rpc.generate(6, function(err) { + rpc.generate(1, function(err) { if(err) { return next(err); } diff --git a/test/services/wallet-api/encoding.unit.js b/test/services/wallet-api/encoding.unit.js index 6a8f1367..0d547b8a 100644 --- a/test/services/wallet-api/encoding.unit.js +++ b/test/services/wallet-api/encoding.unit.js @@ -23,12 +23,24 @@ describe('Wallet-Api service encoding', function() { satsBuf.writeDoubleBE(sats); it('should encode wallet transaction key' , function() { - encoding.encodeWalletTransactionKey(walletId, height).should.deep.equal(Buffer.concat([ + encoding.encodeWalletTransactionKey(walletId, height, txid).should.deep.equal(Buffer.concat([ servicePrefix, encoding.subKeyMap.transaction.buffer, new Buffer('0c', 'hex'), new Buffer(walletId), - new Buffer('00000001', 'hex') + new Buffer('00000001', 'hex'), + new Buffer(txid, 'hex') + ])); + }); + + it('should encode wallet transaction key without a txid (all zeroes) or height (0)' , function() { + encoding.encodeWalletTransactionKey(walletId).should.deep.equal(Buffer.concat([ + servicePrefix, + encoding.subKeyMap.transaction.buffer, + new Buffer('0c', 'hex'), + new Buffer(walletId), + new Buffer('00000000', 'hex'), + new Buffer(new Array(65).join('0'), 'hex') ])); }); @@ -44,14 +56,6 @@ describe('Wallet-Api service encoding', function() { walletTransactionKey.height.should.equal(height); }); - it('should encode wallet transaction value', function() { - encoding.encodeWalletTransactionValue(txid).should.deep.equal(new Buffer(txid, 'hex')); - }); - - it('should decode wallet transaction value', function() { - encoding.decodeWalletTransactionValue(new Buffer(txid, 'hex')).should.equal(txid); - }); - it('should encode wallet utxo key', function() { encoding.encodeWalletUtxoKey(walletId, txid, outputIndex).should.deep.equal(Buffer.concat([ servicePrefix,