Browse Source

Merge pull request #73 from isocolsky/history

History
activeAddress
Ivan Socolsky 10 years ago
parent
commit
36b7fb0a48
  1. 1
      bit-wallet/bit
  2. 43
      bit-wallet/bit-history
  3. 21
      lib/client/api.js
  4. 12
      lib/expressapp.js
  5. 176
      lib/server.js
  6. 1
      package.json
  7. 360
      test/integration/server.js
  8. 76
      test/testdata.js

1
bit-wallet/bit

@ -15,6 +15,7 @@ program
.command('reject <txpId> [reason]', 'reject a transaction proposal') .command('reject <txpId> [reason]', 'reject a transaction proposal')
.command('broadcast <txpId>', 'broadcast a transaction proposal to the Bitcoin network') .command('broadcast <txpId>', 'broadcast a transaction proposal to the Bitcoin network')
.command('rm <txpId>', 'remove a transaction proposal') .command('rm <txpId>', 'remove a transaction proposal')
.command('history', 'list of past incoming and outgoing transactions')
.command('export', 'export wallet critical data') .command('export', 'export wallet critical data')
.command('import', 'import wallet critical data') .command('import', 'import wallet critical data')
.command('confirm', 'show copayer\'s data for confirmation') .command('confirm', 'show copayer\'s data for confirmation')

43
bit-wallet/bit-history

@ -0,0 +1,43 @@
#!/usr/bin/env node
var _ = require('lodash');
var fs = require('fs');
var moment = require('moment');
var program = require('commander');
var Utils = require('./cli-utils');
program = Utils.configureCommander(program);
program
.parse(process.argv);
var args = program.args;
var client = Utils.getClient(program);
var txData;
client.getTxHistory({}, function (err, txs) {
if (_.isEmpty(txs))
return;
console.log("* TX History:")
_.each(txs, function(tx) {
var time = moment(tx.time * 1000).fromNow();
var amount = Utils.renderAmount(tx.amount);
var confirmations = tx.confirmations || 0;
var proposal = tx.proposalId ? '["' + tx.message + '" by ' + tx.creatorName + '] ' : '';
switch (tx.action) {
case 'received':
direction = '<=';
break;
case 'moved':
direction = '==';
break;
case 'sent':
direction = '=>';
break;
}
console.log("\t%s: %s %s %s %s(%s confirmations)", time, direction, tx.action, amount, proposal, confirmations);
});
});

21
lib/client/api.js

@ -36,6 +36,7 @@ function _decryptMessage(message, encryptingKey) {
}; };
function _processTxps(txps, encryptingKey) { function _processTxps(txps, encryptingKey) {
if (!txps) return;
_.each([].concat(txps), function(txp) { _.each([].concat(txps), function(txp) {
txp.encryptedMessage = txp.message; txp.encryptedMessage = txp.message;
txp.message = _decryptMessage(txp.message, encryptingKey); txp.message = _decryptMessage(txp.message, encryptingKey);
@ -482,10 +483,6 @@ API.prototype.getMainAddresses = function(opts, cb) {
}); });
}; };
API.prototype.history = function(limit, cb) {
};
API.prototype.getBalance = function(cb) { API.prototype.getBalance = function(cb) {
var self = this; var self = this;
@ -768,4 +765,20 @@ API.prototype.removeTxProposal = function(txp, cb) {
}); });
}; };
API.prototype.getTxHistory = function(opts, cb) {
var self = this;
this._loadAndCheck(function(err, data) {
if (err) return cb(err);
var url = '/v1/txhistory/';
self._doGetRequest(url, data, function(err, txs) {
if (err) return cb(err);
_processTxps(txs, data.sharedEncryptingKey);
return cb(null, txs);
});
});
};
module.exports = API; module.exports = API;

12
lib/expressapp.js

