change indentation

This commit is contained in:
Ivan Socolsky 2015-02-02 17:07:18 -03:00
parent 5114b0cc25
commit 399c412cde
9 changed files with 409 additions and 409 deletions

View File

@ -3,31 +3,31 @@ var _ = require('lodash');
var locks = {}; var locks = {};
var Lock = function () { var Lock = function () {
this.taken = false; this.taken = false;
this.queue = []; this.queue = [];
}; };
Lock.prototype.free = function () { Lock.prototype.free = function () {
if (this.queue.length > 0) { if (this.queue.length > 0) {
var f = this.queue.shift(); var f = this.queue.shift();
f(this); f(this);
} else { } else {
this.taken = false; this.taken = false;
} }
}; };
Lock.get = function (key, callback) { Lock.get = function (key, callback) {
if (_.isUndefined(locks[key])) { if (_.isUndefined(locks[key])) {
locks[key] = new Lock(); locks[key] = new Lock();
} }
var lock = locks[key]; var lock = locks[key];
if (lock.taken) { if (lock.taken) {
lock.queue.push(callback); lock.queue.push(callback);
} else { } else {
lock.taken = true; lock.taken = true;
callback(lock); callback(lock);
} }
}; };
module.exports = Lock; module.exports = Lock;

View File

@ -1,20 +1,20 @@
'use strict'; 'use strict';
function Address(opts) { function Address(opts) {
opts = opts || {}; opts = opts || {};
this.createdOn = Math.floor(Date.now() / 1000); this.createdOn = Math.floor(Date.now() / 1000);
this.address = opts.address; this.address = opts.address;
this.path = opts.path; this.path = opts.path;
}; };
Address.fromObj = function (obj) { Address.fromObj = function (obj) {
var x = new Address(); var x = new Address();
x.createdOn = obj.createdOn; x.createdOn = obj.createdOn;
x.address = obj.address; x.address = obj.address;
x.path = obj.path; x.path = obj.path;
return x; return x;
}; };
module.exports = Address; module.exports = Address;

View File

