Browse Source

change indentation

activeAddress
Ivan Socolsky 10 years ago
parent
commit
399c412cde
  1. 36
      lib/lock.js
  2. 18
      lib/model/address.js
  3. 32
      lib/model/copayer.js
  4. 106
      lib/model/txproposal.js
  5. 22
      lib/model/txproposalaction.js
  6. 60
      lib/model/wallet.js
  7. 460
      lib/server.js
  8. 90
      lib/storage.js
  9. 26
      lib/utils.js

36
lib/lock.js

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

18
lib/model/address.js

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

32
lib/model/copayer.js

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

106
lib/model/txproposal.js

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

22
lib/model/txproposalaction.js

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

60
lib/model/wallet.js

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

460
lib/server.js

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

90
lib/storage.js

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

26
lib/utils.js

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

Loading…
Cancel
Save