@ -263,6 +263,18 @@ ExpressApp.start = function(opts) {
}); });
}); });
router.get('/v1/txhistory/', function(req, res) {
getServerWithAuth(req, res, function(server) {
server.getTxHistory({}, function(err, txs) {
if (err) return returnError(err, res, req);
res.json(txs);
res.end();
});
});
});
// TODO: DEBUG only! // TODO: DEBUG only!
router.get('/v1/dump', function(req, res) { router.get('/v1/dump', function(req, res) {
var server = WalletService.getInstance(); var server = WalletService.getInstance();

176
lib/server.js

@ -80,8 +80,7 @@ WalletService.getInstanceWithAuth = function(opts, cb) {
if (!copayer) return cb(new ClientError('NOTAUTHORIZED', 'Copayer not found')); if (!copayer) return cb(new ClientError('NOTAUTHORIZED', 'Copayer not found'));
var pubKey = opts.readOnly ? copayer.roPubKey : copayer.rwPubKey; var pubKey = opts.readOnly ? copayer.roPubKey : copayer.rwPubKey;
var isValid = server._verifySignature(opts.message, opts.signature, var isValid = server._verifySignature(opts.message, opts.signature, pubKey);
pubKey);
if (!isValid) if (!isValid)
return cb(new ClientError('NOTAUTHORIZED', 'Invalid signature')); return cb(new ClientError('NOTAUTHORIZED', 'Invalid signature'));
@ -289,11 +288,11 @@ WalletService.prototype.getMainAddresses = function(opts, cb) {
self.storage.fetchAddresses(self.walletId, function(err, addresses) { self.storage.fetchAddresses(self.walletId, function(err, addresses) {
if (err) return cb(err); if (err) return cb(err);
var mainAddresses = _.filter(addresses, {
isChange: false
});
return cb(null, mainAddresses); var onlyMain = _.reject(addresses, {
isChange: true
});
return cb(null, onlyMain);
}); });
}; };
@ -324,6 +323,20 @@ WalletService.prototype.verifyMessageSignature = function(opts, cb) {
WalletService.prototype._getBlockExplorer = function(provider, network) { WalletService.prototype._getBlockExplorer = function(provider, network) {
var url; var url;
function getTransactionsInsight(url, addresses, cb) {
var request = require('request');
request({
method: "POST",
url: url + '/api/addrs/txs',
json: {
addrs: [].concat(addresses).join(',')
}
}, function(err, res, body) {
if (err || res.statusCode != 200) return cb(err || res);
return cb(null, body);
});
};
if (this.blockExplorer) if (this.blockExplorer)
return this.blockExplorer; return this.blockExplorer;
@ -339,7 +352,9 @@ WalletService.prototype._getBlockExplorer = function(provider, network) {
url = 'https://test-insight.bitpay.com:443' url = 'https://test-insight.bitpay.com:443'
break; break;
} }
return new Explorers.Insight(url, network); var bc = new Explorers.Insight(url, network);
bc.getTransactions = _.bind(getTransactionsInsight, bc, url);
return bc;
break; break;
} }
}; };
@ -797,7 +812,7 @@ WalletService.prototype.rejectTx = function(opts, cb) {
}; };
/** /**
* Retrieves all pending transaction proposals. * Retrieves pending transaction proposals.
* @param {Object} opts * @param {Object} opts
* @returns {TxProposal[]} Transaction proposal. * @returns {TxProposal[]} Transaction proposal.
*/ */
@ -812,7 +827,7 @@ WalletService.prototype.getPendingTxs = function(opts, cb) {
}; };
/** /**
* Retrieves pending transaction proposals in the range (maxTs-minTs) * Retrieves all transaction proposals in the range (maxTs-minTs)
* Times are in UNIX EPOCH * Times are in UNIX EPOCH
* *
* @param {Object} opts.minTs (defaults to 0) * @param {Object} opts.minTs (defaults to 0)
@ -830,7 +845,7 @@ WalletService.prototype.getTxs = function(opts, cb) {
/** /**
* Retrieves notifications in the range (maxTs-minTs). * Retrieves notifications in the range (maxTs-minTs).
* Times are in UNIX EPOCH. Order is assured even for events with the same time * Times are in UNIX EPOCH. Order is assured even for events with the same time
* *
* @param {Object} opts.minTs (defaults to 0) * @param {Object} opts.minTs (defaults to 0)
@ -848,7 +863,148 @@ WalletService.prototype.getNotifications = function(opts, cb) {
}; };
WalletService.prototype._normalizeTxHistory = function(txs) {
return _.map(txs, function(tx) {
var inputs = _.map(tx.vin, function(item) {
return {
address: item.addr,
amount: item.valueSat,
}
});
var outputs = _.map(tx.vout, function(item) {
var itemAddr;
// If classic multisig, ignore
if (item.scriptPubKey && item.scriptPubKey.addresses.length == 1) {
itemAddr = item.scriptPubKey.addresses[0];
}
return {
address: itemAddr,
amount: parseInt((item.value * 1e8).toFixed(0)),
}
});
return {
txid: tx.txid,
confirmations: tx.confirmations,
fees: parseInt((tx.fees * 1e8).toFixed(0)),
time: !_.isNaN(tx.time) ? tx.time : Math.floor(Date.now() / 1000),
inputs: inputs,
outputs: outputs,
};
});
};
/**
* Retrieves all transactions (incoming & outgoing) in the range (maxTs-minTs)
* Times are in UNIX EPOCH
*
* @param {Object} opts.minTs (defaults to 0)
* @param {Object} opts.maxTs (defaults to now)
* @param {Object} opts.limit
* @returns {TxProposal[]} Transaction proposals, first newer
*/
WalletService.prototype.getTxHistory = function(opts, cb) {
var self = this;
function decorate(txs, addresses, proposals) {
function sum(items, isMine, isChange) {
var filter = {};
if (_.isBoolean(isMine)) filter.isMine = isMine;
if (_.isBoolean(isChange)) filter.isChange = isChange;
return _.reduce(_.where(items, filter),
function(memo, item) {
return memo + item.amount;
}, 0);
};
var indexedAddresses = _.indexBy(addresses, 'address');
var indexedProposals = _.indexBy(proposals, 'txid');
_.each(txs, function(tx) {
_.each(tx.inputs.concat(tx.outputs), function(item) {
var address = indexedAddresses[item.address];
item.isMine = !!address;
item.isChange = address ? address.isChange : false;
});
var amountIn = sum(tx.inputs, true);
var amountOut = sum(tx.outputs, true, false);
var amountOutChange = sum(tx.outputs, true, true);
var amount;
if (amountIn == (amountOut + amountOutChange + (amountIn > 0 ? tx.fees : 0))) {
tx.action = 'moved';
amount = amountOut;
} else {
amount = amountIn - amountOut - amountOutChange - (amountIn > 0 ? tx.fees : 0);
tx.action = amount > 0 ? 'sent' : 'received';
}
tx.amount = Math.abs(amount);
if (tx.action == 'sent' || tx.action == 'moved') {
tx.addressTo = tx.outputs[0].address;
};
delete tx.inputs;
delete tx.outputs;
var proposal = indexedProposals[tx.txid];
if (proposal) {
tx.proposalId = proposal.id;
tx.creatorName = proposal.creatorName;
tx.message = proposal.message;
tx.actions = _.map(proposal.actions, function(action) {
return _.pick(action, ['createdOn', 'type', 'copayerId', 'copayerName', 'comment']);
});
// tx.sentTs = proposal.sentTs;
// tx.merchant = proposal.merchant;
//tx.paymentAckMemo = proposal.paymentAckMemo;
}
});
};
function paginate(txs) {
// TODO
};
// Get addresses for this wallet
self.storage.fetchAddresses(self.walletId, function(err, addresses) {
if (err) return cb(err);
if (addresses.length == 0) return cb(null, []);
var addressStrs = _.pluck(addresses, 'address');
var networkName = Bitcore.Address(addressStrs[0]).toObject().network;
var bc = self._getBlockExplorer('insight', networkName);
async.parallel([
function(next) {
self.storage.fetchTxs(self.walletId, opts, function(err, txps) {
if (err) return next(err);
next(null, txps);
});
},
function(next) {
bc.getTransactions(addressStrs, function(err, txs) {
if (err) return next(err);
next(null, self._normalizeTxHistory(txs));
});
},
], function(err, res) {
if (err) return cb(err);
var proposals = res[0];
var txs = res[1];
decorate(txs, addresses, proposals);
paginate(txs);
return cb(null, txs);
});
});
};
module.exports = WalletService; module.exports = WalletService;