@ -8,17 +8,17 @@ var VERSION = '1.0.0';
var MESSAGE_SIGNING_PATH = "m/1/0"; var MESSAGE_SIGNING_PATH = "m/1/0";
function Copayer(opts) { function Copayer(opts) {
opts = opts || {}; opts = opts || {};
this.version = VERSION; this.version = VERSION;
this.createdOn = Math.floor(Date.now() / 1000); this.createdOn = Math.floor(Date.now() / 1000);
this.id = opts.id; this.id = opts.id;
this.name = opts.name; this.name = opts.name;
this.xPubKey = opts.xPubKey; this.xPubKey = opts.xPubKey;
this.xPubKeySignature = opts.xPubKeySignature; // So third parties can check independently this.xPubKeySignature = opts.xPubKeySignature; // So third parties can check independently
if (opts.xPubKey) { if (opts.xPubKey) {
this.signingPubKey = this.getSigningPubKey(); this.signingPubKey = this.getSigningPubKey();
} }
}; };
Copayer.prototype.getSigningPubKey = function () { Copayer.prototype.getSigningPubKey = function () {
@ -27,17 +27,17 @@ Copayer.prototype.getSigningPubKey = function () {
}; };
Copayer.fromObj = function (obj) { Copayer.fromObj = function (obj) {
var x = new Copayer(); var x = new Copayer();
x.version = obj.version; x.version = obj.version;
x.createdOn = obj.createdOn; x.createdOn = obj.createdOn;
x.id = obj.id; x.id = obj.id;
x.name = obj.name; x.name = obj.name;
x.xPubKey = obj.xPubKey; x.xPubKey = obj.xPubKey;
x.xPubKeySignature = obj.xPubKeySignature; x.xPubKeySignature = obj.xPubKeySignature;
x.signingPubKey = obj.signingPubKey; x.signingPubKey = obj.signingPubKey;
return x; return x;
}; };

View File

@ -7,88 +7,88 @@ var TxProposalAction = require('./txproposalaction');
var VERSION = '1.0.0'; var VERSION = '1.0.0';
function TxProposal(opts) { function TxProposal(opts) {
opts = opts || {}; opts = opts || {};
this.version = VERSION; this.version = VERSION;
this.createdOn = Math.floor(Date.now() / 1000); this.createdOn = Math.floor(Date.now() / 1000);
this.id = opts.id; this.id = opts.id;
this.creatorId = opts.creatorId; this.creatorId = opts.creatorId;
this.toAddress = opts.toAddress; this.toAddress = opts.toAddress;
this.amount = opts.amount; this.amount = opts.amount;
this.message = opts.message; this.message = opts.message;
this.changeAddress = opts.changeAddress; this.changeAddress = opts.changeAddress;
this.inputs = opts.inputs; this.inputs = opts.inputs;
this.requiredSignatures = opts.requiredSignatures; this.requiredSignatures = opts.requiredSignatures;
this.maxRejections = opts.maxRejections; this.maxRejections = opts.maxRejections;
this.status = 'pending'; this.status = 'pending';
this.actions = []; this.actions = [];
}; };
TxProposal.fromObj = function (obj) { TxProposal.fromObj = function (obj) {
var x = new TxProposal(); var x = new TxProposal();
x.version = obj.version; x.version = obj.version;
x.createdOn = obj.createdOn; x.createdOn = obj.createdOn;
x.id = obj.id; x.id = obj.id;
x.creatorId = obj.creatorId; x.creatorId = obj.creatorId;
x.toAddress = obj.toAddress; x.toAddress = obj.toAddress;
x.amount = obj.amount; x.amount = obj.amount;
x.message = obj.message; x.message = obj.message;
x.changeAddress = obj.changeAddress; x.changeAddress = obj.changeAddress;
x.inputs = obj.inputs; x.inputs = obj.inputs;
x.rawTx = obj.rawTx; x.rawTx = obj.rawTx;
x.requiredSignatures = obj.requiredSignatures; x.requiredSignatures = obj.requiredSignatures;
x.maxRejections = obj.maxRejections; x.maxRejections = obj.maxRejections;
x.status = obj.status; x.status = obj.status;
x.txid = obj.txid; x.txid = obj.txid;
x.actions = _.map(obj.actions, function(action) { x.actions = _.map(obj.actions, function(action) {
return new TxProposalAction(action); return new TxProposalAction(action);
}); });
return x; return x;
}; };
TxProposal.prototype._updateStatus = function () { TxProposal.prototype._updateStatus = function () {
if (this.status != 'pending') return; if (this.status != 'pending') return;
if (this.isRejected()) { if (this.isRejected()) {
this.status = 'rejected'; this.status = 'rejected';
} else if (this.isAccepted()) { } else if (this.isAccepted()) {
this.status = 'accepted'; this.status = 'accepted';
} }
}; };
TxProposal.prototype.addAction = function (copayerId, type, signature) { TxProposal.prototype.addAction = function (copayerId, type, signature) {
var action = new TxProposalAction({ var action = new TxProposalAction({
copayerId: copayerId, copayerId: copayerId,
type: type, type: type,
signature: signature, signature: signature,
}); });
this.actions.push(action); this.actions.push(action);
this._updateStatus(); this._updateStatus();
}; };
TxProposal.prototype.sign = function (copayerId, signature) { TxProposal.prototype.sign = function (copayerId, signature) {
this.addAction(copayerId, 'accept', signature); this.addAction(copayerId, 'accept', signature);
}; };
TxProposal.prototype.reject = function (copayerId) { TxProposal.prototype.reject = function (copayerId) {
this.addAction(copayerId, 'reject'); this.addAction(copayerId, 'reject');
}; };
TxProposal.prototype.isAccepted = function () { TxProposal.prototype.isAccepted = function () {
var votes = _.countBy(this.actions, 'type'); var votes = _.countBy(this.actions, 'type');
return votes['accept'] >= this.requiredSignatures; return votes['accept'] >= this.requiredSignatures;
}; };
TxProposal.prototype.isRejected = function () { TxProposal.prototype.isRejected = function () {
var votes = _.countBy(this.actions, 'type'); var votes = _.countBy(this.actions, 'type');
return votes['reject'] > this.maxRejections; return votes['reject'] > this.maxRejections;
}; };
TxProposal.prototype.setBroadcasted = function (txid) { TxProposal.prototype.setBroadcasted = function (txid) {
this.txid = txid; this.txid = txid;
this.status = 'broadcasted'; this.status = 'broadcasted';
}; };
module.exports = TxProposal; module.exports = TxProposal;

View File

@ -1,23 +1,23 @@
'use strict'; 'use strict';
function TxProposalAction(opts) { function TxProposalAction(opts) {
opts = opts || {}; opts = opts || {};
this.createdOn = Math.floor(Date.now() / 1000); this.createdOn = Math.floor(Date.now() / 1000);
this.copayerId = opts.copayerId; this.copayerId = opts.copayerId;
this.type = opts.type || (opts.signature ? 'accept' : 'reject'); this.type = opts.type || (opts.signature ? 'accept' : 'reject');
this.signature = opts.signature; this.signature = opts.signature;
}; };
TxProposalAction.fromObj = function (obj) { TxProposalAction.fromObj = function (obj) {
var x = new TxProposalAction(); var x = new TxProposalAction();
x.createdOn = obj.createdOn; x.createdOn = obj.createdOn;
x.copayerId = obj.copayerId; x.copayerId = obj.copayerId;
x.type = obj.type; x.type = obj.type;
x.signature = obj.signature; x.signature = obj.signature;
return x; return x;
}; };
module.exports = TxProposalAction; module.exports = TxProposalAction;

View File

@ -6,18 +6,18 @@ var Copayer = require('./copayer');
var VERSION = '1.0.0'; var VERSION = '1.0.0';
function Wallet(opts) { function Wallet(opts) {
opts = opts || {}; opts = opts || {};
this.version = VERSION; this.version = VERSION;
this.createdOn = Math.floor(Date.now() / 1000); this.createdOn = Math.floor(Date.now() / 1000);
this.id = opts.id; this.id = opts.id;
this.name = opts.name; this.name = opts.name;
this.m = opts.m; this.m = opts.m;
this.n = opts.n; this.n = opts.n;
this.status = 'pending'; this.status = 'pending';
this.publicKeyRing = []; this.publicKeyRing = [];
this.addressIndex = 0; this.addressIndex = 0;
this.copayers = []; this.copayers = [];
this.pubKey = opts.pubKey; this.pubKey = opts.pubKey;
}; };
@ -48,40 +48,40 @@ Wallet.getMaxRequiredCopayers = function(totalCopayers) {
}; };
Wallet.verifyCopayerLimits = function (m, n) { Wallet.verifyCopayerLimits = function (m, n) {
return (n >= 1 && n <= 12) && (m >= 1 && m <= Wallet.COPAYER_PAIR_LIMITS[n]); return (n >= 1 && n <= 12) && (m >= 1 && m <= Wallet.COPAYER_PAIR_LIMITS[n]);
}; };
Wallet.fromObj = function (obj) { Wallet.fromObj = function (obj) {
var x = new Wallet(); var x = new Wallet();
x.version = obj.version; x.version = obj.version;
x.createdOn = obj.createdOn; x.createdOn = obj.createdOn;
x.id = obj.id; x.id = obj.id;
x.name = obj.name; x.name = obj.name;
x.m = obj.m; x.m = obj.m;
x.n = obj.n; x.n = obj.n;
x.status = obj.status; x.status = obj.status;
x.publicKeyRing = obj.publicKeyRing; x.publicKeyRing = obj.publicKeyRing;
x.addressIndex = obj.addressIndex; x.addressIndex = obj.addressIndex;
x.copayers = _.map(obj.copayers, function (copayer) { x.copayers = _.map(obj.copayers, function (copayer) {
return new Copayer(copayer); return new Copayer(copayer);
}); });
x.pubKey = obj.pubKey; x.pubKey = obj.pubKey;
return x; return x;
}; };
Wallet.prototype.addCopayer = function (copayer) { Wallet.prototype.addCopayer = function (copayer) {
this.copayers.push(copayer); this.copayers.push(copayer);
if (this.copayers.length < this.n) return; if (this.copayers.length < this.n) return;
this.status = 'complete'; this.status = 'complete';
this.publicKeyRing = _.pluck(this.copayers, 'xPubKey'); this.publicKeyRing = _.pluck(this.copayers, 'xPubKey');
}; };
Wallet.prototype.getCopayer = function (copayerId) { Wallet.prototype.getCopayer = function (copayerId) {
return _.find(this.copayers, { id: copayerId }); return _.find(this.copayers, { id: copayerId });
}; };
module.exports = Wallet; module.exports = Wallet;

