Merge pull request #19 from matiu/feat/uuid

UUID for copayer, wallet and txproposals
This commit is contained in:
Ivan Socolsky 2015-02-07 15:35:00 -03:00
commit 4ce51b2e39
8 changed files with 118 additions and 146 deletions

View File

@ -1,5 +1,8 @@
- When creating a wallet, the id should be generated by the server and returned to the client to be used as part of the wallet secret.
- Copayer id should be auto-generated.
- Check not blank & length < 100 for both wallet.name & copayer.name
- Proposal with spent input should be tagged as invalid or removed
- Cron job to broadcast accepted txps that failed to broadcast (we may need to track broadcast attempts for this).
- check parameters for KEY at storage

View File

@ -5,7 +5,7 @@ var util = require('util');
var Bitcore = require('bitcore');
var HDPublicKey = Bitcore.HDPublicKey;
var Uuid = require('uuid');
var AddressManager = require('./addressmanager');
@ -16,13 +16,14 @@ function Copayer(opts) {
opts = opts || {};
opts.copayerIndex = opts.copayerIndex || 0;
this.id = Uuid.v4();
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
this.signingPubKey = this.getSigningPubKey();
if (this.xPubKey)
this.signingPubKey = this.getSigningPubKey();
this.addressManager = new AddressManager({ copayerIndex: opts.copayerIndex });
};

View File

@ -1,7 +1,7 @@
'use strict';
var _ = require('lodash');
var Guid = require('guid');
var Uuid = require('uuid');
var Bitcore = require('bitcore');
var Address = Bitcore.Address;
@ -14,7 +14,7 @@ function TxProposal(opts) {
this.version = VERSION;
this.createdOn = Math.floor(Date.now() / 1000);
this.id = Guid.raw();
this.id = ('000000000000' + this.createdOn).slice(-12) + Uuid.v4();
this.creatorId = opts.creatorId;
this.toAddress = opts.toAddress;
this.amount = opts.amount;

View File

@ -5,6 +5,7 @@ var util = require('util');
var Bitcore = require('bitcore');
var BitcoreAddress = Bitcore.Address;
var Uuid = require('uuid');
var Address = require('./address');
var Copayer = require('./copayer');
@ -17,7 +18,7 @@ function Wallet(opts) {
this.version = VERSION;
this.createdOn = Math.floor(Date.now() / 1000);
this.id = opts.id;
this.id = Uuid.v4();
this.name = opts.name;
this.m = opts.m;
this.n = opts.n;
@ -71,7 +72,7 @@ Wallet.fromObj = function(obj) {
x.status = obj.status;
x.publicKeyRing = obj.publicKeyRing;
x.copayers = _.map(obj.copayers, function(copayer) {
return new Copayer(copayer);
return Copayer.fromObj(copayer);
});
x.pubKey = obj.pubKey;
x.isTestnet = obj.isTestnet;

View File

@ -43,7 +43,7 @@ function CopayServer() {
* @param {Object} opts
* @param {Storage} [opts.storage] - The storage provider.
*/
CopayServer.initialize = function (opts) {
CopayServer.initialize = function(opts) {
opts = opts || {};
storage = opts.storage ||  new Storage();
initialized = true;
@ -56,12 +56,12 @@ CopayServer.initialize = function (opts) {
* @param {string} opts.message - The contents of the request to be signed.
* @param {string} opts.signature - Signature of message to be verified using the copayer's signingPubKey.
*/
CopayServer.getInstanceWithAuth = function (opts, cb) {
CopayServer.getInstanceWithAuth = function(opts, cb) {
Utils.checkRequired(opts, ['copayerId', 'message', 'signature']);
var server = new CopayServer();
server.storage.fetchCopayerLookup(opts.copayerId, function (err, copayer) {
server.storage.fetchCopayerLookup(opts.copayerId, function(err, copayer) {
if (err) return cb(err);
if (!copayer) return cb('Copayer not found');
@ -89,10 +89,14 @@ CopayServer.prototype.createWallet = function(opts, cb) {
var self = this,
pubKey;
Utils.checkRequired(opts, ['id', 'name', 'm', 'n', 'pubKey']);
if (!Wallet.verifyCopayerLimits(opts.m, opts.n)) return cb(new ClientError('Invalid combination of required copayers / total copayers'));
Utils.checkRequired(opts, ['name', 'm', 'n', 'pubKey']);
if (!Wallet.verifyCopayerLimits(opts.m, opts.n))
return cb(new ClientError('Invalid combination of required copayers / total copayers'));
var network = opts.network || 'livenet';
if (network != 'livenet' && network != 'testnet') return cb(new ClientError('Invalid network'));
if (network != 'livenet' && network != 'testnet')
return cb(new ClientError('Invalid network'));
try {
pubKey = new PublicKey.fromString(opts.pubKey);
@ -100,20 +104,16 @@ 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(new ClientError('WEXISTS', 'Wallet already exists'));
var wallet = new Wallet({
name: opts.name,
m: opts.m,
n: opts.n,
network: opts.network || 'livenet',
pubKey: pubKey,
});
var wallet = new Wallet({
id: opts.id,
name: opts.name,
m: opts.m,
n: opts.n,
network: opts.network || 'livenet',
pubKey: pubKey,
});
self.storage.storeWallet(wallet, cb);
self.storage.storeWallet(wallet, function(err) {
return cb(err,wallet.id);
});
};
@ -147,7 +147,6 @@ CopayServer.prototype._verifySignature = function(text, signature, pubKey) {
* Joins a wallet in creation.
* @param {Object} opts
* @param {string} opts.walletId - The wallet id.
* @param {string} opts.id - The copayer id.
* @param {string} opts.name - The copayer name.
* @param {number} opts.xPubKey - Extended Public Key for this copayer.
* @param {number} opts.xPubKeySignature - Signature of xPubKey using the wallet pubKey.
@ -155,7 +154,7 @@ CopayServer.prototype._verifySignature = function(text, signature, pubKey) {
CopayServer.prototype.joinWallet = function(opts, cb) {
var self = this;
Utils.checkRequired(opts, ['walletId', 'id', 'name', 'xPubKey', 'xPubKeySignature']);
Utils.checkRequired(opts, ['walletId', 'name', 'xPubKey', 'xPubKeySignature']);
Utils.runLocked(opts.walletId, cb, function(cb) {
self.storage.fetchWallet(opts.walletId, function(err, wallet) {
@ -172,7 +171,6 @@ CopayServer.prototype.joinWallet = function(opts, cb) {
if (wallet.copayers.length == wallet.n) return cb(new ClientError('WFULL', 'Wallet full'));
var copayer = new Copayer({
id: opts.id,
name: opts.name,
xPubKey: opts.xPubKey,
xPubKeySignature: opts.xPubKeySignature,
@ -180,9 +178,8 @@ CopayServer.prototype.joinWallet = function(opts, cb) {
});
wallet.addCopayer(copayer);
self.storage.storeWalletAndUpdateCopayersLookup(wallet, function(err) {
return cb(err);
return cb(err, copayer.id);
});
});
});

View File

@ -29,7 +29,6 @@ var opKeyTs = function(key) {
};
var KEY = {
WALLET: function(id) {
return 'wallet!' + id;
@ -40,11 +39,8 @@ var KEY = {
TXP: function(walletId, txProposalId) {
return 'txp!' + walletId + opKey(txProposalId);
},
TXP_BY_TS: function(walletId, ts, txProposalId) {
return 'txp-ts!' + walletId + opKeyTs(ts) + opKey(txProposalId);
},
PENDING_TXP_BY_TS: function(walletId, ts, txProposalId) {
return 'pending-txp-ts!' + walletId + opKey(ts) + opKey(txProposalId);
PENDING_TXP: function(walletId, txProposalId) {
return 'pending-txp-ts!' + walletId + opKey(txProposalId);
},
ADDRESS: function(walletId, address) {
return 'address!' + walletId + opKey(address);
@ -108,7 +104,7 @@ Storage.prototype.fetchTx = function(walletId, txProposalId, cb) {
Storage.prototype.fetchPendingTxs = function(walletId, cb) {
var txs = [];
var key = KEY.PENDING_TXP_BY_TS(walletId);
var key = KEY.PENDING_TXP(walletId);
this.db.createReadStream({
gte: key,
lt: key + '~'
@ -136,12 +132,12 @@ Storage.prototype.fetchPendingTxs = function(walletId, cb) {
Storage.prototype.fetchTxs = function(walletId, opts, cb) {
var txs = [];
opts = opts || {};
opts.limit = opts.limit || -1;
opts.minTs = opts.minTs || 0;
opts.maxTs = opts.maxTs || MAX_TS;
opts.limit = _.isNumber(opts.limit) ? parseInt(opts.limit) : -1;
opts.minTs = _.isNumber(opts.minTs) ? ('000000000000' + parseInt(opts.minTs)).slice(-12) : 0;
opts.maxTs = _.isNumber(opts.maxTs) ? ('000000000000' + parseInt(opts.maxTs)).slice(-12) : MAX_TS;
var key = KEY.TXP_BY_TS(walletId, opts.minTs);
var endkey = KEY.TXP_BY_TS(walletId, opts.maxTs);
var key = KEY.TXP(walletId, opts.minTs);
var endkey = KEY.TXP(walletId, opts.maxTs);
this.db.createReadStream({
gt: key,
@ -169,22 +165,18 @@ Storage.prototype.storeTx = function(walletId, txp, cb) {
type: 'put',
key: KEY.TXP(walletId, txp.id),
value: txp,
}, {
type: 'put',
key: KEY.TXP_BY_TS(walletId, txp.createdOn, txp.id),
value: txp,
}];
if (txp.isPending()) {
ops.push({
type: 'put',
key: KEY.PENDING_TXP_BY_TS(walletId, txp.createdOn, txp.id),
key: KEY.PENDING_TXP(walletId, txp.id),
value: txp,
});
} else {
ops.push({
type: 'del',
key: KEY.PENDING_TXP_BY_TS(walletId, txp.createdOn, txp.id),
key: KEY.PENDING_TXP(walletId, txp.id),
});
}
this.db.batch(ops, cb);

View File

@ -18,7 +18,7 @@
},
"dependencies": {
"async": "^0.9.0",
"bitcore": "0.9.6",
"bitcore": "*",
"bitcore-explorers": "^0.9.1",
"express": "^4.10.0",
"inherits": "^2.0.1",
@ -27,7 +27,7 @@
"lodash": "^2.4.1",
"npmlog": "^0.1.1",
"preconditions": "^1.0.7",
"guid":"*"
"uuid":"*"
},
"devDependencies": {
"chai": "^1.9.1",

View File

@ -73,36 +73,36 @@ helpers.getAuthServer = function(copayerId, cb) {
});
};
helpers.createAndJoinWallet = function(id, m, n, cb) {
helpers.createAndJoinWallet = function(m, n, cb) {
var server = new CopayServer();
var copayerIds = [];
var walletOpts = {
id: id,
name: id + ' wallet',
name: 'a wallet',
m: m,
n: n,
pubKey: keyPair.pub,
};
server.createWallet(walletOpts, function(err) {
server.createWallet(walletOpts, function(err, walletId) {
if (err) return cb(err);
async.each(_.range(1, n + 1), function(i, cb) {
var copayerOpts = {
walletId: id,
id: '' + i,
walletId: walletId,
name: 'copayer ' + i,
xPubKey: someXPubKeys[i - 1],
xPubKeySignature: someXPubKeysSignatures[i - 1],
};
server.joinWallet(copayerOpts, function(err) {
server.joinWallet(copayerOpts, function(err, copayerId) {
copayerIds.push(copayerId);
return cb(err);
});
}, function(err) {
if (err) return new Error('Could not generate wallet');
helpers.getAuthServer('1', function(s) {
helpers.getAuthServer(copayerIds[0], function(s) {
s.getWallet({}, function(err, w) {
cb(s, w);
});
@ -236,24 +236,24 @@ describe('Copay server', function() {
it('should create and store wallet', function(done) {
var opts = {
id: '123',
name: 'my wallet',
m: 2,
n: 3,
pubKey: aPubKey,
};
server.createWallet(opts, function(err) {
server.createWallet(opts, function(err, walletId) {
should.not.exist(err);
server.storage.fetchWallet('123', function(err, wallet) {
server.storage.fetchWallet(walletId, function(err, wallet) {
should.not.exist(err);
wallet.id.should.equal('123');
wallet.id.should.equal(walletId);
wallet.name.should.equal('my wallet');
done();
});
});
});
it('should fail to recreate existing wallet', function(done) {
// non sense with server generated UUIDs
it.skip('should fail to recreate existing wallet', function(done) {
var opts = {
id: '123',
name: 'my wallet',
@ -322,31 +322,29 @@ describe('Copay server', function() {
it('should join existing wallet', function(done) {
var walletOpts = {
id: '123',
name: 'my wallet',
m: 2,
n: 3,
pubKey: keyPair.pub,
};
server.createWallet(walletOpts, function(err) {
server.createWallet(walletOpts, function(err, walletId) {
should.not.exist(err);
var copayerOpts = {
walletId: '123',
id: '999',
walletId: walletId,
name: 'me',
xPubKey: aXPubKey,
xPubKeySignature: aXPubKeySignature,
};
server.joinWallet(copayerOpts, function(err) {
server.joinWallet(copayerOpts, function(err, copayerId) {
should.not.exist(err);
helpers.getAuthServer('999', function(server) {
helpers.getAuthServer(copayerId, function(server) {
server.getWallet({}, function(err, wallet) {
wallet.id.should.equal('123');
wallet.id.should.equal(walletId);
wallet.copayers.length.should.equal(1);
var copayer = wallet.copayers[0];
copayer.id.should.equal('999');
copayer.name.should.equal('me');
copayer.id.should.equal(copayerId);
done();
});
});
@ -355,59 +353,47 @@ describe('Copay server', function() {
});
it('should fail to join non-existent wallet', function(done) {
var walletOpts = {
id: '123',
name: 'my wallet',
m: 2,
n: 3,
pubKey: aPubKey,
var copayerOpts = {
walletId: '234',
name: 'me',
xPubKey: 'dummy',
xPubKeySignature: 'dummy',
};
server.createWallet(walletOpts, function(err) {
should.not.exist(err);
var copayerOpts = {
walletId: '234',
id: '999',
name: 'me',
xPubKey: 'dummy',
xPubKeySignature: 'dummy',
};
server.joinWallet(copayerOpts, function(err) {
should.exist(err);
done();
});
server.joinWallet(copayerOpts, function(err) {
should.exist(err);
done();
});
});
it('should fail to join full wallet', function(done) {
var walletOpts = {
id: '123',
name: 'my wallet',
m: 1,
n: 1,
pubKey: keyPair.pub,
};
server.createWallet(walletOpts, function(err) {
server.createWallet(walletOpts, function(err,walletId) {
should.not.exist(err);
var copayer1Opts = {
walletId: '123',
walletId: walletId,
id: '111',
name: 'me',
xPubKey: someXPubKeys[0],
xPubKeySignature: someXPubKeysSignatures[0],
};
var copayer2Opts = {
walletId: '123',
walletId: walletId,
id: '222',
name: 'me 2',
xPubKey: someXPubKeys[1],
xPubKeySignature: someXPubKeysSignatures[1],
};
server.joinWallet(copayer1Opts, function(err) {
server.joinWallet(copayer1Opts, function(err, copayer1Id) {
should.not.exist(err);
helpers.getAuthServer('111', function(server) {
helpers.getAuthServer(copayer1Id, function(server) {
server.getWallet({}, function(err, wallet) {
wallet.status.should.equal('complete');
server.joinWallet(copayer2Opts, function(err) {
server.joinWallet(copayer2Opts, function(err, copayer2Id) {
should.exist(err);
err.code.should.equal('WFULL');
err.message.should.equal('Wallet full');
@ -421,16 +407,15 @@ describe('Copay server', function() {
it('should fail to re-join wallet', function(done) {
var walletOpts = {
id: '123',
name: 'my wallet',
m: 1,
n: 1,
pubKey: keyPair.pub,
};
server.createWallet(walletOpts, function(err) {
server.createWallet(walletOpts, function(err, walletId) {
should.not.exist(err);
var copayerOpts = {
walletId: '123',
walletId: walletId,
id: '111',
name: 'me',
xPubKey: someXPubKeys[0],
@ -457,11 +442,10 @@ describe('Copay server', function() {
n: 1,
pubKey: aPubKey,
};
server.createWallet(walletOpts, function(err) {
server.createWallet(walletOpts, function(err, walletId) {
should.not.exist(err);
var copayerOpts = {
walletId: '123',
id: '111',
walletId: walletId,
name: 'me',
xPubKey: someXPubKeys[0],
xPubKeySignature: 'bad sign',
@ -507,11 +491,10 @@ describe('Copay server', function() {
n: 1,
pubKey: aPubKey,
};
server.createWallet(walletOpts, function(err) {
server.createWallet(walletOpts, function(err, walletId) {
should.not.exist(err);
var copayerOpts = {
walletId: '123',
id: '111',
walletId: walletId,
name: 'me',
xPubKey: someXPubKeys[0],
xPubKeySignature: someXPubKeysSignatures[0],
@ -524,7 +507,7 @@ describe('Copay server', function() {
});
it('should set pkr and status = complete on last copayer joining (2-3)', function(done) {
helpers.createAndJoinWallet('123', 2, 3, function(server) {
helpers.createAndJoinWallet(2, 3, function(server) {
server.getWallet({}, function(err, wallet) {
should.not.exist(err);
wallet.status.should.equal('complete');
@ -539,7 +522,7 @@ describe('Copay server', function() {
describe('#verifyMessageSignature', function() {
var server, wallet;
beforeEach(function(done) {
helpers.createAndJoinWallet('123', 2, 2, function(s, w) {
helpers.createAndJoinWallet(2, 2, function(s, w) {
server = s;
wallet = w;
done();
@ -563,7 +546,7 @@ describe('Copay server', function() {
message: aText,
signature: aTextSignature,
};
helpers.getAuthServer('2', function (server) {
helpers.getAuthServer(wallet.copayers[1].id, function(server) {
server.verifyMessageSignature(opts, function(err, isValid) {
should.not.exist(err);
isValid.should.be.false;
@ -576,7 +559,7 @@ describe('Copay server', function() {
describe('#createAddress', function() {
var server, wallet;
beforeEach(function(done) {
helpers.createAndJoinWallet('123', 2, 2, function(s, w) {
helpers.createAndJoinWallet(2, 2, function(s, w) {
server = s;
wallet = w;
done();
@ -608,8 +591,7 @@ describe('Copay server', function() {
});
});
it.skip('should fail to create address when wallet is not complete', function(done) {
});
it.skip('should fail to create address when wallet is not complete', function(done) {});
it('should create many addresses on simultaneous requests', function(done) {
async.map(_.range(10), function(i, cb) {
@ -684,7 +666,7 @@ describe('Copay server', function() {
describe('#createTx', function() {
var server, wallet;
beforeEach(function(done) {
helpers.createAndJoinWallet('123', 2, 2, function(s, w) {
helpers.createAndJoinWallet(2, 2, function(s, w) {
server = s;
wallet = w;
server.createAddress({
@ -726,11 +708,7 @@ describe('Copay server', function() {
});
});
it.skip('should fail to create tx when wallet is not complete', function(done) {
});
it.skip('should fail to create tx when wallet is not complete', function(done) {
});
it.skip('should fail to create tx when wallet is not complete', function(done) {});
it('should fail to create tx when insufficient funds', function(done) {
helpers.createUtxos(server, wallet, helpers.toSatoshi([100]), function(utxos) {
@ -845,7 +823,7 @@ describe('Copay server', function() {
var server, wallet, txid;
beforeEach(function(done) {
helpers.createAndJoinWallet('123', 2, 2, function(s, w) {
helpers.createAndJoinWallet(2, 2, function(s, w) {
server = s;
wallet = w;
server.createAddress({
@ -904,7 +882,7 @@ describe('Copay server', function() {
});
});
});
it('should fail on invalid signature', function(done) {
it('should fail on invalid signature', function(done) {
server.getPendingTxs({}, function(err, txs) {
var tx = txs[0];
tx.id.should.equal(txid);
@ -927,7 +905,7 @@ describe('Copay server', function() {
var server, wallet, utxos;
beforeEach(function(done) {
helpers.createAndJoinWallet('123', 1, 1, function(s, w) {
helpers.createAndJoinWallet(1, 1, function(s, w) {
server = s;
wallet = w;
server.createAddress({
@ -1016,26 +994,26 @@ describe('Copay server', function() {
});
describe('Multisignature wallet', function() {
it.skip('all copayers should see pending proposal created by one copayer', function (done) {
});
it.skip('all copayers should see pending proposal created by one copayer', function(done) {});
it.skip('tx proposals should not be broadcast until quorum is reached', function (done) {
});
it.skip('tx proposals should not be broadcast until quorum is reached', function(done) {});
it.skip('tx proposals should accept as many rejections as possible without finally rejecting', function (done) {
});
it.skip('tx proposals should accept as many rejections as possible without finally rejecting', function(done) {});
it.skip('proposal creator should be able to delete proposal if there are no other signatures', function (done) {
});
it.skip('proposal creator should be able to delete proposal if there are no other signatures', function(done) {});
});
describe('#getTxs', function() {
var server, wallet, clock;
beforeEach(function(done) {
if (server)
return done();
this.timeout(5000);
console.log('\tCreating TXS...');
clock = sinon.useFakeTimers();
helpers.createAndJoinWallet('123', 1, 1, function(s, w) {
helpers.createAndJoinWallet(1, 1, function(s, w) {
server = s;
wallet = w;
server.createAddress({
@ -1052,8 +1030,9 @@ describe('Copay server', function() {
server.createTx(txOpts, function(err, tx) {
next();
});
},
done
}, function(err) {
return done(err);
}
);
});
});
@ -1069,8 +1048,8 @@ describe('Copay server', function() {
limit: 8
}, function(err, txps) {
should.not.exist(err);
var times = _.pluck(txps,'createdOn');
times.should.deep.equal([90,80,70,60]);
var times = _.pluck(txps, 'createdOn');
times.should.deep.equal([90, 80, 70, 60]);
done();
});
});
@ -1081,8 +1060,8 @@ describe('Copay server', function() {
limit: 5
}, function(err, txps) {
should.not.exist(err);
var times = _.pluck(txps,'createdOn');
times.should.deep.equal([50,40,30,20,10]);
var times = _.pluck(txps, 'createdOn');
times.should.deep.equal([50, 40, 30, 20, 10]);
done();
});
});
@ -1092,18 +1071,17 @@ describe('Copay server', function() {
limit: 4
}, function(err, txps) {
should.not.exist(err);
var times = _.pluck(txps,'createdOn');
times.should.deep.equal([90,80,70,60]);
var times = _.pluck(txps, 'createdOn');
times.should.deep.equal([90, 80, 70, 60]);
done();
});
});
it('should pull all txs', function(done) {
server.getTxs({
}, function(err, txps) {
server.getTxs({}, function(err, txps) {
should.not.exist(err);
var times = _.pluck(txps,'createdOn');
times.should.deep.equal([90,80,70,60,50,40,30,20,10]);
var times = _.pluck(txps, 'createdOn');
times.should.deep.equal([90, 80, 70, 60, 50, 40, 30, 20, 10]);
done();
});
});
@ -1115,8 +1093,8 @@ describe('Copay server', function() {
maxTs: 70,
}, function(err, txps) {
should.not.exist(err);
var times = _.pluck(txps,'createdOn');
times.should.deep.equal([70,60,50]);
var times = _.pluck(txps, 'createdOn');
times.should.deep.equal([70, 60, 50]);
done();
});
});