From 5dfadf9d2b4944be1c0e22008cb092e323e7f8f8 Mon Sep 17 00:00:00 2001 From: Chris Kleeschulte Date: Thu, 27 Apr 2017 07:57:59 -0400 Subject: [PATCH] Added wallet regtest. --- lib/services/wallet-api/index.js | 13 +-- regtest/wallet.js | 144 +++++++++++++++++++++++-------- 2 files changed, 117 insertions(+), 40 deletions(-) diff --git a/lib/services/wallet-api/index.js b/lib/services/wallet-api/index.js index ac017a1f..f11c7806 100644 --- a/lib/services/wallet-api/index.js +++ b/lib/services/wallet-api/index.js @@ -397,10 +397,9 @@ WalletService.prototype._loadAllBalances = function(callback) { WalletService.prototype._endpointUTXOs = function() { var self = this; return function(req, res) { - req.setTimeout(600000); var walletId = req.params.walletId; var queryMempool = req.query.queryMempool !== false; - var height = null; + var height = self.node.services.db.tip.__height; var options = { queryMempool: queryMempool }; @@ -435,7 +434,11 @@ WalletService.prototype._endpointGetBalance= function() { if(err) { return utils.sendError(err, res); } - res.status(200).jsonp(result); + res.status(200).jsonp({ + satoshis: result, + height: self.node.services.db.tip.__height, + hash: self.node.services.db.tip.hash + }); }); }; }; @@ -699,13 +702,12 @@ WalletService.prototype._getUtxos = function(walletId, options, callback) { stream.on('data', function(data) { var key = self._encoding.decodeWalletUtxoKey(data.key); var value = self._encoding.decodeWalletUtxoValue(data.value); - utxos.push({ txid: key.txid, vout: key.outputIndex, height: value.height, satoshis: value.satoshis, - scriptPubKey: value.script._scriptBuffer + scriptPubKey: value.script.toString('hex') }); }); @@ -727,7 +729,6 @@ WalletService.prototype._getBalance = function(walletId, options, callback) { if(err) { return callback(err); } - callback(null, self._encoding.decodeWalletBalanceValue(buffer)); }); }; diff --git a/regtest/wallet.js b/regtest/wallet.js index 37e2189b..3d967ab2 100644 --- a/regtest/wallet.js +++ b/regtest/wallet.js @@ -17,6 +17,7 @@ var fs = require('fs'); var http = require('http'); var crypto = require('crypto'); +var debug = false; var bitcoreDataDir = '/tmp/bitcore'; var bitcoinDataDir = '/tmp/bitcoin'; @@ -93,12 +94,15 @@ var bitcore = { var rpc = new BitcoinRPC(rpcConfig); var walletPassphrase = 'test'; -var numberOfStartingTxs = 50; +var numberOfStartingTxs = 49; //this should be an even number of txs +var txCount = 0; +var blockHeight = 0; var walletPrivKeys = []; var initialTxs = []; var fee = 100000; var walletId = crypto.createHash('sha256').update('test').digest('hex'); +var satoshisReceived = 0; describe('Wallet Operations', function() { @@ -123,11 +127,7 @@ describe('Wallet Operations', function() { it('should register wallet', function(done) { - var httpOpts = Object.assign({ - path: '/wallet-api/wallets/' + walletId, - method: 'POST' - }, bitcore.httpOpts); - + var httpOpts = getHttpOpts({ path: '/wallet-api/wallets/' + walletId, method: 'POST' }); queryBitcoreNode(httpOpts, function(err, res) { if (err) { return done(err); @@ -143,15 +143,12 @@ describe('Wallet Operations', function() { var addresses = JSON.stringify(walletPrivKeys.map(function(privKey) { return privKey.toAddress().toString(); })); - var httpOpts = Object.assign({ + var httpOpts = getHttpOpts({ path: '/wallet-api/wallets/' + walletId + '/addresses', method: 'POST', body: addresses, - headers: { - 'Content-Type': 'application/json', - 'Content-Length': addresses.length - } - }, bitcore.httpOpts); + length: addresses.length + }); async.waterfall([ queryBitcoreNode.bind(this, httpOpts) ], function(err, res) { if (err) { return done(err); @@ -160,10 +157,7 @@ describe('Wallet Operations', function() { Object.keys(job).should.deep.equal(['jobId']); - var httpOpts = Object.assign({ - path: '/wallet-api/jobs/' + job.jobId, - method: 'GET' - }, bitcore.httpOpts); + var httpOpts = getHttpOpts({ path: '/wallet-api/jobs/' + job.jobId }); async.retry({ times: 10, interval: 1000 }, function(next) { queryBitcoreNode(httpOpts, function(err, res) { @@ -187,27 +181,89 @@ describe('Wallet Operations', function() { }); 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); + var httpOpts = getHttpOpts({ path: '/wallet-api/wallets/' + walletId + '/transactions' }); queryBitcoreNode(httpOpts, function(err, res) { if(err) { return done(err); } - var results = res.split('\n').slice(0, -1); - results.length.should.equal(numberOfStartingTxs); + //jsonl is returned, so there will be a newline at the end + var results = res.split('\n').filter(function(result) { + return result.length > 0; + }); var map = initialTxs.map(function(tx) { return tx.serialize(); }); 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()); + map.splice(map.indexOf(tx.uncheckedSerialize()), 1); } + map.length.should.equal(0); + results.length.should.equal(numberOfStartingTxs); + done(); + }); + }); + + it('should get the balance of a wallet', function(done) { + var httpOpts = getHttpOpts({ path: '/wallet-api/wallets/' + walletId + '/balance' }); + queryBitcoreNode(httpOpts, function(err, res) { + if(err) { + return done(err); + } + var results = JSON.parse(res); + results.satoshis.should.equal(satoshisReceived); + done(); + }); + + }); + + it('should get the set of utxos for the wallet', function(done) { + var httpOpts = getHttpOpts({ path: '/wallet-api/wallets/' + walletId + '/utxos' }); + queryBitcoreNode(httpOpts, function(err, res) { + if(err) { + return done(err); + } + var results = JSON.parse(res); + // all starting txs were spending to our wallet + results.utxos.length.should.equal(numberOfStartingTxs); + var map = initialTxs.map(function(tx) { + return tx.txid; + }); + var balance = 0; + for(var i = 0; i < results.utxos.length; i++) { + var result = results.utxos[i]; + balance += result.satoshis; + map.splice(map.indexOf(result.txid), 1); + } + map.length.should.equal(0); + results.height.should.equal(blockHeight); + balance.should.equal(satoshisReceived); + done(); + }); + }); + + it('should get the list of jobs', function(done) { + var httpOpts = getHttpOpts({ path: '/wallet-api/jobs' }); + queryBitcoreNode(httpOpts, function(err, res) { + if(err) { + return done(err); + } + var results = JSON.parse(res); + results.jobCount.should.equal(1); + done(); + }); + }); + + it('should remove all wallets', function(done) { + var httpOpts = getHttpOpts({ path: '/wallet-api/wallets', method: 'DELETE' }); + queryBitcoreNode(httpOpts, function(err, res) { + if(err) { + return done(err); + } + //walletTransactionKey = 1, walletUtxoKey = 1, walletUtxoSatoshis = 1 <-- multiples of numberOfStartingTxs + //walletAddresses = 1, walletBalance = 1 <-- one record per index + var results = JSON.parse(res); + results.numberRemoved.should.equal((numberOfStartingTxs * 3) + 2); done(); }); }); @@ -273,7 +329,9 @@ function queryBitcoreNode(httpOpts, next) { function waitForBitcoreNode(next) { bitcore.process.stdout.on('data', function(data) { - console.log(data.toString()); + if (debug) { + console.log(data.toString()); + } }); bitcore.process.stderr.on('data', function(data) { console.log(data.toString()); @@ -284,11 +342,7 @@ function waitForBitcoreNode(next) { } }; - var httpOpts = Object.assign({ - path: '/wallet-api/issynced', - errorFilter: errorFilter, - method: 'GET' - }, bitcore.httpOpts); + var httpOpts = getHttpOpts({ path: '/wallet-api/issynced', errorFilter: errorFilter }); waitForService(queryBitcoreNode.bind(this, httpOpts), next); } @@ -299,6 +353,7 @@ function waitForBitcoinReady(next) { if (err || (res && res.error)) { return next('keep trying'); } + blockHeight += 150; next(); }); }, function(err) { @@ -372,7 +427,7 @@ function getPrivateKeyWithABalance(next) { } function generateSpendingTx(privKey, utxo) { - + txCount++; var toPrivKey = new PrivateKey('testnet'); //external addresses var changePrivKey = new PrivateKey('testnet'); //our wallet keys var utxoSatoshis = Unit.fromBTC(utxo.amount).satoshis; @@ -386,6 +441,7 @@ function generateSpendingTx(privKey, utxo) { tx.sign(privKey); walletPrivKeys.push(changePrivKey); + satoshisReceived += Unit.fromBTC(utxo.amount).toSatoshis() - (satsToPrivKey + fee); return tx; } @@ -406,7 +462,13 @@ function setupInitialTx(index, next) { } function setupInitialTxs(next) { - async.timesSeries(numberOfStartingTxs, setupInitialTx, next); + async.timesSeries(numberOfStartingTxs, setupInitialTx, function(err) { + if(err) { + return next(err); + } + blockHeight++; + rpc.generate(1, next); + }); } function sendTx(tx, generateBlocks, next) { @@ -415,6 +477,7 @@ function sendTx(tx, generateBlocks, next) { return next(err); } if (generateBlocks) { + blockHeight += generateBlocks; rpc.generate(generateBlocks, function(err) { if(err) { return next(err); @@ -426,3 +489,16 @@ function sendTx(tx, generateBlocks, next) { } }); } + +function getHttpOpts(opts) { + return Object.assign({ + path: opts.path, + method: opts.method || 'GET', + body: opts.body, + headers: { + 'Content-Type': 'application/json', + 'Content-Length': opts.length || 0 + }, + errorFilter: opts.errorFilter + }, bitcore.httpOpts); +}