View File

@ -30,8 +30,8 @@ var TxProposal = require('./model/txproposal');
* @param {Storage} [opts.storage] - The storage provider. * @param {Storage} [opts.storage] - The storage provider.
*/ */
function CopayServer(opts) { function CopayServer(opts) {
opts = opts || {}; opts = opts || {};
this.storage = opts.storage || new Storage(); this.storage = opts.storage || new Storage();
}; };
inherits(CopayServer, events.EventEmitter); inherits(CopayServer, events.EventEmitter);
@ -53,12 +53,12 @@ CopayServer._emit = function (event) {
* @param {string} [opts.network = 'livenet'] - The Bitcoin network for this wallet. * @param {string} [opts.network = 'livenet'] - The Bitcoin network for this wallet.
*/ */
CopayServer.prototype.createWallet = function (opts, cb) { CopayServer.prototype.createWallet = function (opts, cb) {
var self = this, pubKey; var self = this, pubKey;
Utils.checkRequired(opts, ['id', 'name', 'm', 'n', 'pubKey']); Utils.checkRequired(opts, ['id', 'name', 'm', 'n', 'pubKey']);
if (!Wallet.verifyCopayerLimits(opts.m, opts.n)) return cb('Incorrect m or n value'); if (!Wallet.verifyCopayerLimits(opts.m, opts.n)) return cb('Incorrect m or n value');
var network = opts.network || 'livenet'; var network = opts.network || 'livenet';
if (network != 'livenet' && network != 'testnet') return cb('Invalid network'); if (network != 'livenet' && network != 'testnet') return cb('Invalid network');
try { try {
pubKey = new PublicKey.fromString(opts.pubKey); pubKey = new PublicKey.fromString(opts.pubKey);
@ -66,21 +66,21 @@ CopayServer.prototype.createWallet = function (opts, cb) {
return cb(e.toString()); return cb(e.toString());
}; };
self.storage.fetchWallet(opts.id, function (err, wallet) { self.storage.fetchWallet(opts.id, function (err, wallet) {
if (err) return cb(err); if (err) return cb(err);
if (wallet) return cb('Wallet already exists'); if (wallet) return cb('Wallet already exists');
var wallet = new Wallet({ var wallet = new Wallet({
id: opts.id, id: opts.id,
name: opts.name, name: opts.name,
m: opts.m, m: opts.m,
n: opts.n, n: opts.n,
network: network, network: network,
pubKey: pubKey, pubKey: pubKey,
}); });
self.storage.storeWallet(wallet, cb); self.storage.storeWallet(wallet, cb);
}); });
}; };
/** /**
@ -90,13 +90,13 @@ CopayServer.prototype.createWallet = function (opts, cb) {
* @returns {Object} wallet * @returns {Object} wallet
*/ */
CopayServer.prototype.getWallet = function (opts, cb) { CopayServer.prototype.getWallet = function (opts, cb) {
var self = this; var self = this;
self.storage.fetchWallet(opts.id, function (err, wallet) { self.storage.fetchWallet(opts.id, function (err, wallet) {
if (err) return cb(err); if (err) return cb(err);
if (!wallet) return cb('Wallet not found'); if (!wallet) return cb('Wallet not found');
return cb(null, wallet); return cb(null, wallet);
}); });
}; };
@ -120,40 +120,40 @@ CopayServer.prototype._verifySignature = function (text, signature, pubKey) {
* @param {number} opts.xPubKeySignature - Signature of xPubKey using the wallet pubKey. * @param {number} opts.xPubKeySignature - Signature of xPubKey using the wallet pubKey.
*/ */
CopayServer.prototype.joinWallet = function (opts, cb) { CopayServer.prototype.joinWallet = function (opts, cb) {
var self = this; var self = this;
Utils.checkRequired(opts, ['walletId', 'id', 'name', 'xPubKey', 'xPubKeySignature']); Utils.checkRequired(opts, ['walletId', 'id', 'name', 'xPubKey', 'xPubKeySignature']);
Utils.runLocked(opts.walletId, cb, function (cb) { Utils.runLocked(opts.walletId, cb, function (cb) {
self.getWallet({ id: opts.walletId }, function (err, wallet) { self.getWallet({ id: opts.walletId }, function (err, wallet) {
if (err) return cb(err); if (err) return cb(err);
if (!self._verifySignature(opts.xPubKey, opts.xPubKeySignature, wallet.pubKey)) { if (!self._verifySignature(opts.xPubKey, opts.xPubKeySignature, wallet.pubKey)) {
return cb('Bad request'); return cb('Bad request');
} }
if (_.find(wallet.copayers, { xPubKey: opts.xPubKey })) return cb('Copayer already in wallet'); if (_.find(wallet.copayers, { xPubKey: opts.xPubKey })) return cb('Copayer already in wallet');
if (wallet.copayers.length == wallet.n) return cb('Wallet full'); if (wallet.copayers.length == wallet.n) return cb('Wallet full');
var copayer = new Copayer({ var copayer = new Copayer({
id: opts.id, id: opts.id,
name: opts.name, name: opts.name,
xPubKey: opts.xPubKey, xPubKey: opts.xPubKey,
xPubKeySignature: opts.xPubKeySignature, xPubKeySignature: opts.xPubKeySignature,
}); });
wallet.addCopayer(copayer); wallet.addCopayer(copayer);
self.storage.storeWallet(wallet, function (err) { self.storage.storeWallet(wallet, function (err) {
if (err) return cb(err); if (err) return cb(err);
return cb(); return cb();
}); });
}); });
}); });
}; };
CopayServer.prototype._doCreateAddress = function (pkr, index, isChange) { CopayServer.prototype._doCreateAddress = function (pkr, index, isChange) {
throw 'not implemented'; throw 'not implemented';
}; };
/** /**
@ -167,27 +167,27 @@ CopayServer.prototype._doCreateAddress = function (pkr, index, isChange) {
* @returns {Address} address * @returns {Address} address
*/ */
CopayServer.prototype.createAddress = function (opts, cb) { CopayServer.prototype.createAddress = function (opts, cb) {
var self = this; var self = this;
Utils.checkRequired(opts, ['walletId', 'isChange']); Utils.checkRequired(opts, ['walletId', 'isChange']);
Utils.runLocked(opts.walletId, cb, function (cb) { Utils.runLocked(opts.walletId, cb, function (cb) {
self.getWallet({ id: opts.walletId }, function (err, wallet) { self.getWallet({ id: opts.walletId }, function (err, wallet) {
if (err) return cb(err); if (err) return cb(err);
var index = wallet.addressIndex++; var index = wallet.addressIndex++;
self.storage.storeWallet(wallet, function (err) { self.storage.storeWallet(wallet, function (err) {
if (err) return cb(err); if (err) return cb(err);
var address = self._doCreateAddress(wallet.publicKeyRing, index, opts.isChange); var address = self._doCreateAddress(wallet.publicKeyRing, index, opts.isChange);
self.storage.storeAddress(opts.walletId, address, function (err) { self.storage.storeAddress(opts.walletId, address, function (err) {
if (err) return cb(err); if (err) return cb(err);
return cb(null, address); return cb(null, address);
}); });
}); });
}); });
}); });
}; };
/** /**
@ -200,78 +200,78 @@ CopayServer.prototype._doCreateAddress = function (pkr, index, isChange) {
* @returns {truthy} The result of the verification. * @returns {truthy} The result of the verification.
*/ */
CopayServer.prototype.verifyMessageSignature = function (opts, cb) { CopayServer.prototype.verifyMessageSignature = function (opts, cb) {
var self = this; var self = this;
Utils.checkRequired(opts, ['walletId', 'copayerId', 'message', 'signature']); Utils.checkRequired(opts, ['walletId', 'copayerId', 'message', 'signature']);
self.getWallet({ id: opts.walletId }, function (err, wallet) { self.getWallet({ id: opts.walletId }, function (err, wallet) {
if (err) return cb(err); if (err) return cb(err);
var copayer = wallet.getCopayer(opts.copayerId); var copayer = wallet.getCopayer(opts.copayerId);
if (!copayer) return cb('Copayer not found'); if (!copayer) return cb('Copayer not found');
var isValid = self._verifySignature(opts.message, opts.signature, copayer.signingPubKey); var isValid = self._verifySignature(opts.message, opts.signature, copayer.signingPubKey);
return cb(null, isValid); return cb(null, isValid);
}); });
}; };
CopayServer.prototype._getBlockExplorer = function (provider, network) { CopayServer.prototype._getBlockExplorer = function (provider, network) {
var url; var url;
switch (provider) { switch (provider) {
default: default:
case 'insight': case 'insight':
switch (network) { switch (network) {
default: default:
case 'livenet': case 'livenet':
url = 'https://insight.bitpay.com:443'; url = 'https://insight.bitpay.com:443';
break; break;
case 'testnet': case 'testnet':
url = 'https://test-insight.bitpay.com:443' url = 'https://test-insight.bitpay.com:443'
break; break;
} }
return new Explorers.Insight(url, network); return new Explorers.Insight(url, network);
break; break;
} }
}; };
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);
if (addresses.length == 0) return cb('The wallet has no addresses'); if (addresses.length == 0) return cb('The wallet has no addresses');
var addresses = _.pluck(addresses, 'address'); var addresses = _.pluck(addresses, 'address');
var bc = self._getBlockExplorer('insight', opts.network); var bc = self._getBlockExplorer('insight', opts.network);
bc.getUnspentUtxos(addresses, function (err, utxos) { bc.getUnspentUtxos(addresses, function (err, utxos) {
if (err) return cb(err); if (err) return cb(err);
self.getPendingTxs({ walletId: opts.walletId }, function (err, txps) { self.getPendingTxs({ walletId: opts.walletId }, function (err, txps) {
if (err) return cb(err); if (err) return cb(err);
var inputs = _.chain(txps) var inputs = _.chain(txps)
.pluck('inputs') .pluck('inputs')
.flatten() .flatten()
.map(function (utxo) { return utxo.txid + '|' + utxo.vout }); .map(function (utxo) { return utxo.txid + '|' + utxo.vout });
var dictionary = _.groupBy(utxos, function (utxo) { var dictionary = _.groupBy(utxos, function (utxo) {
return utxo.txid + '|' + utxo.vout; return utxo.txid + '|' + utxo.vout;
}); });
_.each(inputs, function (input) { _.each(inputs, function (input) {
if (dictionary[input]) { if (dictionary[input]) {
dictionary[input].locked = true; dictionary[input].locked = true;
} }
}); });
return cb(null, utxos); return cb(null, utxos);
}); });
}); });
}); });
}; };
@ -282,45 +282,45 @@ CopayServer.prototype._getUtxos = function (opts, cb) {
* @returns {Object} balance - Total amount & locked amount. * @returns {Object} balance - Total amount & locked amount.
*/ */
CopayServer.prototype.getBalance = function (opts, cb) { CopayServer.prototype.getBalance = function (opts, cb) {
var self = this; var self = this;
Utils.checkRequired(opts, 'walletId'); Utils.checkRequired(opts, 'walletId');
self._getUtxos({ walletId: opts.walletId }, function (err, utxos) { self._getUtxos({ walletId: opts.walletId }, function (err, utxos) {
if (err) return cb(err); if (err) return cb(err);
var balance = {}; var balance = {};
balance.totalAmount = _.reduce(utxos, function(sum, utxo) { return sum + utxo.amount; }, 0); balance.totalAmount = _.reduce(utxos, function(sum, utxo) { return sum + utxo.amount; }, 0);
balance.lockedAmount = _.reduce(_.without(utxos, { locked: true }), function(sum, utxo) { return sum + utxo.amount; }, 0); balance.lockedAmount = _.reduce(_.without(utxos, { locked: true }), function(sum, utxo) { return sum + utxo.amount; }, 0);
return cb(null, balance); return cb(null, balance);
}); });
}; };
CopayServer.prototype._createRawTx = function (txp) { CopayServer.prototype._createRawTx = function (txp) {
var rawTx = new Bitcore.Transaction() var rawTx = new Bitcore.Transaction()
.from(tx.inputs) .from(tx.inputs)
.to(txp.toAddress, txp.amount) .to(txp.toAddress, txp.amount)
.change(txp.changeAddress); .change(txp.changeAddress);
return rawTx; return rawTx;
}; };
CopayServer.prototype._selectUtxos = function (txp, utxos) { CopayServer.prototype._selectUtxos = function (txp, utxos) {
var i = 0; var i = 0;
var total = 0; var total = 0;
var selected = []; var selected = [];
var inputs = _.sortBy(utxos, 'amount'); var inputs = _.sortBy(utxos, 'amount');
while (i < inputs.length) { while (i < inputs.length) {
selected.push(inputs[i]); selected.push(inputs[i]);
total += inputs[i].amount; total += inputs[i].amount;
if (total >= txp.amount) { if (total >= txp.amount) {
break; break;
} }
i++; i++;
}; };
return selected; return selected;
}; };
@ -335,44 +335,44 @@ CopayServer.prototype._selectUtxos = function (txp, utxos) {
* @returns {TxProposal} Transaction proposal. * @returns {TxProposal} Transaction proposal.
*/ */
CopayServer.prototype.createTx = function (opts, cb) { CopayServer.prototype.createTx = function (opts, cb) {
var self = this; var self = this;
Utils.checkRequired(opts, ['walletId', 'copayerId', 'toAddress', 'amount', 'message']); Utils.checkRequired(opts, ['walletId', 'copayerId', 'toAddress', 'amount', 'message']);
self.getWallet({ id: opts.walletId }, function (err, wallet) { self.getWallet({ id: opts.walletId }, function (err, wallet) {
if (err) return cb(err); if (err) return cb(err);
self._getUtxos({ walletId: wallet.id }, function (err, utxos) { self._getUtxos({ walletId: wallet.id }, function (err, utxos) {
if (err) return cb(err); if (err) return cb(err);
utxos = _.without(utxos, { locked: true }); utxos = _.without(utxos, { locked: true });
var txp = new TxProposal({ var txp = new TxProposal({
creatorId: opts.copayerId, creatorId: opts.copayerId,
toAddress: opts.toAddress, toAddress: opts.toAddress,
amount: opts.amount, amount: opts.amount,
inputs: self._selectUtxos(opts.amount, utxos), inputs: self._selectUtxos(opts.amount, utxos),
changeAddress: opts.changeAddress, changeAddress: opts.changeAddress,
requiredSignatures: wallet.m, requiredSignatures: wallet.m,
maxRejections: wallet.n - wallet.m, maxRejections: wallet.n - wallet.m,
}); });
txp.rawTx = self._createRawTx(txp); txp.rawTx = self._createRawTx(txp);
self.storage.storeTx(wallet.id, txp, function (err) { self.storage.storeTx(wallet.id, txp, function (err) {
if (err) return cb(err); if (err) return cb(err);
return cb(null, txp); return cb(null, txp);
}); });
}); });
}); });
}; };
CopayServer.prototype._broadcastTx = function (rawTx, cb) { CopayServer.prototype._broadcastTx = function (rawTx, cb) {
// TODO: this should attempt to broadcast _all_ accepted and not-yet broadcasted (status=='accepted') txps? // TODO: this should attempt to broadcast _all_ accepted and not-yet broadcasted (status=='accepted') txps?
cb = cb || function () {}; cb = cb || function () {};
throw 'not implemented'; throw 'not implemented';
}; };
/** /**
@ -384,35 +384,35 @@ CopayServer.prototype._broadcastTx = function (rawTx, cb) {
* @param {string} opts.signature - The signature of the tx for this copayer. * @param {string} opts.signature - The signature of the tx for this copayer.
*/ */
CopayServer.prototype.signTx = function (opts, cb) { CopayServer.prototype.signTx = function (opts, cb) {
var self = this; var self = this;
Utils.checkRequired(opts, ['walletId', 'copayerId', 'txProposalId', 'signature']); Utils.checkRequired(opts, ['walletId', 'copayerId', 'txProposalId', 'signature']);
self.fetchTx(opts.walletId, opts.txProposalId, function (err, txp) { self.fetchTx(opts.walletId, opts.txProposalId, function (err, txp) {
if (err) return cb(err); if (err) return cb(err);
if (!txp) return cb('Transaction proposal not found'); if (!txp) return cb('Transaction proposal not found');
var action = _.find(txp.actions, { copayerId: opts.copayerId }); var action = _.find(txp.actions, { copayerId: opts.copayerId });
if (action) return cb('Copayer already voted on this transaction proposal'); if (action) return cb('Copayer already voted on this transaction proposal');
if (txp.status != 'pending') return cb('The transaction proposal is not pending'); if (txp.status != 'pending') return cb('The transaction proposal is not pending');
txp.sign(opts.copayerId, opts.signature); txp.sign(opts.copayerId, opts.signature);
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);
if (txp.status == 'accepted'); if (txp.status == 'accepted');
self._broadcastTx(txp.rawTx, function (err, txid) { self._broadcastTx(txp.rawTx, function (err, txid) {
if (err) return cb(err); if (err) return cb(err);
tx.setBroadcasted(txid); tx.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);
return cb(); return cb();
}); });
}); });
}); });
}); });
}; };
/** /**
@ -424,25 +424,25 @@ CopayServer.prototype.signTx = function (opts, cb) {
* @param {string} [opts.reason] - A message to other copayers explaining the rejection. * @param {string} [opts.reason] - A message to other copayers explaining the rejection.
*/ */
CopayServer.prototype.rejectTx = function (opts, cb) { CopayServer.prototype.rejectTx = function (opts, cb) {
var self = this; var self = this;
Utils.checkRequired(opts, ['walletId', 'copayerId', 'txProposalId']); Utils.checkRequired(opts, ['walletId', 'copayerId', 'txProposalId']);
self.fetchTx(opts.walletId, opts.txProposalId, function (err, txp) { self.fetchTx(opts.walletId, opts.txProposalId, function (err, txp) {
if (err) return cb(err); if (err) return cb(err);
if (!txp) return cb('Transaction proposal not found'); if (!txp) return cb('Transaction proposal not found');
var action = _.find(txp.actions, { copayerId: opts.copayerId }); var action = _.find(txp.actions, { copayerId: opts.copayerId });
if (action) return cb('Copayer already voted on this transaction proposal'); if (action) return cb('Copayer already voted on this transaction proposal');
if (txp.status != 'pending') return cb('The transaction proposal is not pending'); if (txp.status != 'pending') return cb('The transaction proposal is not pending');
txp.reject(opts.copayerId); txp.reject(opts.copayerId);
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);
return cb(); return cb();
}); });
}); });
}; };
/** /**
@ -452,17 +452,17 @@ CopayServer.prototype.rejectTx = function (opts, cb) {
* @returns {TxProposal[]} Transaction proposal. * @returns {TxProposal[]} Transaction proposal.
*/ */
CopayServer.prototype.getPendingTxs = function (opts, cb) { CopayServer.prototype.getPendingTxs = function (opts, cb) {
var self = this; var self = this;
Utils.checkRequired(opts, 'walletId'); Utils.checkRequired(opts, 'walletId');
self.storage.fetchTxs(opts.walletId, function (err, txps) { self.storage.fetchTxs(opts.walletId, function (err, txps) {
if (err) return cb(err); if (err) return cb(err);
var pending = _.filter(txps, { status: 'pending' }); var pending = _.filter(txps, { status: 'pending' });
return cb(null, pending); return cb(null, pending);
}); });
}; };

