From 8ac34da3efac142a35e15b12df0aae4d7964f80b Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Tue, 3 Feb 2015 23:17:06 -0300 Subject: [PATCH] add createTx --- lib/model/address.js | 2 +- lib/model/txproposal.js | 3 +- lib/model/wallet.js | 1 + lib/server.js | 36 ++++++--- lib/storage.js | 2 +- lib/utils.js | 10 +++ package.json | 3 +- test/integration.js | 164 +++++++++++++++++++++++++++++++++++----- 8 files changed, 189 insertions(+), 32 deletions(-) diff --git a/lib/model/address.js b/lib/model/address.js index b56746e..ef042d0 100644 --- a/lib/model/address.js +++ b/lib/model/address.js @@ -29,7 +29,7 @@ Address.fromObj = function (obj) { * @return {Script} */ Address.prototype.getScriptPubKey = function (threshold) { - return Bitcore.Script.buildMultisigOut(this.publicKeys, threshold) + return Bitcore.Script.buildMultisigOut(this.publicKeys, threshold).toScriptHashOut(); }; module.exports = Address; diff --git a/lib/model/txproposal.js b/lib/model/txproposal.js index 789a37c..9f462db 100644 --- a/lib/model/txproposal.js +++ b/lib/model/txproposal.js @@ -1,6 +1,7 @@ 'use strict'; var _ = require('lodash'); +var Guid = require('guid'); var TxProposalAction = require('./txproposalaction'); @@ -11,7 +12,7 @@ function TxProposal(opts) { this.version = VERSION; this.createdOn = Math.floor(Date.now() / 1000); - this.id = opts.id; + this.id = Guid.raw(); this.creatorId = opts.creatorId; this.toAddress = opts.toAddress; this.amount = opts.amount; diff --git a/lib/model/wallet.js b/lib/model/wallet.js index 3480d32..18689c3 100644 --- a/lib/model/wallet.js +++ b/lib/model/wallet.js @@ -113,6 +113,7 @@ Wallet.prototype.createAddress = function(isChange) { return new Address({ address: bitcoreAddress.toString(), path: path, + publicKeys: _.invoke(publicKeys, 'toString'), }); }; diff --git a/lib/server.js b/lib/server.js index a611bfb..561e807 100644 --- a/lib/server.js +++ b/lib/server.js @@ -279,6 +279,8 @@ CopayServer.prototype._getUtxos = function(opts, cb) { dictionary[input].locked = true; } }); + + return cb(null, utxos); }); }); @@ -303,14 +305,15 @@ CopayServer.prototype.getBalance = function(opts, cb) { if (err) return cb(err); var balance = {}; - balance.totalAmount = _.reduce(utxos, function(sum, utxo) { + balance.totalAmount = Utils.strip(_.reduce(utxos, function(sum, utxo) { return sum + utxo.amount; - }, 0); - balance.lockedAmount = _.reduce(_.filter(utxos, { + }, 0)); + + balance.lockedAmount = Utils.strip(_.reduce(_.filter(utxos, { locked: true }), function(sum, utxo) { return sum + utxo.amount; - }, 0); + }, 0)); return cb(null, balance); }); @@ -318,12 +321,15 @@ CopayServer.prototype.getBalance = function(opts, cb) { CopayServer.prototype._createRawTx = function(txp) { - console.log(txp); +console.log('[server.js.320:txp:]',txp.inputs, txp.toAddress, txp.amount, txp.changeAddress); //TODO + + var rawTx = new Bitcore.Transaction() .from(txp.inputs) .to(txp.toAddress, txp.amount) .change(txp.changeAddress); +console.log('[server.js.324:rawTx:]',rawTx); //TODO return rawTx; }; @@ -341,7 +347,7 @@ CopayServer.prototype._selectUtxos = function(txp, utxos) { } i++; }; - return selected; + return total >= txp.amount ? selected : null; }; @@ -360,6 +366,11 @@ CopayServer.prototype.createTx = function(opts, cb) { Utils.checkRequired(opts, ['walletId', 'copayerId', 'toAddress', 'amount', 'message']); + + // TODO? + // Check some parameters like: + // amount > dust + self.getWallet({ id: opts.walletId }, function(err, wallet) { @@ -370,7 +381,9 @@ CopayServer.prototype.createTx = function(opts, cb) { }, function(err, utxos) { if (err) return cb(err); - utxos = _.without(utxos, { + var changeAddress = wallet.createAddress(true).address; + + utxos = _.reject(utxos, { locked: true }); @@ -378,14 +391,17 @@ CopayServer.prototype.createTx = function(opts, cb) { creatorId: opts.copayerId, toAddress: opts.toAddress, amount: opts.amount, - changeAddress: opts.changeAddress, + changeAddress: changeAddress, requiredSignatures: wallet.m, maxRejections: wallet.n - wallet.m, }); + txp.inputs = self._selectUtxos(txp, utxos); + if (!txp.inputs) + return cb('insufficient funds') - txp.rawTx = self._createRawTx(txp); - + // no need to do this now: // TODO remove this comment + //self._createRawTx(txp); self.storage.storeTx(wallet.id, txp, function(err) { if (err) return cb(err); diff --git a/lib/storage.js b/lib/storage.js index c43a53f..6d1ae8b 100644 --- a/lib/storage.js +++ b/lib/storage.js @@ -59,7 +59,7 @@ Storage.prototype.fetchTxs = function (walletId, cb) { }; Storage.prototype.storeTx = function (walletId, txp, cb) { - this.db.put('wallet-' + walletId + '-txp-' + txp.txProposalId, txp, cb); + this.db.put('wallet-' + walletId + '-txp-' + txp.id, txp, cb); }; Storage.prototype.fetchAddresses = function (walletId, cb) { diff --git a/lib/utils.js b/lib/utils.js index 6661fff..aaaa456 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -25,4 +25,14 @@ Utils.checkRequired = function (obj, args) { }); }; +/** + * + * @desc rounds a JAvascript number + * @param number + * @return {number} + */ +Utils.strip = function(number) { + return (parseFloat(number.toPrecision(12))); +} + module.exports = Utils; diff --git a/package.json b/package.json index 96cd865..68214c8 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,8 @@ "levelup": "^0.19.0", "lodash": "^2.4.1", "npmlog": "^0.1.1", - "preconditions": "^1.0.7" + "preconditions": "^1.0.7", + "guid":"*" }, "devDependencies": { "chai": "^1.9.1", diff --git a/test/integration.js b/test/integration.js index 4a1affb..caced26 100644 --- a/test/integration.js +++ b/test/integration.js @@ -97,11 +97,7 @@ helpers.createAndJoinWallet = function(id, m, n, cb) { }; helpers.randomTXID = function() { - var ret = ''; - for (var i = 0; i < 64 / 4; i++) - ret += Math.floor(Math.random() * 255 * 255).toString(16); - - return ret; + return Bitcore.crypto.Hash.sha256(new Buffer(Math.random() * 100000)).toString('hex');; }; helpers.createUtxos = function(server, wallet, amounts, cb) { @@ -113,7 +109,6 @@ helpers.createUtxos = function(server, wallet, amounts, cb) { isChange: false, }, function(err, address) { addresses.push(address); - console.log('[integration.js.115:address:]', address); //TODO next(err); }); }, @@ -126,7 +121,7 @@ helpers.createUtxos = function(server, wallet, amounts, cb) { txid: helpers.randomTXID(), vout: Math.floor((Math.random() * 10) + 1), amount: amount, - scriptPubKey: addresses[i].getScriptPubKey(wallet.m), + scriptPubKey: addresses[i].getScriptPubKey(wallet.m).toBuffer().toString('hex'), }; })); }); @@ -623,12 +618,12 @@ describe('Copay server', function() { it('should create many addresses on simultaneous requests', function(done) { helpers.createAndJoinWallet('123', 2, 2, function(err, wallet) { - async.map(_.range(10), function (i, cb) { + async.map(_.range(10), function(i, cb) { server.createAddress({ walletId: '123', isChange: false, }, cb); - }, function (err, addresses) { + }, function(err, addresses) { addresses.length.should.equal(10); addresses[0].path.should.equal('m/2147483647/0/0'); addresses[9].path.should.equal('m/2147483647/0/9'); @@ -657,30 +652,27 @@ describe('Copay server', function() { }); }); - it.skip('should create tx', function(done) { + it('should create tx', function(done) { helpers.createUtxos(server, wallet, [100, 200], function(utxos) { - console.log('[integration.js.670:utxos:]', utxos); //TODO var bc = sinon.stub(); bc.getUnspentUtxos = sinon.stub().callsArgWith(1, null, utxos); server._getBlockExplorer = sinon.stub().returns(bc); - //server._createRawTx = sinon.stub().returns('raw'); var txOpts = { copayerId: '1', walletId: '123', - toAddress: 'dummy', + toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: 80, message: 'some message', otToken: 'dummy', requestSignature: 'dummy', }; + server.createTx(txOpts, function(err, tx) { should.not.exist(err); tx.should.exist; - console.log(tx); - //tx.rawTx.should.equal('raw'); tx.isAccepted().should.equal.false; tx.isRejected().should.equal.false; server.getPendingTxs({ @@ -701,10 +693,146 @@ describe('Copay server', function() { }); }); - it.skip('should fail to create tx when insufficient funds', function(done) {}); + it('should fail to create tx when insufficient funds', function(done) { - it.skip('should create tx when there is a pending tx and enough UTXOs', function(done) {}); + helpers.createUtxos(server, wallet, [100], function(utxos) { - it.skip('should fail to create tx when there is a pending tx and not enough UTXOs', function(done) {}); + var bc = sinon.stub(); + bc.getUnspentUtxos = sinon.stub().callsArgWith(1, null, utxos); + server._getBlockExplorer = sinon.stub().returns(bc); + + var txOpts = { + copayerId: '1', + walletId: '123', + toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', + amount: 120, + message: 'some message', + otToken: 'dummy', + requestSignature: 'dummy', + }; + + server.createTx(txOpts, function(err, tx) { + err.should.contain('insufficient'); + server.getPendingTxs({ + walletId: '123' + }, function(err, txs) { + should.not.exist(err); + txs.length.should.equal(0); + server.getBalance({ + walletId: '123' + }, function(err, balance) { + should.not.exist(err); + balance.lockedAmount.should.equal(0); + balance.totalAmount.should.equal(100); + done(); + }); + }); + }); + }); + }); + + it('should create tx when there is a pending tx and enough UTXOs', function(done) { + + helpers.createUtxos(server, wallet, [10.1, 10.2, 10.3], function(utxos) { + + var bc = sinon.stub(); + bc.getUnspentUtxos = sinon.stub().callsArgWith(1, null, utxos); + server._getBlockExplorer = sinon.stub().returns(bc); + + var txOpts = { + copayerId: '1', + walletId: '123', + toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', + amount: 12, + message: 'some message', + otToken: 'dummy', + requestSignature: 'dummy', + }; + server.createTx(txOpts, function(err, tx) { + should.not.exist(err); + tx.should.exist; + + var txOpts2 = { + copayerId: '1', + walletId: '123', + toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', + amount: 8, + message: 'some message 2', + otToken: 'dummy', + requestSignature: 'dummy', + }; + server.createTx(txOpts2, function(err, tx) { + should.not.exist(err); + tx.should.exist; + server.getPendingTxs({ + walletId: '123' + }, function(err, txs) { + should.not.exist(err); + txs.length.should.equal(2); + server.getBalance({ + walletId: '123' + }, function(err, balance) { + should.not.exist(err); + balance.totalAmount.should.equal(30.6); + balance.lockedAmount.should.equal(30.6); + done(); + }); + }); + }); + }); + }); + }); + + it('should fail to create tx when there is a pending tx and not enough UTXOs', function(done) { + helpers.createUtxos(server, wallet, [10.1, 10.2, 10.3], function(utxos) { + + var bc = sinon.stub(); + bc.getUnspentUtxos = sinon.stub().callsArgWith(1, null, utxos); + server._getBlockExplorer = sinon.stub().returns(bc); + + var txOpts = { + copayerId: '1', + walletId: '123', + toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', + amount: 12, + message: 'some message', + otToken: 'dummy', + requestSignature: 'dummy', + }; + server.createTx(txOpts, function(err, tx) { + should.not.exist(err); + tx.should.exist; + + var txOpts2 = { + copayerId: '1', + walletId: '123', + toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', + amount: 24, + message: 'some message 2', + otToken: 'dummy', + requestSignature: 'dummy', + }; + server.createTx(txOpts2, function(err, tx) { + err.should.contain('insufficient'); + should.not.exist(tx); + server.getPendingTxs({ + walletId: '123' + }, function(err, txs) { + should.not.exist(err); + txs.length.should.equal(1); + server.getBalance({ + walletId: '123' + }, function(err, balance) { + should.not.exist(err); + balance.totalAmount.should.equal(30.6); + balance.lockedAmount.should.equal(20.3); + done(); + }); + }); + }); + }); + }); + + }); }); });