add tests to broadcast

This commit is contained in:
Matias Alejo Garcia 2015-02-06 15:15:54 -03:00
parent dba306045c
commit 1200f2b2f0
3 changed files with 175 additions and 57 deletions

View File

@ -3,6 +3,7 @@
var _ = require('lodash');
var Guid = require('guid');
var Bitcore = require('bitcore');
var Address = Bitcore.Address;
var TxProposalAction = require('./txproposalaction');
@ -79,6 +80,9 @@ TxProposal.prototype._getBitcoreTx = function() {
return t;
};
TxProposal.prototype.getNetworkName = function() {
return Bitcore.Address(this.toAddress).toObject().networkName;
};
TxProposal.prototype.getRawTx = function() {
var t = this._getBitcoreTx();
@ -127,7 +131,7 @@ TxProposal.prototype.checkSignatures = function(signatures, xpub) {
oks++;
} catch (e) {
// TODO only for debug now
console.log('DEBUG ONLY:',e.message); //TODO
console.log('DEBUG ONLY:', e.message); //TODO
};
});
return oks === t.inputs.length;

View File

@ -11,6 +11,7 @@ var events = require('events');
var Bitcore = require('bitcore');
var PublicKey = Bitcore.PublicKey;
var HDPublicKey = Bitcore.HDPublicKey;
var Address = Bitcore.Address;
var Explorers = require('bitcore-explorers');
var ClientError = require('./clienterror');
@ -272,53 +273,50 @@ CopayServer.prototype._getUtxos = function(opts, cb) {
var self = this;
self.storage.fetchWallet(opts.walletId, function(err, wallet) {
// Get addresses for this wallet
self.storage.fetchAddresses(opts.walletId, function(err, addresses) {
if (err) return cb(err);
if (addresses.length == 0) return cb(new ClientError('The wallet has no addresses'));
// Get addresses for this wallet
self.storage.fetchAddresses(opts.walletId, function(err, addresses) {
var addressStrs = _.pluck(addresses, 'address');
var addressToPath = _.indexBy(addresses, 'address'); // TODO : check performance
var networkName = Bitcore.Address(addressStrs[0]).toObject().networkName;
var bc = self._getBlockExplorer('insight', networkName);
bc.getUnspentUtxos(addressStrs, function(err, utxos) {
if (err) return cb(err);
if (addresses.length == 0) return cb(new ClientError('The wallet has no addresses'));
var addressStrs = _.pluck(addresses, 'address');
var addressToPath = _.indexBy(addresses, 'address'); // TODO : check performance
var bc = self._getBlockExplorer('insight', wallet.getNetworkName());
bc.getUnspentUtxos(addressStrs, function(err, utxos) {
self.getPendingTxs({
walletId: opts.walletId
}, function(err, txps) {
if (err) return cb(err);
self.getPendingTxs({
walletId: opts.walletId
}, function(err, txps) {
if (err) return cb(err);
var inputs = _.chain(txps)
.pluck('inputs')
.flatten()
.map(function(utxo) {
return utxo.txid + '|' + utxo.vout
})
.value();
var inputs = _.chain(txps)
.pluck('inputs')
.flatten()
.map(function(utxo) {
return utxo.txid + '|' + utxo.vout
})
.value();
var dictionary = _.reduce(utxos, function(memo, utxo) {
memo[utxo.txid + '|' + utxo.vout] = utxo;
return memo;
}, {});
var dictionary = _.reduce(utxos, function(memo, utxo) {
memo[utxo.txid + '|' + utxo.vout] = utxo;
return memo;
}, {});
_.each(inputs, function(input) {
if (dictionary[input]) {
dictionary[input].locked = true;
}
});
// Needed for the clients to sign UTXOs
_.each(utxos, function(utxo) {
utxo.path = addressToPath[utxo.address].path;
utxo.publicKeys = addressToPath[utxo.address].publicKeys;
});
return cb(null, utxos);
_.each(inputs, function(input) {
if (dictionary[input]) {
dictionary[input].locked = true;
}
});
// Needed for the clients to sign UTXOs
_.each(utxos, function(utxo) {
utxo.path = addressToPath[utxo.address].path;
utxo.publicKeys = addressToPath[utxo.address].publicKeys;
});
return cb(null, utxos);
});
});
});
@ -464,7 +462,7 @@ CopayServer.prototype.getTx = function(opts, cb) {
CopayServer.prototype._broadcastTx = function(txp, networkName, cb) {
var raw = txp.getRawTx();
var bc = self._getBlockExplorer('insight', networkName);
var bc = this._getBlockExplorer('insight', networkName);
bc.broadcast(raw, function(err, txid) {
return cb(err, txid);
})
@ -514,9 +512,9 @@ CopayServer.prototype.signTx = function(opts, cb) {
if (txp.status == 'accepted') {
self._broadcastTx(txp, wallet.getNetworkName(), function(err, txid) {
if (err) return cb(err);
if (err) return cb(err, txp);
tx.setBroadcasted(txid);
txp.setBroadcasted(txid);
self.storage.storeTx(opts.walletId, txp, function(err) {
if (err) return cb(err);

View File

@ -137,15 +137,27 @@ helpers.createUtxos = function(server, wallet, amounts, cb) {
address: addresses[i++].address,
};
});
var bc = sinon.stub();
bc.getUnspentUtxos = sinon.stub().callsArgWith(1, null, utxos);
server._getBlockExplorer = sinon.stub().returns(bc);
return cb();
return cb(utxos);
});
};
helpers.stubBlockExplorer = function(server, utxos, txid) {
var bc = sinon.stub();
bc.getUnspentUtxos = sinon.stub().callsArgWith(1, null, utxos);
if (txid) {
bc.broadcast = sinon.stub().callsArgWith(1, null, txid);
} else {
bc.broadcast = sinon.stub().callsArgWith(1, 'broadcast error');
}
server._getBlockExplorer = sinon.stub().returns(bc);
};
helpers.clientSign = function(tx, xpriv, n) {
//Derive proper key to sign, for each input
var privs = [],
@ -155,7 +167,7 @@ helpers.clientSign = function(tx, xpriv, n) {
_.each(tx.inputs, function(i) {
if (!derived[i.path]) {
derived[i.path] = xpriv.derive(i.path).privateKey;
}
}
privs.push(derived[i.path]);
});
@ -171,9 +183,9 @@ helpers.clientSign = function(tx, xpriv, n) {
var signatures = [];
_.each(privs, function(p) {
var s = t.getSignatures(p)[0].signature.toDER().toString('hex');
signatures.push(s);
});
var s = t.getSignatures(p)[0].signature.toDER().toString('hex');
signatures.push(s);
});
//
return signatures;
};
@ -777,6 +789,8 @@ describe('Copay server', function() {
helpers.createUtxos(server, wallet, helpers.toSatoshi([100, 200]), function(utxos) {
helpers.stubBlockExplorer(server, utxos);
var txOpts = {
copayerId: '1',
walletId: '123',
@ -812,7 +826,8 @@ describe('Copay server', function() {
it('should fail to create tx when insufficient funds', function(done) {
helpers.createUtxos(server, wallet, helpers.toSatoshi([100]), function() {
helpers.createUtxos(server, wallet, helpers.toSatoshi([100]), function(utxos) {
helpers.stubBlockExplorer(server, utxos);
var txOpts = {
copayerId: '1',
@ -848,6 +863,7 @@ describe('Copay server', function() {
it('should create tx when there is a pending tx and enough UTXOs', function(done) {
helpers.createUtxos(server, wallet, helpers.toSatoshi([10.1, 10.2, 10.3]), function(utxos) {
helpers.stubBlockExplorer(server, utxos);
var txOpts = {
copayerId: '1',
@ -896,6 +912,7 @@ describe('Copay server', function() {
it('should fail to create tx when there is a pending tx and not enough UTXOs', function(done) {
helpers.createUtxos(server, wallet, helpers.toSatoshi([10.1, 10.2, 10.3]), function(utxos) {
helpers.stubBlockExplorer(server, utxos);
var txOpts = {
copayerId: '1',
walletId: '123',
@ -957,6 +974,7 @@ describe('Copay server', function() {
isChange: false,
}, function(err, address) {
helpers.createUtxos(server, wallet, helpers.toSatoshi([1, 2, 3, 4, 5, 6, 7, 8]), function(utxos) {
helpers.stubBlockExplorer(server, utxos);
var txOpts = {
copayerId: '1',
walletId: '123',
@ -977,7 +995,7 @@ describe('Copay server', function() {
});
});
it('should sign a TX with multiple inputs, different paths', function(done) {
it('should sign a TX with multiple inputs, different paths', function(done) {
server.getPendingTxs({
walletId: '123'
}, function(err, txs) {
@ -997,7 +1015,7 @@ describe('Copay server', function() {
});
});
it('should fail if one signature is broken', function(done) {
it('should fail if one signature is broken', function(done) {
server.getPendingTxs({
walletId: '123'
}, function(err, txs) {
@ -1005,7 +1023,7 @@ describe('Copay server', function() {
tx.id.should.equal(txid);
var signatures = helpers.clientSign(tx, someXPrivKey[0], wallet.n);
signatures[0]=1;
signatures[0] = 1;
server.signTx({
walletId: '123',
@ -1018,7 +1036,7 @@ describe('Copay server', function() {
});
});
});
it('should fail on invalids signature', function(done) {
it('should fail on invalids signature', function(done) {
server.getPendingTxs({
walletId: '123'
}, function(err, txs) {
@ -1040,4 +1058,102 @@ describe('Copay server', function() {
});
describe('#signTx and broadcast', function() {
var wallet, utxos;
beforeEach(function(done) {
server = new CopayServer({
storage: storage,
});
helpers.createAndJoinWallet('123', 1, 1, function(err, w) {
wallet = w;
server.createAddress({
walletId: '123',
isChange: false,
}, function(err, address) {
helpers.createUtxos(server, wallet, helpers.toSatoshi([1, 2, 3, 4, 5, 6, 7, 8]), function(inutxos) {
utxos = inutxos;
done();
});
});
});
});
it('should sign and broadcast a tx', function(done) {
helpers.stubBlockExplorer(server, utxos, '1122334455');
var txOpts = {
copayerId: '1',
walletId: '123',
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
amount: helpers.toSatoshi(10),
message: 'some message',
otToken: 'dummy',
requestSignature: 'dummy',
};
server.createTx(txOpts, function(err, txp) {
should.not.exist(err);
txp.should.exist;
var txpid = txp.id;
server.getPendingTxs({
walletId: '123'
}, function(err, txps) {
var txp = txps[0];
txp.id.should.equal(txpid);
var signatures = helpers.clientSign(txp, someXPrivKey[0], wallet.n);
server.signTx({
walletId: '123',
copayerId: '1',
txProposalId: txpid,
signatures: signatures,
}, function(err, txp) {
should.not.exist(err);
txp.status.should.equal('broadcasted');
txp.txid.should.equal('1122334455');
done();
});
});
});
});
it('should keep tx as *accepted* if unable to broadcast it', function(done) {
helpers.stubBlockExplorer(server, utxos);
var txOpts = {
copayerId: '1',
walletId: '123',
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
amount: helpers.toSatoshi(10),
message: 'some message',
otToken: 'dummy',
requestSignature: 'dummy',
};
server.createTx(txOpts, function(err, txp) {
should.not.exist(err);
txp.should.exist;
var txpid = txp.id;
server.getPendingTxs({
walletId: '123'
}, function(err, txps) {
var txp = txps[0];
txp.id.should.equal(txpid);
var signatures = helpers.clientSign(txp, someXPrivKey[0], wallet.n);
server.signTx({
walletId: '123',
copayerId: '1',
txProposalId: txpid,
signatures: signatures,
}, function(err, txp) {
err.should.contain('broadcast');
txp.status.should.equal('accepted');
should.not.exist(txp.txid);
done();
});
});
});
});
});
});