View File

@ -13,73 +13,73 @@ var Address = require('./model/address');
var TxProposal = require('./model/txproposal'); var TxProposal = require('./model/txproposal');
var Storage = function (opts) { var Storage = function (opts) {
opts = opts || {}; opts = opts || {};
this.db = opts.db || levelup(opts.dbPath || './db/copay.db', { valueEncoding: 'json' }); this.db = opts.db || levelup(opts.dbPath || './db/copay.db', { valueEncoding: 'json' });
}; };
Storage.prototype.fetchWallet = function (id, cb) { Storage.prototype.fetchWallet = function (id, cb) {
this.db.get('wallet-' + id, function (err, data) { this.db.get('wallet-' + id, function (err, data) {
if (err) { if (err) {
if (err.notFound) return cb(); if (err.notFound) return cb();
return cb(err); return cb(err);
} }
return cb(null, Wallet.fromObj(data)); return cb(null, Wallet.fromObj(data));
}); });
}; };
Storage.prototype.storeWallet = function (wallet, cb) { Storage.prototype.storeWallet = function (wallet, cb) {
this.db.put('wallet-' + wallet.id, wallet, cb); this.db.put('wallet-' + wallet.id, wallet, cb);
}; };
Storage.prototype.fetchTx = function (walletId, txProposalId, cb) { Storage.prototype.fetchTx = function (walletId, txProposalId, cb) {
this.db.get('wallet-' + walletId + '-txp-' + txProposalId, function (err, data) { this.db.get('wallet-' + walletId + '-txp-' + txProposalId, function (err, data) {
if (err) { if (err) {
if (err.notFound) return cb(); if (err.notFound) return cb();
return cb(err); return cb(err);
} }
return cb(null, TxProposal.fromObj(data)); return cb(null, TxProposal.fromObj(data));
}); });
}; };
Storage.prototype.fetchTxs = function (walletId, cb) { Storage.prototype.fetchTxs = function (walletId, cb) {
var txs = []; var txs = [];
var key = 'wallet-' + walletId + '-txp-'; var key = 'wallet-' + walletId + '-txp-';
this.db.createReadStream({ gte: key, lt: key + '~' }) this.db.createReadStream({ gte: key, lt: key + '~' })
.on('data', function (data) { .on('data', function (data) {
txs.push(TxProposal.fromObj(data.value)); txs.push(TxProposal.fromObj(data.value));
}) })
.on('error', function (err) { .on('error', function (err) {
if (err.notFound) return cb(); if (err.notFound) return cb();
return cb(err); return cb(err);
}) })
.on('end', function () { .on('end', function () {
return cb(null, txs); return cb(null, txs);
}); });
}; };
Storage.prototype.storeTx = function (walletId, txp, 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.txProposalId, txp, cb);
}; };
Storage.prototype.fetchAddresses = function (walletId, cb) { Storage.prototype.fetchAddresses = function (walletId, cb) {
var addresses = []; var addresses = [];
var key = 'wallet-' + walletId + '-address-'; var key = 'wallet-' + walletId + '-address-';
this.db.createReadStream({ gte: key, lt: key + '~' }) this.db.createReadStream({ gte: key, lt: key + '~' })
.on('data', function (data) { .on('data', function (data) {
addresses.push(Address.fromObj(data.value)); addresses.push(Address.fromObj(data.value));
}) })
.on('error', function (err) { .on('error', function (err) {
if (err.notFound) return cb(); if (err.notFound) return cb();
return cb(err); return cb(err);
}) })
.on('end', function () { .on('end', function () {
return cb(null, addresses); return cb(null, addresses);
}); });
}; };
Storage.prototype.storeAddress = function (walletId, address, cb) { Storage.prototype.storeAddress = function (walletId, address, cb) {
this.db.put('wallet-' + walletId + '-address-' + address.address, address, cb); this.db.put('wallet-' + walletId + '-address-' + address.address, address, cb);
}; };

View File

@ -5,24 +5,24 @@ var Lock = require('./lock');
var Utils = {}; var Utils = {};
Utils.runLocked = function (token, cb, task) { Utils.runLocked = function (token, cb, task) {
var self = this; var self = this;
Lock.get(token, function (lock) { Lock.get(token, function (lock) {
var _cb = function () { var _cb = function () {
cb.apply(null, arguments); cb.apply(null, arguments);
lock.free(); lock.free();
}; };
task(_cb); task(_cb);
}); });
}; };
Utils.checkRequired = function (obj, args) { Utils.checkRequired = function (obj, args) {
args = [].concat(args); args = [].concat(args);
if (!_.isObject(obj)) throw 'Required arguments missing'; if (!_.isObject(obj)) throw 'Required arguments missing';
_.each(args, function (arg) { _.each(args, function (arg) {
if (!obj.hasOwnProperty(arg)) throw "Missing required argument '" + arg + "'"; if (!obj.hasOwnProperty(arg)) throw "Missing required argument '" + arg + "'";
}); });
}; };
module.exports = Utils; module.exports = Utils;