1
package.json

@ -29,6 +29,7 @@
"levelup": "^0.19.0", "levelup": "^0.19.0",
"lodash": "*", "lodash": "*",
"mocha-lcov-reporter": "0.0.1", "mocha-lcov-reporter": "0.0.1",
"moment": "^2.9.0",
"morgan": "*", "morgan": "*",
"npmlog": "^0.1.1", "npmlog": "^0.1.1",
"preconditions": "^1.0.7", "preconditions": "^1.0.7",

360
test/integration/server.js

@ -2,12 +2,15 @@
var _ = require('lodash'); var _ = require('lodash');
var async = require('async'); var async = require('async');
var inspect = require('util').inspect;
var chai = require('chai'); var chai = require('chai');
var sinon = require('sinon'); var sinon = require('sinon');
var should = chai.should(); var should = chai.should();
var levelup = require('levelup'); var levelup = require('levelup');
var memdown = require('memdown'); var memdown = require('memdown');
var log = require('npmlog');
log.debug = log.verbose;
var Bitcore = require('bitcore'); var Bitcore = require('bitcore');
var Utils = require('../../lib/utils'); var Utils = require('../../lib/utils');
@ -91,7 +94,7 @@ helpers.toSatoshi = function(btc) {
helpers.stubUtxos = function(server, wallet, amounts, cb) { helpers.stubUtxos = function(server, wallet, amounts, cb) {
var amounts = [].concat(amounts); var amounts = [].concat(amounts);
async.map(_.range(Math.ceil(amounts.length / 2)), function(i, next) { async.map(_.range(1, Math.ceil(amounts.length / 2) + 1), function(i, next) {
server.createAddress({}, function(err, address) { server.createAddress({}, function(err, address) {
next(err, address); next(err, address);
}); });
@ -126,6 +129,9 @@ helpers.stubBroadcastFail = function() {
blockExplorer.broadcast = sinon.stub().callsArgWith(1, 'broadcast error'); blockExplorer.broadcast = sinon.stub().callsArgWith(1, 'broadcast error');
}; };
helpers.stubHistory = function(txs) {
blockExplorer.getTransactions = sinon.stub().callsArgWith(1, null, txs);
};
helpers.clientSign = function(txp, xprivHex) { helpers.clientSign = function(txp, xprivHex) {
//Derive proper key to sign, for each input //Derive proper key to sign, for each input
@ -175,6 +181,19 @@ helpers.createProposalOpts = function(toAddress, amount, message, signingKey) {
return opts; return opts;
}; };
helpers.createAddresses = function(server, wallet, main, change, cb) {
async.map(_.range(main + change), function(i, next) {
var address = wallet.createAddress(i >= main);
server.storage.storeAddressAndWallet(wallet, address, function(err) {
if (err) return next(err);
next(null, address);
});
}, function(err, addresses) {
if (err) throw new Error('Could not generate addresses');
return cb(_.take(addresses, main), _.takeRight(addresses, change));
});
};
var db, storage, blockExplorer; var db, storage, blockExplorer;
@ -650,9 +669,7 @@ describe('Copay server', function() {
helpers.createAndJoinWallet(2, 3, function(s, w) { helpers.createAndJoinWallet(2, 3, function(s, w) {
server = s; server = s;
wallet = w; wallet = w;
server.createAddress({}, function(err, address) { done();
done();
});
}); });
}); });
@ -661,7 +678,7 @@ describe('Copay server', function() {
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, 'some message', TestData.copayers[0].privKey); var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, 'some message', TestData.copayers[0].privKey);
server.createTx(txOpts, function(err, tx) { server.createTx(txOpts, function(err, tx) {
should.not.exist(err); should.not.exist(err);
tx.should.exist; should.exist(tx);
tx.message.should.equal('some message'); tx.message.should.equal('some message');
tx.isAccepted().should.equal.false; tx.isAccepted().should.equal.false;
tx.isRejected().should.equal.false; tx.isRejected().should.equal.false;
@ -791,11 +808,11 @@ describe('Copay server', function() {
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 12, null, TestData.copayers[0].privKey); var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 12, null, TestData.copayers[0].privKey);
server.createTx(txOpts, function(err, tx) { server.createTx(txOpts, function(err, tx) {
should.not.exist(err); should.not.exist(err);
tx.should.exist; should.exist(tx);
var txOpts2 = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 8, null, TestData.copayers[0].privKey); var txOpts2 = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 8, null, TestData.copayers[0].privKey);
server.createTx(txOpts2, function(err, tx) { server.createTx(txOpts2, function(err, tx) {
should.not.exist(err); should.not.exist(err);
tx.should.exist; should.exist(tx);
server.getPendingTxs({}, function(err, txs) { server.getPendingTxs({}, function(err, txs) {
should.not.exist(err); should.not.exist(err);
txs.length.should.equal(2); txs.length.should.equal(2);
@ -816,7 +833,7 @@ describe('Copay server', function() {
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 12, null, TestData.copayers[0].privKey); var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 12, null, TestData.copayers[0].privKey);
server.createTx(txOpts, function(err, tx) { server.createTx(txOpts, function(err, tx) {
should.not.exist(err); should.not.exist(err);
tx.should.exist; should.exist(tx);
var txOpts2 = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 24, null, TestData.copayers[0].privKey); var txOpts2 = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 24, null, TestData.copayers[0].privKey);
server.createTx(txOpts2, function(err, tx) { server.createTx(txOpts2, function(err, tx) {
err.code.should.equal('INSUFFICIENTFUNDS'); err.code.should.equal('INSUFFICIENTFUNDS');
@ -839,9 +856,7 @@ describe('Copay server', function() {
it('should create tx using different UTXOs for simultaneous requests', function(done) { it('should create tx using different UTXOs for simultaneous requests', function(done) {
var N = 5; var N = 5;
helpers.stubUtxos(server, wallet, _.times(N, function() { helpers.stubUtxos(server, wallet, _.range(100, 100 + N, 0), function(utxos) {
return 100;
}), function(utxos) {
server.getBalance({}, function(err, balance) { server.getBalance({}, function(err, balance) {
should.not.exist(err); should.not.exist(err);
balance.totalAmount.should.equal(helpers.toSatoshi(N * 100)); balance.totalAmount.should.equal(helpers.toSatoshi(N * 100));
@ -877,15 +892,13 @@ describe('Copay server', function() {
helpers.createAndJoinWallet(2, 3, function(s, w) { helpers.createAndJoinWallet(2, 3, function(s, w) {
server = s; server = s;
wallet = w; wallet = w;
server.createAddress({}, function(err, address) { helpers.stubUtxos(server, wallet, _.range(1, 9), function() {
helpers.stubUtxos(server, wallet, _.range(1, 9), function() { var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, null, TestData.copayers[0].privKey);
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, null, TestData.copayers[0].privKey); server.createTx(txOpts, function(err, tx) {
server.createTx(txOpts, function(err, tx) { should.not.exist(err);
should.not.exist(err); should.exist(tx);
tx.should.exist; txid = tx.id;
txid = tx.id; done();
done();
});
}); });
}); });
}); });
@ -926,15 +939,13 @@ describe('Copay server', function() {
helpers.createAndJoinWallet(2, 3, function(s, w) { helpers.createAndJoinWallet(2, 3, function(s, w) {
server = s; server = s;
wallet = w; wallet = w;
server.createAddress({}, function(err, address) { helpers.stubUtxos(server, wallet, _.range(1, 9), function() {
helpers.stubUtxos(server, wallet, _.range(1, 9), function() { var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, null, TestData.copayers[0].privKey);
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, null, TestData.copayers[0].privKey); server.createTx(txOpts, function(err, tx) {
server.createTx(txOpts, function(err, tx) { should.not.exist(err);
should.not.exist(err); should.exist(tx);
should.exist(tx); txid = tx.id;
txid = tx.id; done();
done();
});
}); });
}); });
}); });
@ -1084,10 +1095,8 @@ describe('Copay server', function() {
helpers.createAndJoinWallet(1, 1, function(s, w) { helpers.createAndJoinWallet(1, 1, function(s, w) {
server = s; server = s;
wallet = w; wallet = w;
server.createAddress({}, function(err, address) { helpers.stubUtxos(server, wallet, _.range(1, 9), function() {
helpers.stubUtxos(server, wallet, _.range(1, 9), function() { done();
done();
});
}); });
}); });
}); });
@ -1163,11 +1172,9 @@ describe('Copay server', function() {
helpers.createAndJoinWallet(2, 3, function(s, w) { helpers.createAndJoinWallet(2, 3, function(s, w) {
server = s; server = s;
wallet = w; wallet = w;
server.createAddress({}, function(err, address) { helpers.stubUtxos(server, wallet, _.range(1, 9), function() {
helpers.stubUtxos(server, wallet, _.range(1, 9), function() { helpers.stubBroadcast('999');
helpers.stubBroadcast('999'); done();
done();
});
}); });
}); });
}); });
@ -1363,17 +1370,15 @@ describe('Copay server', function() {
helpers.createAndJoinWallet(1, 1, function(s, w) { helpers.createAndJoinWallet(1, 1, function(s, w) {
server = s; server = s;
wallet = w; wallet = w;
server.createAddress({}, function(err, address) { helpers.stubUtxos(server, wallet, _.range(10), function() {
helpers.stubUtxos(server, wallet, _.range(10), function() { var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.1, null, TestData.copayers[0].privKey);
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.1, null, TestData.copayers[0].privKey); async.eachSeries(_.range(10), function(i, next) {
async.eachSeries(_.range(10), function(i, next) { clock.tick(10000);
clock.tick(10000); server.createTx(txOpts, function(err, tx) {
server.createTx(txOpts, function(err, tx) { next();
next();
});
}, function(err) {
return done(err);
}); });
}, function(err) {
return done(err);
}); });
}); });
}); });
@ -1449,17 +1454,15 @@ describe('Copay server', function() {
helpers.createAndJoinWallet(1, 1, function(s, w) { helpers.createAndJoinWallet(1, 1, function(s, w) {
server = s; server = s;
wallet = w; wallet = w;
server.createAddress({}, function(err, address) { helpers.stubUtxos(server, wallet, helpers.toSatoshi(_.range(4)), function() {
helpers.stubUtxos(server, wallet, helpers.toSatoshi(_.range(4)), function() { var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.01, null, TestData.copayers[0].privKey);
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.01, null, TestData.copayers[0].privKey); async.eachSeries(_.range(3), function(i, next) {
async.eachSeries(_.range(3), function(i, next) { server.createTx(txOpts, function(err, tx) {
server.createTx(txOpts, function(err, tx) { should.not.exist(err);
should.not.exist(err); next();
next();
});
}, function(err) {
return done(err);
}); });
}, function(err) {
return done(err);
}); });
}); });
}); });
@ -1583,18 +1586,16 @@ describe('Copay server', function() {
server = s; server = s;
wallet = w; wallet = w;
server.createAddress({}, function(err, address) { helpers.stubUtxos(server, wallet, _.range(2), function() {
helpers.stubUtxos(server, wallet, _.range(2), function() { var txOpts = {
var txOpts = { toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: helpers.toSatoshi(0.1),
amount: helpers.toSatoshi(0.1), };
}; async.eachSeries(_.range(2), function(i, next) {
async.eachSeries(_.range(2), function(i, next) { server.createTx(txOpts, function(err, tx) {
server.createTx(txOpts, function(err, tx) { next();
next(); });
}); }, done);
}, done);
});
}); });
}); });
}); });
@ -1632,27 +1633,25 @@ describe('Copay server', function() {
server = s; server = s;
wallet = w; wallet = w;
server.createAddress({}, function(err, address) { helpers.stubUtxos(server, wallet, _.range(2), function() {
helpers.stubUtxos(server, wallet, _.range(2), function() { var txOpts = {
var txOpts = { toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: helpers.toSatoshi(0.1),
amount: helpers.toSatoshi(0.1), };
}; async.eachSeries(_.range(2), function(i, next) {
async.eachSeries(_.range(2), function(i, next) { server.createTx(txOpts, function(err, tx) {
server.createTx(txOpts, function(err, tx) { next();
next(); });
}); }, function() {
}, function() { server.removeWallet({}, function(err) {
server.removeWallet({}, function(err) { db = [];
db = []; server.storage._dump(function() {
server.storage._dump(function() { var after = _.clone(db);
var after = _.clone(db); after.should.deep.equal(before);
after.should.deep.equal(before); done();
done(); }, cat);
}, cat); });
}); }, cat);
}, cat);
});
}); });
}); });
}, cat); }, cat);
@ -1666,14 +1665,12 @@ describe('Copay server', function() {
helpers.createAndJoinWallet(2, 3, function(s, w) { helpers.createAndJoinWallet(2, 3, function(s, w) {
server = s; server = s;
wallet = w; wallet = w;
server.createAddress({}, function(err, address) { helpers.stubUtxos(server, wallet, [100, 200], function() {
helpers.stubUtxos(server, wallet, [100, 200], function() { var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, 'some message', TestData.copayers[0].privKey);
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, 'some message', TestData.copayers[0].privKey); server.createTx(txOpts, function(err, tx) {
server.createTx(txOpts, function(err, tx) { server.getPendingTxs({}, function(err, txs) {
server.getPendingTxs({}, function(err, txs) { txp = txs[0];
txp = txs[0]; done();
done();
});
}); });
}); });
}); });
@ -1742,4 +1739,169 @@ describe('Copay server', function() {
}); });
}); });
}); });
describe('#getTxHistory', function() {
var server, wallet, mainAddresses, changeAddresses;
beforeEach(function(done) {
helpers.createAndJoinWallet(1, 1, function(s, w) {
server = s;
wallet = w;
helpers.createAddresses(server, wallet, 1, 1, function(main, change) {
mainAddresses = main;
changeAddresses = change;
done();
});
});
});
it('should get tx history from insight', function(done) {
helpers.stubHistory(TestData.history);
server.getTxHistory({}, function(err, txs) {
should.not.exist(err);
should.exist(txs);
txs.length.should.equal(2);
done();
});
});
it('should get tx history for incoming txs', function(done) {
server._normalizeTxHistory = sinon.stub().returnsArg(0);
var txs = [{
txid: '1',
confirmations: 1,
fees: 100,
minedTs: 1,
inputs: [{
address: 'external',
amount: 500,
}],
outputs: [{
address: mainAddresses[0].address,
amount: 200,
}],
}];
helpers.stubHistory(txs);
server.getTxHistory({}, function(err, txs) {
should.not.exist(err);
should.exist(txs);
txs.length.should.equal(1);
var tx = txs[0];
tx.action.should.equal('received');
tx.amount.should.equal(200);
tx.fees.should.equal(100);
done();
});
});
it('should get tx history for outgoing txs', function(done) {
server._normalizeTxHistory = sinon.stub().returnsArg(0);
var txs = [{
txid: '1',
confirmations: 1,
fees: 100,
minedTs: 1,
inputs: [{
address: mainAddresses[0].address,
amount: 500,
}],
outputs: [{
address: 'external',
amount: 400,
}],
}];
helpers.stubHistory(txs);
server.getTxHistory({}, function(err, txs) {
should.not.exist(err);
should.exist(txs);
txs.length.should.equal(1);
var tx = txs[0];
tx.action.should.equal('sent');
tx.amount.should.equal(400);
tx.fees.should.equal(100);
done();
});
});
it('should get tx history for outgoing txs + change', function(done) {
server._normalizeTxHistory = sinon.stub().returnsArg(0);
var txs = [{
txid: '1',
confirmations: 1,
fees: 100,
minedTs: 1,
inputs: [{
address: mainAddresses[0].address,
amount: 500,
}],
outputs: [{
address: 'external',
amount: 300,
}, {
address: changeAddresses[0].address,
amount: 100,
}],
}];
helpers.stubHistory(txs);
server.getTxHistory({}, function(err, txs) {
should.not.exist(err);
should.exist(txs);
txs.length.should.equal(1);
var tx = txs[0];
tx.action.should.equal('sent');
tx.amount.should.equal(300);
tx.fees.should.equal(100);
done();
});
});
it('should get tx history with accepted proposal', function(done) {
server._normalizeTxHistory = sinon.stub().returnsArg(0);
helpers.stubUtxos(server, wallet, [100, 200], function(utxos) {
var txOpts = helpers.createProposalOpts(mainAddresses[0].address, 80, 'some message', TestData.copayers[0].privKey);
server.createTx(txOpts, function(err, tx) {
should.not.exist(err);
should.exist(tx);
helpers.stubBroadcast('1122334455');
var signatures = helpers.clientSign(tx, TestData.copayers[0].xPrivKey);
server.signTx({
txProposalId: tx.id,
signatures: signatures,
}, function(err, tx) {
should.not.exist(err);
var txs = [{
txid: '1122334455',
confirmations: 1,
fees: 5460,
minedTs: 1,
inputs: [{
address: tx.inputs[0].address,
amount: utxos[0].satoshis,
}],
outputs: [{
address: 'external',
amount: helpers.toSatoshi(80) - 5460,
}, {
address: changeAddresses[0].address,
amount: helpers.toSatoshi(20) - 5460,
}],
}];
helpers.stubHistory(txs);
server.getTxHistory({}, function(err, txs) {
should.not.exist(err);
should.exist(txs);
txs.length.should.equal(1);
var tx = txs[0];
tx.action.should.equal('sent');
tx.amount.should.equal(helpers.toSatoshi(80));
tx.message.should.equal('some message');
tx.actions.length.should.equal(1);
tx.actions[0].type.should.equal('accept');
tx.actions[0].copayerName.should.equal('copayer 1');
done();
});
});
});
});
});
});
}); });

76
test/testdata.js

@ -62,6 +62,82 @@ var copayers = [{
}, ]; }, ];
var history = [{
txid: "0279ef7b21630f859deb723e28beac9e7011660bd1346c2da40321d2f7e34f04",
vin: [{
txid: "c8e221141e8bb60977896561b77fa59d6dacfcc10db82bf6f5f923048b11c70d",
vout: 0,
n: 0,
addr: "2N6Zutg26LEC4iYVxi7SHhopVLP1iZPU1rZ",
valueSat: 485645,
value: 0.00485645,
}, {
txid: "6e599eea3e2898b91087eb87e041c5d8dec5362447a8efba185ed593f6dc64c0",
vout: 1,
n: 1,
addr: "2MyqmcWjmVxW8i39wdk1CVPdEqKyFSY9H1S",
valueSat: 885590,
value: 0.0088559,
}],
vout: [{
value: "0.00045753",
n: 0,
scriptPubKey: {
addresses: [
"2NAVFnsHqy5JvqDJydbHPx393LFqFFBQ89V"
]
},
}, {
value: "0.01300000",
n: 1,
scriptPubKey: {
addresses: [
"mq4D3Va5mYHohMEHrgHNGzCjKhBKvuEhPE"
]
}
}],
confirmations: 2,
time: 1424471041,
blocktime: 1424471041,
valueOut: 0.01345753,
valueIn: 0.01371235,
fees: 0.00025482
}, {
txid: "fad88682ccd2ff34cac6f7355fe9ecd8addd9ef167e3788455972010e0d9d0de",
vin: [{
txid: "0279ef7b21630f859deb723e28beac9e7011660bd1346c2da40321d2f7e34f04",
vout: 0,
n: 0,
addr: "2NAVFnsHqy5JvqDJydbHPx393LFqFFBQ89V",
valueSat: 45753,
value: 0.00045753,
}],
vout: [{
value: "0.00011454",
n: 0,
scriptPubKey: {
addresses: [
"2N7GT7XaN637eBFMmeczton2aZz5rfRdZso"
]
}
}, {
value: "0.00020000",
n: 1,
scriptPubKey: {
addresses: [
"mq4D3Va5mYHohMEHrgHNGzCjKhBKvuEhPE"
]
}
}],
confirmations: 1,
time: 1424472242,
blocktime: 1424472242,
valueOut: 0.00031454,
valueIn: 0.00045753,
fees: 0.00014299
}];
module.exports.keyPair = keyPair; module.exports.keyPair = keyPair;
module.exports.message = message; module.exports.message = message;
module.exports.copayers = copayers; module.exports.copayers = copayers;
module.exports.history = history;

Loading…
Cancel
Save