updates from eordano comments. Better jsdocs some more tests. Still WIP

This commit is contained in:
Matias Alejo Garcia 2014-11-24 11:46:51 -03:00
parent 6462968d5e
commit abd19e5a96
5 changed files with 292 additions and 189 deletions

View File

@ -20,6 +20,3 @@ module.exports.Compatibility = require('./js/models/Compatibility');
module.exports.PluginManager = require('./js/models/PluginManager'); module.exports.PluginManager = require('./js/models/PluginManager');
module.exports.version = require('./version').version; module.exports.version = require('./version').version;
module.exports.commitHash = require('./version').commitHash; module.exports.commitHash = require('./version').commitHash;
// test hack :s, will fix
module.exports.FakePayProServer = require('./test/mocks/FakePayProServer');

View File

@ -260,7 +260,7 @@ Network.prototype._setupConnectionHandlers = function(opts, cb) {
if (fromTs) { if (fromTs) {
self.ignoreMessageFromTs = fromTs; self.ignoreMessageFromTs = fromTs;
} }
log.info('Async: syncing from: ', fromTs); log.info('Async: synchronizing from: ', fromTs);
self.socket.emit('sync', fromTs); self.socket.emit('sync', fromTs);
self.started = true; self.started = true;
}); });

View File

@ -36,8 +36,7 @@ var TX_MAX_INS = 70;
/** /**
* @desc * @desc
* Wallet manages a private key for Copay, network, storage of the wallet for * Wallet manages a private key for Copay, network and blockchain information.
* persistance, and blockchain information.
* *
* @TODO: Split this leviathan. * @TODO: Split this leviathan.
* *
@ -133,13 +132,13 @@ Wallet.prototype.emitAndKeepAlive = function(args) {
}; };
/** /**
* @TODO: Document this. Its usage is kind of weird * @desc Fixed & Forced TransactionBuilder options, for genereration transactions.
* *
* @static * @static
* @property lockTime * @property lockTime null
* @property signhash * @property signhash SIGHASH
* @property fee * @property fee null (automatic)
* @property feeSat * @property feeSat null
*/ */
Wallet.builderOpts = { Wallet.builderOpts = {
lockTime: null, lockTime: null,
@ -468,6 +467,15 @@ Wallet.prototype._checkIfTxProposalIsSent = function(ntxid, cb) {
}; };
/**
* _processTxProposalPayPro
*
* @desc Process and incoming PayPro TX Proposal. Fetchs the payment request
* from the merchant.
*
* @param mergeInfo Proposals merge information, as returned by TxProposals.merge
* @return {fetchPaymentRequestCallback}
*/
Wallet.prototype._processTxProposalPayPro = function(mergeInfo, cb) { Wallet.prototype._processTxProposalPayPro = function(mergeInfo, cb) {
var self = this; var self = this;
var txp = mergeInfo.txp; var txp = mergeInfo.txp;
@ -494,6 +502,14 @@ Wallet.prototype._processTxProposalPayPro = function(mergeInfo, cb) {
}); });
}; };
/**
* _processIncomingTxProposal
*
* @desc Process an incoming transaction proposal. Runs safety and sanity checks on it.
*
* @param mergeInfo Proposals merge information, as returned by TxProposals.merge
* @return {errCallback}
*/
Wallet.prototype._processIncomingTxProposal = function(mergeInfo, cb) { Wallet.prototype._processIncomingTxProposal = function(mergeInfo, cb) {
if (!mergeInfo) return cb(); if (!mergeInfo) return cb();
var self = this; var self = this;
@ -617,7 +633,8 @@ Wallet.prototype._onAddressBook = function(senderId, data) {
if (!data.addressBook || !_.isObject(data.addressBook)) if (!data.addressBook || !_.isObject(data.addressBook))
return; return;
var self = this, hasChange; var self = this,
hasChange;
_.each(data.addressBook, function(value, key) { _.each(data.addressBook, function(value, key) {
if (!self.addressBook[key] && Address.validate(key)) { if (!self.addressBook[key] && Address.validate(key)) {
@ -629,7 +646,6 @@ Wallet.prototype._onAddressBook = function(senderId, data) {
hasChange = true; hasChange = true;
} }
}); });
console.log('[Wallet.js.635:hasChange:]',hasChange); //TODO
if (hasChange) { if (hasChange) {
this.emitAndKeepAlive('addressBookUpdated'); this.emitAndKeepAlive('addressBookUpdated');
@ -675,7 +691,6 @@ Wallet.prototype._onNoMessages = function() {
* @emits corrupt * @emits corrupt
*/ */
Wallet.prototype._onData = function(senderId, data, ts) { Wallet.prototype._onData = function(senderId, data, ts) {
console.log('[Wallet.js.533]0', this.txProposals.txps); //TODO
preconditions.checkArgument(senderId); preconditions.checkArgument(senderId);
preconditions.checkArgument(data); preconditions.checkArgument(data);
preconditions.checkArgument(data.type); preconditions.checkArgument(data.type);
@ -1159,13 +1174,13 @@ Wallet.fromObj = function(o, readOpts) {
/** /**
* @desc sendToPeers a message to other peers * @desc sends a message to peers
* @param {string[]} recipients - the pubkey of the recipients of the message * @param {string[]} recipients - the pubkey of the recipients of the message. Null for sending to all peers.
* @param {Object} obj - the data to be sent to them * @param {Object} obj - the data to be sent to them.
* @param {String} obj.type - Type of the message to be send
*/ */
Wallet.prototype._sendToPeers = function(recipients, obj) { Wallet.prototype._sendToPeers = function(recipients, obj) {
if (!this.isShared()) return; if (!this.isShared()) return;
log.info('Wallet:' + this.getName() + ' ### Sending ' + obj.type); log.info('Wallet:' + this.getName() + ' ### Sending ' + obj.type);
log.debug('Sending obj', obj); log.debug('Sending obj', obj);
@ -1173,7 +1188,7 @@ Wallet.prototype._sendToPeers = function(recipients, obj) {
}; };
/** /**
* @desc Send the set of TxProposals to some peers * @desc Send the set of TxProposals to peers
* @param {string[]} recipients - the pubkeys of the recipients * @param {string[]} recipients - the pubkeys of the recipients
*/ */
Wallet.prototype.sendAllTxProposals = function(recipients, sinceTs) { Wallet.prototype.sendAllTxProposals = function(recipients, sinceTs) {
@ -1243,8 +1258,6 @@ Wallet.prototype.sendWalletReady = function(recipients, sinceTs) {
* @param {string[]} [recipients] - the pubkeys of the recipients * @param {string[]} [recipients] - the pubkeys of the recipients
*/ */
Wallet.prototype.sendWalletId = function(recipients) { Wallet.prototype.sendWalletId = function(recipients) {
log.debug('Wallet:' + this.id + ' ### SENDING walletId TO:', recipients || 'All', this.id);
this._sendToPeers(recipients, { this._sendToPeers(recipients, {
type: 'walletId', type: 'walletId',
walletId: this.id, walletId: this.id,
@ -1454,12 +1467,25 @@ Wallet.prototype.reject = function(ntxid) {
}; };
/** /**
* @desc Sign a proposal * @callback signCallback
* @param {Error} error if any
* @param {number} Transaction ID or Transaction Proposal ID
* @param {status} Wallet.TX_* Status:
*
* TX_BROADCASTED
* TX_SIGNED
* TX_PROPOSAL_SENT
*/
/**
* @desc Signs a proposal
* @param {string} ntxid the id of the transaction proposal to sign * @param {string} ntxid the id of the transaction proposal to sign
* @emits txProposalsUpdated * @emits txProposalsUpdated
* @throws {Error} Could not sign proposal * @throws {Error} Could not sign proposal
* @throws {Error} Bad payment request * @throws {Error} Bad payment request
* @return {boolean} true if signing actually incremented the number of signatures * @return {boolean} true if signing actually incremented the number of signatures
* @emits txProposalsUpdated
*/ */
Wallet.prototype.sign = function(ntxid) { Wallet.prototype.sign = function(ntxid) {
preconditions.checkState(!_.isUndefined(this.getMyCopayerId())); preconditions.checkState(!_.isUndefined(this.getMyCopayerId()));
@ -1476,6 +1502,17 @@ Wallet.prototype.sign = function(ntxid) {
}; };
/**
*
* @desc signs and send or broadcast a transaction.
* In m-n wallets,
* if m==1 it will broadcast it to the Bitcoin Network
* if n>1 it will send the proposal to the peers
*
* @param ntxid Transaction Proposal Id
* @param {signCallback} cb
* @throws {Error} Could not sign proposal
*/
Wallet.prototype.signAndSend = function(ntxid, cb) { Wallet.prototype.signAndSend = function(ntxid, cb) {
if (this.sign(ntxid)) { if (this.sign(ntxid)) {
var txp = this.txProposals.get(ntxid); var txp = this.txProposals.get(ntxid);
@ -1483,7 +1520,7 @@ Wallet.prototype.signAndSend = function(ntxid, cb) {
return this.broadcastTx(ntxid, cb); return this.broadcastTx(ntxid, cb);
} else { } else {
this.sendTxProposal(ntxid); this.sendTxProposal(ntxid);
return cb(null, ntxid, Wallet.TX_SIGNED ); return cb(null, ntxid, Wallet.TX_SIGNED);
} }
} else { } else {
return cb(new Error('Could not sign the proposal')); return cb(new Error('Could not sign the proposal'));
@ -1493,9 +1530,8 @@ Wallet.prototype.signAndSend = function(ntxid, cb) {
/** /**
* @desc Broadcasts a transaction to the blockchain * @desc Broadcasts a transaction to the blockchain
* @param {string} ntxid - the transaction proposal id * @param {string} ntxid - the transaction proposal id
* @param {broadcastCallback} cb
* @callback broadcastCallback
* @param {string} txid - the transaction id on the blockchain * @param {string} txid - the transaction id on the blockchain
* @param {signCallback} cb
*/ */
Wallet.prototype.broadcastTx = function(ntxid, cb) { Wallet.prototype.broadcastTx = function(ntxid, cb) {
var self = this; var self = this;
@ -1505,32 +1541,36 @@ Wallet.prototype.broadcastTx = function(ntxid, cb) {
if (!tx.isComplete()) if (!tx.isComplete())
throw new Error('Tx is not complete. Can not broadcast'); throw new Error('Tx is not complete. Can not broadcast');
var serializedTx = tx.serialize();
log.info('Wallet:' + this.id + ' Broadcasting Transaction ntxid:' + ntxid); log.info('Wallet:' + this.id + ' Broadcasting Transaction ntxid:' + ntxid);
var txHex = serializedTx.toString('hex'); var txHex = tx.serialize().toString('hex');
log.debug('\tRaw transaction: ', txHex); log.debug('\tRaw transaction: ', txHex);
this.blockchain.broadcast(txHex, function(err, txid) { this.blockchain.broadcast(txHex, function(err, txid) {
if (err) if (err) {
log.error('Error sending TX:', err); log.error('Error sending TX:' + err);
return cb(err);;
}
if (txid) { if (txid) {
log.debug('Wallet:' + self.getName() + ' broadcasted a TX. BITCOIND txid:', txid); log.debug('Wallet:' + self.getName() + ' broadcasted a TX. BITCOIND txid:', txid);
txp.setSent(txid); txp.setSent(txid);
self.sendTxProposal(ntxid);
self.emitAndKeepAlive('txProposalsUpdated');
// PAYPRO: Payment message is optional, only if payment_url is set // PAYPRO: Payment message is optional, only if payment_url is set
// This is async. and will notify and update txp async. // This is async. and will notify and update txp async.
if (txp.merchant && txp.merchant.pr.pd.payment_url) { if (txp.merchant && txp.merchant.pr.pd.payment_url) {
self.sendPaymentTx(ntxid, serializedTx); var data = this.createPayProPayment(txp);
self.sendPayProPayment(txp, data, function(err, data) {
if (err) return cb(err);
self.onPayProPaymentAck(ntxid, data);
});
} }
self.sendTxProposal(ntxid);
self.emitAndKeepAlive('txProposalsUpdated');
return cb(null, txid, Wallet.TX_BROADCASTED); return cb(null, txid, Wallet.TX_BROADCASTED);
} else { } else {
log.info('Wallet:' + self.getName() + '. Sent failed. Checking if the TX was sent already'); log.info('Wallet:' + self.getName() + '. Sent failed. Checking if the TX was sent already');
self._checkIfTxProposalIsSent(ntxid, cb); self._checkIfTxProposalIsSent(ntxid, cb);
@ -1538,11 +1578,18 @@ Wallet.prototype.broadcastTx = function(ntxid, cb) {
}); });
}; };
/** /**
* @desc Create a Payment Protocol transaction * @callback {fetchPaymentRequestCallback}
* @param {string=} err - an error, if any
* @param {Object} merchantData - object representing the payment request. Add described on BIP70 merchant_data
*/
/**
* @desc Creates a Payment Protocol transaction
* @param {Object|string} options - if it's a string, parse it as the url * @param {Object|string} options - if it's a string, parse it as the url
* @param {string} options.url the url for the transaction * @param {string} options.url the url for the transaction
* @param {Function} cb * @return {fetchPaymentRequestCallback} cb
*/ */
Wallet.prototype.fetchPaymentRequest = function(options, cb) { Wallet.prototype.fetchPaymentRequest = function(options, cb) {
preconditions.checkArgument(_.isObject(options)); preconditions.checkArgument(_.isObject(options));
@ -1578,24 +1625,18 @@ Wallet.prototype.fetchPaymentRequest = function(options, cb) {
}); });
}; };
/*
* addOutputsToMerchantData
*
* NOTE: We use to: set the TX scripts with the payment request scripts:
* but this is a hack around transaction builder, so we dont do it anymore.
* See Readme.md. For now we only support p2scripthash or p2pubkeyhash
merchantData.pr.pd.outputs.forEach(function(output, i) {
var script = {
offset: output.script.offset,
limit: output.script.limit,
buffer: new Buffer(output.script.buffer, 'hex')
};
var s = script.buffer.slice(script.offset, script.limit);
b.tx.outs[i].s = s;
});
*
*/
/**
* _addOutputsToMerchantData
*
* @desc parses merchant_data internal output representation and stores
* the result in merchant_data.outs = [{address: xx, amountSatStr: xx}],
* to be compatible with TransactionBuilder.
*`
* @param merchantData BIP70 merchant_data (from the payment request)
* @throws {Error} PayPro: Unsupported inputs
* @return {undefined}
*/
Wallet.prototype._addOutputsToMerchantData = function(merchantData) { Wallet.prototype._addOutputsToMerchantData = function(merchantData) {
var total = bignum(0); var total = bignum(0);
@ -1737,7 +1778,7 @@ Wallet.prototype.parsePaymentRequest = function(options, rawData) {
/** /**
* _getPayProRefundOutputs * _getPayProRefundOutputs
* Create refund address for PayPro. * Create refund outputs for a PayPro Payment Message
* Uses current transaction's change address. * Uses current transaction's change address.
* *
* @param txp * @param txp
@ -1750,13 +1791,23 @@ Wallet.prototype._getPayProRefundOutputs = function(txp) {
var output = new PayPro.Output(); var output = new PayPro.Output();
var script = pkr.getScriptPubKeyHex(index.changeIndex, true, this.pubkey); var script = pkr.getScriptPubKeyHex(index.changeIndex, true, this.pubkey);
output.set('script',new Buffer(script, 'hex')); output.set('script', new Buffer(script, 'hex'));
output.set('amount', amount); output.set('amount', amount);
return [output]; return [output];
}; };
Wallet.prototype._createPaymentTx = function(txp, txHex) { /**
*
* @desc Creates a Payment Protocol Payment message for the given TX Proposal
* @param txp Transaction Proposal
* @param txHex
* @return {undefined}
*/
Wallet.prototype.createPayProPayment = function(txp) {
var tx = txp.builder.build();
var txBuf = tx.serialize();
var refund_outputs = this._getPayProRefundOutputs(txp); var refund_outputs = this._getPayProRefundOutputs(txp);
@ -1770,7 +1821,7 @@ Wallet.prototype._createPaymentTx = function(txp, txHex) {
pay.set('merchant_data', merchant_data); pay.set('merchant_data', merchant_data);
} }
pay.set('transactions', [txHex]); pay.set('transactions', [txBuf]);
pay.set('refund_to', refund_outputs); pay.set('refund_to', refund_outputs);
// Unused for now // Unused for now
@ -1787,19 +1838,40 @@ Wallet.prototype._createPaymentTx = function(txp, txHex) {
return view; return view;
}; };
/** /**
* @desc Send a payment transaction to a server, complying with BIP70 * onPayProPaymentAck
* *
* @param {string} ntxid - the transaction proposal id * @desc parse and process a Payment Protocol Payment Ack. Updates
* @param {Function} txHex * given TX Proposal with merchant's memo and send it to copayers
* *
* emits paymentACK(server's memo) * @param ntxid ID of the Transaction Proposal
* @param rawData of the Payment Ack
* @emits paymentACK - (merchants's memo)
*/ */
Wallet.prototype.sendPaymentTx = function(ntxid, txHex) { Wallet.prototype.onPayProPaymentAck = function(ntxid, rawData) {
var data = PayPro.PaymentACK.decode(rawData);
var paypro = new PayPro();
var ack = paypro.makePaymentACK(data);
var memo = ack.get('memo');
log.debug('Payment Acknowledged!: %s', memo);
txp.paymentAckMemo = memo;
self.sendTxProposal(ntxid);
self.emitAndKeepAlive('paymentACK', memo);
};
/**
* @desc Send a payment transaction to a merchant, complying with BIP70
* on Acknoledge, updates the TX Proposal with server's memo and send it
* to peers
*
* @param {string} ntxid - the transaction proposal ID for with the
*
*/
Wallet.prototype.sendPayProPayment = function(txp, data, cb) {
var self = this; var self = this;
var txp = this.txProposals.get(ntxid);
var data = this._createPaymentTx(txp, txHex);
log.debug('Sending Payment Message to merchant server'); log.debug('Sending Payment Message to merchant server');
var postInfo = { var postInfo = {
method: 'POST', method: 'POST',
@ -1818,19 +1890,13 @@ Wallet.prototype.sendPaymentTx = function(ntxid, txHex) {
responseType: 'arraybuffer' responseType: 'arraybuffer'
}; };
return this.httpUtil.request(postInfo) this.httpUtil.request(postInfo)
.success(function(rawData) { .success(function(rawData) {
var data = PayPro.PaymentACK.decode(rawData); return cb(null, rawData);
var paypro = new PayPro();
var ack = paypro.makePaymentACK(data);
var memo = ack.get('memo');
log.debug('Payment Acknowledged!: %s', memo);
txp.paymentAckMemo = memo;
self.sendTxProposal(ntxid);
self.emitAndKeepAlive('paymentACK', memo);
}) })
.error(function(data, status) { .error(function(data, status) {
log.error('Sending payment notification: XHR status: ' + status); log.error('Sending payment notification: XHR status: ' + status);
return cb(new Error(status));
}); });
}; };
@ -1899,7 +1965,7 @@ Wallet.prototype.addressIsOwn = function(addrStr) {
/** /**
* Estimate a tx fee in satoshis given its input count * Estimate a tx fee in satoshis given its input count
* only for spending all wallet funds * (only used when spending all wallet funds)
*/ */
Wallet.estimatedFee = function(unspentCount) { Wallet.estimatedFee = function(unspentCount) {
preconditions.checkArgument(_.isNumber(unspentCount)); preconditions.checkArgument(_.isNumber(unspentCount));
@ -2071,6 +2137,7 @@ Wallet.prototype.spend = function(opts, cb) {
var comment = opts.comment; var comment = opts.comment;
var url = opts.url; var url = opts.url;
// PayPro? Fetch payment data and recurse
if (url && !opts.merchantData) { if (url && !opts.merchantData) {
return self.fetchPaymentRequest({ return self.fetchPaymentRequest({
url: url, url: url,
@ -2083,7 +2150,8 @@ Wallet.prototype.spend = function(opts, cb) {
opts.amountSat = parseInt(merchantData.outs[0].amountSatStr); opts.amountSat = parseInt(merchantData.outs[0].amountSatStr);
return self.spend(opts, cb); return self.spend(opts, cb);
}); });
}; }
preconditions.checkArgument(amountSat, 'no amount'); preconditions.checkArgument(amountSat, 'no amount');
preconditions.checkArgument(toAddress, 'no address'); preconditions.checkArgument(toAddress, 'no address');
@ -2213,7 +2281,7 @@ Wallet.prototype._createTxProposal = function(toAddress, amountSat, comment, utx
var tx = b.build(); var tx = b.build();
var myId = this.getMyCopayerId(); var myId = this.getMyCopayerId();
var keys = priv.getForPaths(inputChainPaths); var keys = priv.getForPaths(inputChainPaths);
return new TxProposal({ return new TxProposal({
inputChainPaths: inputChainPaths, inputChainPaths: inputChainPaths,
comment: comment, comment: comment,
builder: b, builder: b,

View File

@ -767,7 +767,10 @@ describe('Wallet model', function() {
'confirmations': 10, 'confirmations': 10,
'confirmationsFromCache': false 'confirmationsFromCache': false
}]; }];
sinon.stub(w, 'sendIndexes');
var addr = w.generateAddress().toString(); var addr = w.generateAddress().toString();
w.sendIndexes.restore();
utxo[0].address = addr; utxo[0].address = addr;
utxo[0].scriptPubKey = (new bitcore.Address(addr)).getScriptPubKey().serialize().toString('hex'); utxo[0].scriptPubKey = (new bitcore.Address(addr)).getScriptPubKey().serialize().toString('hex');
return utxo; return utxo;
@ -775,124 +778,154 @@ describe('Wallet model', function() {
var toAddress = 'mjfAe7YrzFujFf8ub5aUrCaN5GfSABdqjh'; var toAddress = 'mjfAe7YrzFujFf8ub5aUrCaN5GfSABdqjh';
var amountSatStr = '10000'; var amountSatStr = '10000';
it('should create transaction', function(done) { describe('#spend', function() {
var w = cachedCreateW2(); it('should create transaction', function(done) {
var utxo = createUTXO(w); var w = cachedCreateW2();
w.blockchain.fixUnspent(utxo); var utxo = createUTXO(w);
w.spend({ w.blockchain.fixUnspent(utxo);
toAddress: toAddress, w.spend({
amountSat: amountSatStr, toAddress: toAddress,
}, function(err, ntxid) { amountSat: amountSatStr,
ntxid.length.should.equal(64); }, function(err, ntxid) {
done(); ntxid.length.should.equal(64);
});
});
it('should create & sign transaction from received funds', function(done) {
var k2 = new PrivateKey({
networkName: walletConfig.networkName
});
var w = createW2([k2]);
var utxo = createUTXO(w);
w.blockchain.fixUnspent(utxo);
w.spend({
toAddress: toAddress,
amountSat: amountSatStr,
}, function(err, ntxid) {
w.on('txProposalsUpdated', function() {
w.getTxProposals()[0].signedByUs.should.equal(true);
w.getTxProposals()[0].rejectedByUs.should.equal(false);
done(); done();
}); });
w.privateKey = k2;
w.sign(ntxid).should.equal.true;
}); });
});
it('should fail to reject a signed transaction', function() { it('should create & sign transaction from received funds', function(done) {
var w = cachedCreateW2(); var k2 = new PrivateKey({
var utxo = createUTXO(w); networkName: walletConfig.networkName
w.blockchain.fixUnspent(utxo); });
w.spend({
toAddress: toAddress, var w = createW2([k2]);
amountSat: amountSatStr, var utxo = createUTXO(w);
}, function(err, ntxid) { w.blockchain.fixUnspent(utxo);
(function() { w.spend({
toAddress: toAddress,
amountSat: amountSatStr,
}, function(err, ntxid) {
w.on('txProposalsUpdated', function() {
w.getTxProposals()[0].signedByUs.should.equal(true);
w.getTxProposals()[0].rejectedByUs.should.equal(false);
done();
});
w.privateKey = k2;
w.sign(ntxid).should.equal.true;
});
});
it('should fail to reject a signed transaction', function() {
var w = cachedCreateW2();
var utxo = createUTXO(w);
w.blockchain.fixUnspent(utxo);
w.spend({
toAddress: toAddress,
amountSat: amountSatStr,
}, function(err, ntxid) {
(function() {
w.reject(ntxid);
}).should.throw('reject a signed');
});
});
it('should create & reject transaction', function(done) {
var w = cachedCreateW2();
var oldK = w.privateKey;
var utxo = createUTXO(w);
w.blockchain.fixUnspent(utxo);
w.spend({
toAddress: toAddress,
amountSat: amountSatStr,
}, function(err, ntxid) {
var s = sinon.stub(w, 'getMyCopayerId').returns('213');
Object.keys(w.txProposals.get(ntxid).rejectedBy).length.should.equal(0);
w.reject(ntxid); w.reject(ntxid);
}).should.throw('reject a signed'); Object.keys(w.txProposals.get(ntxid).rejectedBy).length.should.equal(1);
w.txProposals.get(ntxid).rejectedBy['213'].should.gt(1);
s.restore();
done();
});
}); });
}); it('should fail to send incomplete transaction', function(done) {
var w = createW2(null, 1);
var utxo = createUTXO(w);
w.blockchain.fixUnspent(utxo);
it('should create & reject transaction', function(done) { // TODO in this test, txp should be created with createTxProposal
var w = cachedCreateW2(); w.spend({
var oldK = w.privateKey; toAddress: toAddress,
var utxo = createUTXO(w); amountSat: amountSatStr,
w.blockchain.fixUnspent(utxo); }, function(err, ntxid) {
w.spend({ var txp = w.txProposals.get(ntxid);
toAddress: toAddress, // Assign fake builder
amountSat: amountSatStr, txp.builder = new Builder();
}, function(err, ntxid) { sinon.stub(txp.builder, 'build').returns({
var s = sinon.stub(w, 'getMyCopayerId').returns('213'); isComplete: function() {
Object.keys(w.txProposals.get(ntxid).rejectedBy).length.should.equal(0); return false;
w.reject(ntxid); }
Object.keys(w.txProposals.get(ntxid).rejectedBy).length.should.equal(1); });
w.txProposals.get(ntxid).rejectedBy['213'].should.gt(1); (function() {
s.restore(); w.broadcastTx(ntxid);
done(); }).should.throw('Tx is not complete. Can not broadcast');
done();
});
}); });
}); it('should send a TX proposal to peers if incomplete', function(done) {
it('should create & sign & send a transaction', function(done) { var w = createW2(null, 1);
var w = createW2(null, 1); var utxo = createUTXO(w);
var utxo = createUTXO(w); w.blockchain.fixUnspent(utxo);
w.blockchain.fixUnspent(utxo);
w.spend({ sinon.spy(w, 'sendIndexes');
toAddress: toAddress, sinon.spy(w, 'sendTxProposal');
amountSat: amountSatStr, w.spend({
}, function(err, ntxid) { toAddress: toAddress,
w.broadcastTx(ntxid, function(err, txid, status) { amountSat: amountSatStr,
}, function(err, id, status) {
should.not.exist(err); should.not.exist(err);
txid.length.should.equal(64); should.exist(id);
status.should.equal(Wallet.TX_PROPOSAL_SENT);
w.sendTxProposal.calledOnce.should.equal(true);
w.sendIndexes.calledOnce.should.equal(true);
done();
});
});
it('should broadcast a TX if complete', function(done) {
var w = createW2(null, 1);
var utxo = createUTXO(w);
w.blockchain.fixUnspent(utxo);
sinon.spy(w, 'sendIndexes');
sinon.spy(w, 'sendTxProposal');
sinon.spy(w, 'broadcastTx');
sinon.stub(w, 'requiresMultipleSignatures').returns(false);
w.spend({
toAddress: toAddress,
amountSat: amountSatStr,
}, function(err, id, status) {
should.not.exist(err);
should.exist(id);
status.should.equal(Wallet.TX_BROADCASTED); status.should.equal(Wallet.TX_BROADCASTED);
w.sendTxProposal.calledOnce.should.equal(true);
w.sendIndexes.calledOnce.should.equal(true);
w.broadcastTx.calledOnce.should.equal(true);
done(); done();
}); });
}); });
}); });
it('should fail to send incomplete transaction', function(done) {
it('should return error if failing to send', function(done) {
var w = createW2(null, 1); var w = createW2(null, 1);
var utxo = createUTXO(w); var utxo = createUTXO(w);
w.blockchain.fixUnspent(utxo); w.blockchain.fixUnspent(utxo);
sinon.stub(w, 'requiresMultipleSignatures').returns(false);
sinon.spy(w, 'sendIndexes');
sinon.spy(w, 'sendTxProposal');
sinon.stub(w.blockchain, 'broadcast').yields('error');
w.spend({ w.spend({
toAddress: toAddress, toAddress: toAddress,
amountSat: amountSatStr, amountSat: amountSatStr,
}, function(err, ntxid) { }, function(err, id, status) {
var txp = w.txProposals.get(ntxid); err.should.equal('error');
// Assign fake builder w.sendTxProposal.calledOnce.should.equal(false);
txp.builder = new Builder(); w.sendIndexes.calledOnce.should.equal(true);
sinon.stub(txp.builder, 'build').returns({
isComplete: function() {
return false;
}
});
(function() {
w.broadcastTx(ntxid);
}).should.throw('Tx is not complete. Can not broadcast');
done();
});
});
it('should check if transaction already sent when failing to send', function(done) {
var w = createW2(null, 1);
var utxo = createUTXO(w);
w.blockchain.fixUnspent(utxo);
w.spend({
toAddress: toAddress,
amountSat: amountSatStr,
}, function(err, ntxid) {
sinon.stub(w.blockchain, 'broadcast').yields({
statusCode: 303
});
var spyCheckSentTx = sinon.spy(w, '_checkSentTx');
w.broadcastTx(ntxid, function() {});
chai.expect(spyCheckSentTx.calledOnce).to.be.true;
done(); done();
}); });
}); });
@ -940,19 +973,19 @@ describe('Wallet model', function() {
url: 'http://xxx', url: 'http://xxx',
}; };
var rawData ='wqer'; var rawData = 'wqer';
var e = sinon.stub(); var e = sinon.stub();
e.error = sinon.stub(); e.error = sinon.stub();
var s = sinon.stub(); var s = sinon.stub();
s.success = sinon.stub().yields(rawData).returns(e); s.success = sinon.stub().yields(rawData).returns(e);
sinon.stub(w.httpUtil,'request').returns(s); sinon.stub(w.httpUtil, 'request').returns(s);
w.fetchPaymentRequest(opts, function(err, merchantData){ w.fetchPaymentRequest(opts, function(err, merchantData) {
should.not.exist(err); should.not.exist(err);
should.exist(merchantData); should.exist(merchantData);
w.parsePaymentRequest.firstCall.args.should.deep.equal([opts,rawData]); w.parsePaymentRequest.firstCall.args.should.deep.equal([opts, rawData]);
done(); done();
}); });
}); });
@ -964,14 +997,14 @@ describe('Wallet model', function() {
url: 'http://xxx', url: 'http://xxx',
}; };
var rawData ='wqer'; var rawData = 'wqer';
var e = sinon.stub(); var e = sinon.stub();
e.error = sinon.stub().yields(null, 'status'); e.error = sinon.stub().yields(null, 'status');
var s = sinon.stub(); var s = sinon.stub();
s.success = sinon.stub().returns(e); s.success = sinon.stub().returns(e);
sinon.stub(w.httpUtil,'request').returns(s); sinon.stub(w.httpUtil, 'request').returns(s);
w.fetchPaymentRequest(opts, function(err, merchantData){ w.fetchPaymentRequest(opts, function(err, merchantData) {
err.toString().should.contain('status'); err.toString().should.contain('status');
done(); done();
}); });
@ -983,13 +1016,13 @@ describe('Wallet model', function() {
// FakePayProServer.getRequest should be parametrizable // FakePayProServer.getRequest should be parametrizable
describe('#parsePaymentRequest', function() { describe('#parsePaymentRequest', function() {
it('should parse a Payment Request', function() { it('should parse a Payment Request', function() {
var now = Date.now()/1000; var now = Date.now() / 1000;
var w = cachedCreateW2(); var w = cachedCreateW2();
var opts = { var opts = {
url: 'http://xxx', url: 'http://xxx',
}; };
var data = FakePayProServer.getRequest(); var data = FakePayProServer.getRequest();
var md = w.parsePaymentRequest(opts,data); var md = w.parsePaymentRequest(opts, data);
md.outs.should.deep.equal(FakePayProServer.outs); md.outs.should.deep.equal(FakePayProServer.outs);
md.request_url.should.equal(opts.url); md.request_url.should.equal(opts.url);
md.pr.untrusted.should.equal(true); md.pr.untrusted.should.equal(true);
@ -1158,7 +1191,9 @@ describe('Wallet model', function() {
var w = cachedCreateW2(); var w = cachedCreateW2();
var save = w.network.send; var save = w.network.send;
w.network.send = sinon.spy(); w.network.send = sinon.spy();
w._sendToPeers(null, {type:'hola'}); w._sendToPeers(null, {
type: 'hola'
});
w.network.send.calledOnce.should.equal(true); w.network.send.calledOnce.should.equal(true);
w.network.send = save; w.network.send = save;
}); });
@ -2389,6 +2424,12 @@ describe('Wallet model', function() {
}); });
}); });
describe.skip('#onPayProPaymentAck', function() {
it('should emit', function() {
var w = cachedCreateW2();
w.onPayProPaymentAck('id', 'data');
});
});
describe.skip('#read', function() { describe.skip('#read', function() {
var network, blockchain; var network, blockchain;

View File

@ -131,9 +131,6 @@ var createBundle = function(opts) {
b.require('./test/mocks/FakePayProServer', { b.require('./test/mocks/FakePayProServer', {
expose: './mocks/FakePayProServer' expose: './mocks/FakePayProServer'
}); });
b.require('./test/mocks/FakePayProServer', {
expose: '../../mocks/FakePayProServer'
});
b.require('./test/mocks/FakeBuilder', { b.require('./test/mocks/FakeBuilder', {
expose: './mocks/FakeBuilder' expose: './mocks/FakeBuilder'
}); });