commit
df84843a96
|
@ -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');
|
||||||
|
|
||||||
|
@ -64,7 +65,7 @@ TxProposal.prototype._updateStatus = function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
TxProposal.prototype._getBitcoreTx = function(n) {
|
TxProposal.prototype._getBitcoreTx = function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
var t = new Bitcore.Transaction();
|
var t = new Bitcore.Transaction();
|
||||||
|
@ -79,6 +80,16 @@ TxProposal.prototype._getBitcoreTx = function(n) {
|
||||||
return t;
|
return t;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
TxProposal.prototype.getNetworkName = function() {
|
||||||
|
return Bitcore.Address(this.toAddress).toObject().networkName;
|
||||||
|
};
|
||||||
|
|
||||||
|
TxProposal.prototype.getRawTx = function() {
|
||||||
|
var t = this._getBitcoreTx();
|
||||||
|
|
||||||
|
return t.serialize();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
TxProposal.prototype.addAction = function(copayerId, type, signatures) {
|
TxProposal.prototype.addAction = function(copayerId, type, signatures) {
|
||||||
var action = new TxProposalAction({
|
var action = new TxProposalAction({
|
||||||
|
@ -120,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;
|
||||||
|
|
|
@ -96,6 +96,10 @@ Wallet.prototype.getCopayer = function(copayerId) {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Wallet.prototype.getNetworkName = function() {
|
||||||
|
return this.isTestnet ? 'testnet' : 'livenet';
|
||||||
|
};
|
||||||
|
|
||||||
Wallet.prototype._getBitcoreNetwork = function() {
|
Wallet.prototype._getBitcoreNetwork = function() {
|
||||||
return this.isTestnet ? Bitcore.Networks.testnet : Bitcore.Networks.livenet;
|
return this.isTestnet ? Bitcore.Networks.testnet : Bitcore.Networks.livenet;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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');
|
||||||
|
@ -263,9 +264,15 @@ CopayServer.prototype._getBlockExplorer = function(provider, network) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* _getUtxos
|
||||||
|
*
|
||||||
|
* @param opts.walletId
|
||||||
|
*/
|
||||||
CopayServer.prototype._getUtxos = function(opts, cb) {
|
CopayServer.prototype._getUtxos = function(opts, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
|
||||||
// Get addresses for this wallet
|
// Get addresses for this wallet
|
||||||
self.storage.fetchAddresses(opts.walletId, function(err, addresses) {
|
self.storage.fetchAddresses(opts.walletId, function(err, addresses) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
@ -273,8 +280,9 @@ CopayServer.prototype._getUtxos = function(opts, cb) {
|
||||||
|
|
||||||
var addressStrs = _.pluck(addresses, 'address');
|
var addressStrs = _.pluck(addresses, 'address');
|
||||||
var addressToPath = _.indexBy(addresses, 'address'); // TODO : check performance
|
var addressToPath = _.indexBy(addresses, 'address'); // TODO : check performance
|
||||||
|
var networkName = Bitcore.Address(addressStrs[0]).toObject().networkName;
|
||||||
|
|
||||||
var bc = self._getBlockExplorer('insight', opts.network);
|
var bc = self._getBlockExplorer('insight', networkName);
|
||||||
bc.getUnspentUtxos(addressStrs, function(err, utxos) {
|
bc.getUnspentUtxos(addressStrs, function(err, utxos) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
|
||||||
|
@ -452,11 +460,12 @@ CopayServer.prototype.getTx = function(opts, cb) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
CopayServer.prototype._broadcastTx = function(rawTx, cb) {
|
CopayServer.prototype._broadcastTx = function(txp, cb) {
|
||||||
// TODO: this should attempt to broadcast _all_ accepted and not-yet broadcasted (status=='accepted') txps?
|
var raw = txp.getRawTx();
|
||||||
cb = cb || function() {};
|
var bc = this._getBlockExplorer('insight', txp.getNetworkName());
|
||||||
|
bc.broadcast(raw, function(err, txid) {
|
||||||
throw 'not implemented';
|
return cb(err, txid);
|
||||||
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -502,10 +511,10 @@ CopayServer.prototype.signTx = function(opts, cb) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
|
||||||
if (txp.status == 'accepted') {
|
if (txp.status == 'accepted') {
|
||||||
self._broadcastTx(txp.rawTx, function(err, txid) {
|
self._broadcastTx(txp, 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 = [],
|
||||||
|
@ -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