add tests to broadcast
This commit is contained in:
parent
dba306045c
commit
1200f2b2f0
|
@ -3,6 +3,7 @@
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
var Guid = require('guid');
|
var Guid = require('guid');
|
||||||
var Bitcore = require('bitcore');
|
var Bitcore = require('bitcore');
|
||||||
|
var Address = Bitcore.Address;
|
||||||
|
|
||||||
var TxProposalAction = require('./txproposalaction');
|
var TxProposalAction = require('./txproposalaction');
|
||||||
|
|
||||||
|
@ -79,6 +80,9 @@ TxProposal.prototype._getBitcoreTx = function() {
|
||||||
return t;
|
return t;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
TxProposal.prototype.getNetworkName = function() {
|
||||||
|
return Bitcore.Address(this.toAddress).toObject().networkName;
|
||||||
|
};
|
||||||
|
|
||||||
TxProposal.prototype.getRawTx = function() {
|
TxProposal.prototype.getRawTx = function() {
|
||||||
var t = this._getBitcoreTx();
|
var t = this._getBitcoreTx();
|
||||||
|
@ -127,7 +131,7 @@ TxProposal.prototype.checkSignatures = function(signatures, xpub) {
|
||||||
oks++;
|
oks++;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// TODO only for debug now
|
// TODO only for debug now
|
||||||
console.log('DEBUG ONLY:',e.message); //TODO
|
console.log('DEBUG ONLY:', e.message); //TODO
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
return oks === t.inputs.length;
|
return oks === t.inputs.length;
|
||||||
|
|
|
@ -11,6 +11,7 @@ var events = require('events');
|
||||||
var Bitcore = require('bitcore');
|
var Bitcore = require('bitcore');
|
||||||
var PublicKey = Bitcore.PublicKey;
|
var PublicKey = Bitcore.PublicKey;
|
||||||
var HDPublicKey = Bitcore.HDPublicKey;
|
var HDPublicKey = Bitcore.HDPublicKey;
|
||||||
|
var Address = Bitcore.Address;
|
||||||
var Explorers = require('bitcore-explorers');
|
var Explorers = require('bitcore-explorers');
|
||||||
|
|
||||||
var ClientError = require('./clienterror');
|
var ClientError = require('./clienterror');
|
||||||
|
@ -272,53 +273,50 @@ CopayServer.prototype._getUtxos = function(opts, cb) {
|
||||||
var self = this;
|
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 (err) return cb(err);
|
||||||
|
if (addresses.length == 0) return cb(new ClientError('The wallet has no addresses'));
|
||||||
|
|
||||||
// Get addresses for this wallet
|
var addressStrs = _.pluck(addresses, 'address');
|
||||||
self.storage.fetchAddresses(opts.walletId, function(err, addresses) {
|
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 (err) return cb(err);
|
||||||
if (addresses.length == 0) return cb(new ClientError('The wallet has no addresses'));
|
|
||||||
|
|
||||||
var addressStrs = _.pluck(addresses, 'address');
|
self.getPendingTxs({
|
||||||
var addressToPath = _.indexBy(addresses, 'address'); // TODO : check performance
|
walletId: opts.walletId
|
||||||
|
}, function(err, txps) {
|
||||||
var bc = self._getBlockExplorer('insight', wallet.getNetworkName());
|
|
||||||
bc.getUnspentUtxos(addressStrs, function(err, utxos) {
|
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
|
||||||
self.getPendingTxs({
|
var inputs = _.chain(txps)
|
||||||
walletId: opts.walletId
|
.pluck('inputs')
|
||||||
}, function(err, txps) {
|
.flatten()
|
||||||
if (err) return cb(err);
|
.map(function(utxo) {
|
||||||
|
return utxo.txid + '|' + utxo.vout
|
||||||
|
})
|
||||||
|
.value();
|
||||||
|
|
||||||
var inputs = _.chain(txps)
|
var dictionary = _.reduce(utxos, function(memo, utxo) {
|
||||||
.pluck('inputs')
|
memo[utxo.txid + '|' + utxo.vout] = utxo;
|
||||||
.flatten()
|
return memo;
|
||||||
.map(function(utxo) {
|
}, {});
|
||||||
return utxo.txid + '|' + utxo.vout
|
|
||||||
})
|
|
||||||
.value();
|
|
||||||
|
|
||||||
var dictionary = _.reduce(utxos, function(memo, utxo) {
|
_.each(inputs, function(input) {
|
||||||
memo[utxo.txid + '|' + utxo.vout] = utxo;
|
if (dictionary[input]) {
|
||||||
return memo;
|
dictionary[input].locked = true;
|
||||||
}, {});
|
}
|
||||||
|
|
||||||
_.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);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 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) {
|
CopayServer.prototype._broadcastTx = function(txp, networkName, cb) {
|
||||||
var raw = txp.getRawTx();
|
var raw = txp.getRawTx();
|
||||||
var bc = self._getBlockExplorer('insight', networkName);
|
var bc = this._getBlockExplorer('insight', networkName);
|
||||||
bc.broadcast(raw, function(err, txid) {
|
bc.broadcast(raw, function(err, txid) {
|
||||||
return cb(err, txid);
|
return cb(err, txid);
|
||||||
})
|
})
|
||||||
|
@ -514,9 +512,9 @@ CopayServer.prototype.signTx = function(opts, cb) {
|
||||||
|
|
||||||
if (txp.status == 'accepted') {
|
if (txp.status == 'accepted') {
|
||||||
self._broadcastTx(txp, wallet.getNetworkName(), function(err, txid) {
|
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) {
|
self.storage.storeTx(opts.walletId, txp, function(err) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
|
||||||
|
|
|
@ -137,15 +137,27 @@ helpers.createUtxos = function(server, wallet, amounts, cb) {
|
||||||
address: addresses[i++].address,
|
address: addresses[i++].address,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
return cb(utxos);
|
||||||
var bc = sinon.stub();
|
|
||||||
bc.getUnspentUtxos = sinon.stub().callsArgWith(1, null, utxos);
|
|
||||||
server._getBlockExplorer = sinon.stub().returns(bc);
|
|
||||||
|
|
||||||
return cb();
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
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) {
|
helpers.clientSign = function(tx, xpriv, n) {
|
||||||
//Derive proper key to sign, for each input
|
//Derive proper key to sign, for each input
|
||||||
var privs = [],
|
var privs = [],
|
||||||
|
@ -155,7 +167,7 @@ helpers.clientSign = function(tx, xpriv, n) {
|
||||||
_.each(tx.inputs, function(i) {
|
_.each(tx.inputs, function(i) {
|
||||||
if (!derived[i.path]) {
|
if (!derived[i.path]) {
|
||||||
derived[i.path] = xpriv.derive(i.path).privateKey;
|
derived[i.path] = xpriv.derive(i.path).privateKey;
|
||||||
}
|
}
|
||||||
privs.push(derived[i.path]);
|
privs.push(derived[i.path]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -171,9 +183,9 @@ helpers.clientSign = function(tx, xpriv, n) {
|
||||||
|
|
||||||
var signatures = [];
|
var signatures = [];
|
||||||
_.each(privs, function(p) {
|
_.each(privs, function(p) {
|
||||||
var s = t.getSignatures(p)[0].signature.toDER().toString('hex');
|
var s = t.getSignatures(p)[0].signature.toDER().toString('hex');
|
||||||
signatures.push(s);
|
signatures.push(s);
|
||||||
});
|
});
|
||||||
//
|
//
|
||||||
return signatures;
|
return signatures;
|
||||||
};
|
};
|
||||||
|
@ -777,6 +789,8 @@ describe('Copay server', function() {
|
||||||
|
|
||||||
helpers.createUtxos(server, wallet, helpers.toSatoshi([100, 200]), function(utxos) {
|
helpers.createUtxos(server, wallet, helpers.toSatoshi([100, 200]), function(utxos) {
|
||||||
|
|
||||||
|
helpers.stubBlockExplorer(server, utxos);
|
||||||
|
|
||||||
var txOpts = {
|
var txOpts = {
|
||||||
copayerId: '1',
|
copayerId: '1',
|
||||||
walletId: '123',
|
walletId: '123',
|
||||||
|
@ -812,7 +826,8 @@ describe('Copay server', function() {
|
||||||
|
|
||||||
it('should fail to create tx when insufficient funds', function(done) {
|
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 = {
|
var txOpts = {
|
||||||
copayerId: '1',
|
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) {
|
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.createUtxos(server, wallet, helpers.toSatoshi([10.1, 10.2, 10.3]), function(utxos) {
|
||||||
|
helpers.stubBlockExplorer(server, utxos);
|
||||||
|
|
||||||
var txOpts = {
|
var txOpts = {
|
||||||
copayerId: '1',
|
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) {
|
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.createUtxos(server, wallet, helpers.toSatoshi([10.1, 10.2, 10.3]), function(utxos) {
|
||||||
|
|
||||||
|
helpers.stubBlockExplorer(server, utxos);
|
||||||
var txOpts = {
|
var txOpts = {
|
||||||
copayerId: '1',
|
copayerId: '1',
|
||||||
walletId: '123',
|
walletId: '123',
|
||||||
|
@ -957,6 +974,7 @@ describe('Copay server', function() {
|
||||||
isChange: false,
|
isChange: false,
|
||||||
}, function(err, address) {
|
}, function(err, address) {
|
||||||
helpers.createUtxos(server, wallet, helpers.toSatoshi([1, 2, 3, 4, 5, 6, 7, 8]), function(utxos) {
|
helpers.createUtxos(server, wallet, helpers.toSatoshi([1, 2, 3, 4, 5, 6, 7, 8]), function(utxos) {
|
||||||
|
helpers.stubBlockExplorer(server, utxos);
|
||||||
var txOpts = {
|
var txOpts = {
|
||||||
copayerId: '1',
|
copayerId: '1',
|
||||||
walletId: '123',
|
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({
|
server.getPendingTxs({
|
||||||
walletId: '123'
|
walletId: '123'
|
||||||
}, function(err, txs) {
|
}, 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({
|
server.getPendingTxs({
|
||||||
walletId: '123'
|
walletId: '123'
|
||||||
}, function(err, txs) {
|
}, function(err, txs) {
|
||||||
|
@ -1005,7 +1023,7 @@ describe('Copay server', function() {
|
||||||
tx.id.should.equal(txid);
|
tx.id.should.equal(txid);
|
||||||
|
|
||||||
var signatures = helpers.clientSign(tx, someXPrivKey[0], wallet.n);
|
var signatures = helpers.clientSign(tx, someXPrivKey[0], wallet.n);
|
||||||
signatures[0]=1;
|
signatures[0] = 1;
|
||||||
|
|
||||||
server.signTx({
|
server.signTx({
|
||||||
walletId: '123',
|
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({
|
server.getPendingTxs({
|
||||||
walletId: '123'
|
walletId: '123'
|
||||||
}, function(err, txs) {
|
}, 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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue