Merge pull request #508 from isocolsky/feat/tx-notes

Transaction notes
This commit is contained in:
Matias Alejo Garcia 2016-05-23 11:20:50 -03:00
commit cf7d1cd55f
6 changed files with 699 additions and 219 deletions

View File

@ -514,6 +514,29 @@ ExpressApp.prototype.start = function(opts, cb) {
});
});
router.put('/v1/txnotes/:txid/', function(req, res) {
req.body.txid = req.params['txid'];
getServerWithAuth(req, res, function(server) {
server.editTxNote(req.body, function(err) {
if (err) return returnError(err, res, req);
res.end();
});
});
});
router.get('/v1/txnotes/', function(req, res) {
getServerWithAuth(req, res, function(server) {
var opts = {};
if (_.isNumber(+req.query.minTs)) {
opts.minTs = +req.query.minTs;
}
server.getTxNotes(opts, function(err, notes) {
if (err) return returnError(err, res, req);
res.json(notes);
});
});
});
router.get('/v1/fiatrates/:code/', function(req, res) {
getServerWithAuth(req, res, function(server) {
var opts = {

View File

@ -8,5 +8,6 @@ Model.Address = require('./address');
Model.Notification = require('./notification');
Model.Preferences = require('./preferences');
Model.Email = require('./email');
Model.TxNote = require('./txnote');
module.exports = Model;

48
lib/model/txnote.js Normal file
View File

@ -0,0 +1,48 @@
var _ = require('lodash');
var Uuid = require('uuid');
function TxNote() {};
TxNote.create = function(opts) {
opts = opts || {};
var now = Math.floor(Date.now() / 1000);
var x = new TxNote();
x.version = 1;
x.createdOn = now;
x.walletId = opts.walletId;
x.txid = opts.txid;
x.body = opts.body;
x.editedOn = now;
x.editedBy = opts.copayerId;
return x;
};
TxNote.fromObj = function(obj) {
var x = new TxNote();
x.version = obj.version;
x.createdOn = obj.createdOn;
x.walletId = obj.walletId;
x.txid = obj.txid;
x.body = obj.body;
x.editedOn = obj.editedOn;
x.editedBy = obj.editedBy;
return x;
};
TxNote.prototype.edit = function(body, copayerId) {
this.body = body;
this.editedBy = copayerId;
this.editedOn = Math.floor(Date.now() / 1000);
};
TxNote.prototype.toObject = function() {
return this;
};
module.exports = TxNote;

View File

@ -2043,10 +2043,72 @@ WalletService.prototype.getTx = function(opts, cb) {
self.storage.fetchTx(self.walletId, opts.txProposalId, function(err, txp) {
if (err) return cb(err);
if (!txp) return cb(Errors.TX_NOT_FOUND);
return cb(null, txp);
if (!txp.txid) return cb(null, txp);
self.storage.fetchTxNote(self.walletId, txp.txid, function(err, note) {
if (err) {
log.warn('Error fetching tx note for ' + txp.txid);
}
txp.note = note;
return cb(null, txp);
});
});
};
/**
* Edit note associated to a txid.
* @param {Object} opts
* @param {string} opts.txid - The txid of the tx on the blockchain.
* @param {string} opts.body - The contents of the note.
*/
WalletService.prototype.editTxNote = function(opts, cb) {
var self = this;
if (!checkRequired(opts, 'txid', cb)) return;
self._runLocked(cb, function(cb) {
self.storage.fetchTxNote(self.walletId, opts.txid, function(err, note) {
if (err) return cb(err);
if (!note) {
note = Model.TxNote.create({
walletId: self.walletId,
txid: opts.txid,
copayerId: self.copayerId,
body: opts.body,
});
} else {
note.edit(opts.body, self.copayerId);
}
self.storage.storeTxNote(note, cb);
});
});
};
/**
* Get tx notes.
* @param {Object} opts
* @param {string} opts.txid - The txid associated with the note.
*/
WalletService.prototype.getTxNote = function(opts, cb) {
var self = this;
if (!checkRequired(opts, 'txid', cb)) return;
self.storage.fetchTxNote(self.walletId, opts.txid, cb);
};
/**
* Get tx notes.
* @param {Object} opts
* @param {string} opts.minTs[=0] - The start date used to filter notes.
*/
WalletService.prototype.getTxNotes = function(opts, cb) {
var self = this;
opts = opts || {};
self.storage.fetchTxNotes(self.walletId, opts, cb);
};
/**
* removeWallet
@ -2502,10 +2564,11 @@ WalletService.prototype.getTxHistory = function(opts, cb) {
if (opts.limit > Defaults.HISTORY_LIMIT)
return cb(Errors.HISTORY_LIMIT_EXCEEDED);
function decorate(txs, addresses, proposals) {
function decorate(txs, addresses, proposals, notes) {
var indexedAddresses = _.indexBy(addresses, 'address');
var indexedProposals = _.indexBy(proposals, 'txid');
var indexedNotes = _.indexBy(notes, 'txid');
function sum(items, isMine, isChange) {
var filter = {};
@ -2619,6 +2682,11 @@ WalletService.prototype.getTxHistory = function(opts, cb) {
//newTx.paymentAckMemo = proposal.paymentAckMemo;
}
var note = indexedNotes[tx.txid];
if (note) {
newTx.note = _.pick(note, ['body', 'editedBy', 'editedByName', 'editedOn']);
}
return newTx;
});
};
@ -2635,10 +2703,7 @@ WalletService.prototype.getTxHistory = function(opts, cb) {
async.parallel([
function(next) {
self.storage.fetchTxs(self.walletId, {}, function(err, txps) {
if (err) return next(err);
next(null, txps);
});
self.storage.fetchTxs(self.walletId, {}, next);
},
function(next) {
var from = opts.skip || 0;
@ -2648,13 +2713,17 @@ WalletService.prototype.getTxHistory = function(opts, cb) {
next(null, self._normalizeTxHistory(txs));
});
},
function(next) {
self.storage.fetchTxNotes(self.walletId, {}, next);
},
], function(err, res) {
if (err) return cb(err);
var proposals = res[0];
var txs = res[1];
var notes = res[2];
txs = decorate(txs, addresses, proposals);
txs = decorate(txs, addresses, proposals, notes);
return cb(null, txs);
});

View File

@ -22,6 +22,7 @@ var collections = {
EMAIL_QUEUE: 'email_queue',
CACHE: 'cache',
FIAT_RATES: 'fiat_rates',
TX_NOTES: 'tx_notes',
};
var Storage = function(opts) {
@ -69,6 +70,11 @@ Storage.prototype._createIndexes = function() {
this.db.collection(collections.ADDRESSES).dropIndex({
walletId: 1
});
this.db.collection(collections.TX_NOTES).dropIndex({
walletId: 1,
txid: 1,
});
};
Storage.prototype.connect = function(opts, cb) {
@ -611,6 +617,66 @@ Storage.prototype.fetchFiatRate = function(providerName, code, ts, cb) {
});
};
Storage.prototype.fetchTxNote = function(walletId, txid, cb) {
var self = this;
this.db.collection(collections.TX_NOTES).findOne({
walletId: walletId,
txid: txid,
}, function(err, result) {
if (err) return cb(err);
if (!result || !result.body) return cb();
return cb(null, Model.TxNote.fromObj(result));
});
};
// TODO: should be done client-side
Storage.prototype._completeTxNotesData = function(walletId, notes, cb) {
var notesList = [].concat(notes);
this.fetchWallet(walletId, function(err, wallet) {
if (err) return cb(err);
_.each(notesList, function(note) {
note.editedByName = wallet.getCopayer(note.editedBy).name;
});
return cb(null, notes);
});
};
/**
* fetchTxNotes. Times are in UNIX EPOCH (seconds)
*
* @param walletId
* @param opts.minTs
*/
Storage.prototype.fetchTxNotes = function(walletId, opts, cb) {
var self = this;
var filter = {
walletId: walletId,
};
if (_.isNumber(opts.minTs)) filter.editedOn = {
$gte: opts.minTs
};
this.db.collection(collections.TX_NOTES).find(filter).toArray(function(err, result) {
if (err) return cb(err);
var notes = _.compact(_.map(result, function(note) {
if (!note.body) return;
return Model.TxNote.fromObj(note);
}));
return self._completeTxNotesData(walletId, notes, cb);
});
};
Storage.prototype.storeTxNote = function(txNote, cb) {
this.db.collection(collections.TX_NOTES).update({
txid: txNote.txid,
walletId: txNote.walletId
}, txNote.toObject(), {
w: 1,
upsert: true,
}, cb);
};
Storage.prototype._dump = function(cb, fn) {
fn = fn || console.log;
cb = cb || function() {};

View File

@ -269,6 +269,83 @@ describe('Wallet service', function() {
done();
});
});
describe('Address derivation strategy', function() {
var server;
beforeEach(function() {
server = WalletService.getInstance();
});
it('should use BIP44 & P2PKH for 1-of-1 wallet if supported', function(done) {
var walletOpts = {
name: 'my wallet',
m: 1,
n: 1,
pubKey: TestData.keyPair.pub,
};
server.createWallet(walletOpts, function(err, wid) {
should.not.exist(err);
server.storage.fetchWallet(wid, function(err, wallet) {
should.not.exist(err);
wallet.derivationStrategy.should.equal('BIP44');
wallet.addressType.should.equal('P2PKH');
done();
});
});
});
it('should use BIP45 & P2SH for 1-of-1 wallet if not supported', function(done) {
var walletOpts = {
name: 'my wallet',
m: 1,
n: 1,
pubKey: TestData.keyPair.pub,
supportBIP44AndP2PKH: false,
};
server.createWallet(walletOpts, function(err, wid) {
should.not.exist(err);
server.storage.fetchWallet(wid, function(err, wallet) {
should.not.exist(err);
wallet.derivationStrategy.should.equal('BIP45');
wallet.addressType.should.equal('P2SH');
done();
});
});
});
it('should use BIP44 & P2SH for shared wallet if supported', function(done) {
var walletOpts = {
name: 'my wallet',
m: 2,
n: 3,
pubKey: TestData.keyPair.pub,
};
server.createWallet(walletOpts, function(err, wid) {
should.not.exist(err);
server.storage.fetchWallet(wid, function(err, wallet) {
should.not.exist(err);
wallet.derivationStrategy.should.equal('BIP44');
wallet.addressType.should.equal('P2SH');
done();
});
});
});
it('should use BIP45 & P2SH for shared wallet if supported', function(done) {
var walletOpts = {
name: 'my wallet',
m: 2,
n: 3,
pubKey: TestData.keyPair.pub,
supportBIP44AndP2PKH: false,
};
server.createWallet(walletOpts, function(err, wid) {
should.not.exist(err);
server.storage.fetchWallet(wid, function(err, wallet) {
should.not.exist(err);
wallet.derivationStrategy.should.equal('BIP45');
wallet.addressType.should.equal('P2SH');
done();
});
});
});
});
});
describe('#joinWallet', function() {
@ -619,79 +696,133 @@ describe('Wallet service', function() {
});
});
describe('Address derivation strategy', function() {
var server;
beforeEach(function() {
server = WalletService.getInstance();
});
it('should use BIP44 & P2PKH for 1-of-1 wallet if supported', function(done) {
var walletOpts = {
name: 'my wallet',
m: 1,
n: 1,
pubKey: TestData.keyPair.pub,
};
server.createWallet(walletOpts, function(err, wid) {
should.not.exist(err);
server.storage.fetchWallet(wid, function(err, wallet) {
should.not.exist(err);
wallet.derivationStrategy.should.equal('BIP44');
wallet.addressType.should.equal('P2PKH');
done();
describe('#removeWallet', function() {
var server, wallet, clock;
beforeEach(function(done) {
helpers.createAndJoinWallet(1, 1, function(s, w) {
server = s;
wallet = w;
helpers.stubUtxos(server, wallet, _.range(2), function() {
var txOpts = {
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
amount: helpers.toSatoshi(0.1),
};
async.eachSeries(_.range(2), function(i, next) {
server.createTxLegacy(txOpts, function(err, tx) {
next();
});
}, done);
});
});
});
it('should use BIP45 & P2SH for 1-of-1 wallet if not supported', function(done) {
var walletOpts = {
name: 'my wallet',
m: 1,
n: 1,
pubKey: TestData.keyPair.pub,
supportBIP44AndP2PKH: false,
};
server.createWallet(walletOpts, function(err, wid) {
it('should delete a wallet', function(done) {
server.removeWallet({}, function(err) {
should.not.exist(err);
server.storage.fetchWallet(wid, function(err, wallet) {
should.not.exist(err);
wallet.derivationStrategy.should.equal('BIP45');
wallet.addressType.should.equal('P2SH');
done();
server.getWallet({}, function(err, w) {
should.exist(err);
err.code.should.equal('WALLET_NOT_FOUND');
should.not.exist(w);
async.parallel([
function(next) {
server.storage.fetchAddresses(wallet.id, function(err, items) {
items.length.should.equal(0);
next();
});
},
function(next) {
server.storage.fetchTxs(wallet.id, {}, function(err, items) {
items.length.should.equal(0);
next();
});
},
function(next) {
server.storage.fetchNotifications(wallet.id, null, 0, function(err, items) {
items.length.should.equal(0);
next();
});
},
], function(err) {
should.not.exist(err);
done();
});
});
});
});
it('should use BIP44 & P2SH for shared wallet if supported', function(done) {
var walletOpts = {
name: 'my wallet',
m: 2,
n: 3,
pubKey: TestData.keyPair.pub,
};
server.createWallet(walletOpts, function(err, wid) {
// creates 2 wallet, and deletes only 1.
it('should delete a wallet, and only that wallet', function(done) {
var server2, wallet2;
async.series([
function(next) {
helpers.createAndJoinWallet(1, 1, {
offset: 1
}, function(s, w) {
server2 = s;
wallet2 = w;
helpers.stubUtxos(server2, wallet2, _.range(1, 3), function() {
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.1, TestData.copayers[1].privKey_1H_0, {
message: 'some message'
});
async.eachSeries(_.range(2), function(i, next) {
server2.createTxLegacy(txOpts, function(err, tx) {
should.not.exist(err);
next(err);
});
}, next);
});
});
},
function(next) {
server.removeWallet({}, next);
},
function(next) {
server.getWallet({}, function(err, wallet) {
should.exist(err);
err.code.should.equal('WALLET_NOT_FOUND');
next();
});
},
function(next) {
server2.getWallet({}, function(err, wallet) {
should.not.exist(err);
should.exist(wallet);
wallet.id.should.equal(wallet2.id);
next();
});
},
function(next) {
server2.getMainAddresses({}, function(err, addresses) {
should.not.exist(err);
should.exist(addresses);
addresses.length.should.above(0);
next();
});
},
function(next) {
server2.getTxs({}, function(err, txs) {
should.not.exist(err);
should.exist(txs);
txs.length.should.equal(2);
next();
});
},
function(next) {
server2.getNotifications({}, function(err, notifications) {
should.not.exist(err);
should.exist(notifications);
notifications.length.should.above(0);
next();
});
},
], function(err) {
should.not.exist(err);
server.storage.fetchWallet(wid, function(err, wallet) {
should.not.exist(err);
wallet.derivationStrategy.should.equal('BIP44');
wallet.addressType.should.equal('P2SH');
done();
});
});
});
it('should use BIP45 & P2SH for shared wallet if supported', function(done) {
var walletOpts = {
name: 'my wallet',
m: 2,
n: 3,
pubKey: TestData.keyPair.pub,
supportBIP44AndP2PKH: false,
};
server.createWallet(walletOpts, function(err, wid) {
should.not.exist(err);
server.storage.fetchWallet(wid, function(err, wallet) {
should.not.exist(err);
wallet.derivationStrategy.should.equal('BIP45');
wallet.addressType.should.equal('P2SH');
done();
});
done();
});
});
});
@ -1433,7 +1564,6 @@ describe('Wallet service', function() {
});
});
});
});
});
@ -3687,20 +3817,6 @@ describe('Wallet service', function() {
});
});
});
});
});
describe('#createTx backoff time', function() {
var server, wallet, txid;
beforeEach(function(done) {
helpers.createAndJoinWallet(2, 2, function(s, w) {
server = s;
wallet = w;
helpers.stubUtxos(server, wallet, _.range(2, 6), function() {
done();
});
});
it('should ignore small utxos if fee is higher', function(done) {
helpers.stubUtxos(server, wallet, [].concat(_.times(10, function() {
return '30bit';
@ -3736,6 +3852,294 @@ describe('Wallet service', function() {
});
});
});
});
describe('Transaction notes', function(done) {
var server, wallet;
beforeEach(function(done) {
helpers.createAndJoinWallet(1, 2, function(s, w) {
server = s;
wallet = w;
done();
});
});
it('should edit a note for an arbitrary txid', function(done) {
server.editTxNote({
txid: '123',
body: 'note body'
}, function(err) {
should.not.exist(err);
server.getTxNote({
txid: '123',
}, function(err, note) {
should.not.exist(err);
should.exist(note);
note.txid.should.equal('123');
note.walletId.should.equal(wallet.id);
note.body.should.equal('note body');
note.editedBy.should.equal(server.copayerId);
note.createdOn.should.equal(note.editedOn);
done();
});
});
});
it('should preserve last edit', function(done) {
var clock = sinon.useFakeTimers('Date');
server.editTxNote({
txid: '123',
body: 'note body'
}, function(err) {
should.not.exist(err);
server.getTxNote({
txid: '123',
}, function(err, note) {
should.not.exist(err);
should.exist(note);
note.editedBy.should.equal(server.copayerId);
note.createdOn.should.equal(note.editedOn);
var creator = note.editedBy;
helpers.getAuthServer(wallet.copayers[1].id, function(server) {
clock.tick(60 * 1000);
server.editTxNote({
txid: '123',
body: 'edited text'
}, function(err) {
should.not.exist(err);
server.getTxNote({
txid: '123',
}, function(err, note) {
should.not.exist(err);
should.exist(note);
note.editedBy.should.equal(server.copayerId);
note.createdOn.should.be.below(note.editedOn);
creator.should.not.equal(note.editedBy);
clock.restore();
done();
});
});
});
});
});
});
it('should edit a note for an outgoing tx and retrieve it', function(done) {
helpers.stubUtxos(server, wallet, 2, function() {
var txOpts = {
outputs: [{
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
amount: 1e8,
}],
message: 'some message',
feePerKb: 100e2,
};
helpers.createAndPublishTx(server, txOpts, TestData.copayers[0].privKey_1H_0, function(txp) {
should.exist(txp);
var signatures = helpers.clientSign(txp, TestData.copayers[0].xPrivKey_44H_0H_0H);
server.signTx({
txProposalId: txp.id,
signatures: signatures,
}, function(err, txp) {
should.not.exist(err);
should.exist(txp);
should.exist(txp.txid);
server.editTxNote({
txid: txp.txid,
body: 'note body'
}, function(err) {
should.not.exist(err);
server.getTx({
txProposalId: txp.id,
}, function(err, txp) {
should.not.exist(err);
should.exist(txp.note);
txp.note.txid.should.equal(txp.txid);
txp.note.walletId.should.equal(wallet.id);
txp.note.body.should.equal('note body');
txp.note.editedBy.should.equal(server.copayerId);
done();
});
});
});
});
});
});
it('should share notes between copayers', function(done) {
server.editTxNote({
txid: '123',
body: 'note body'
}, function(err) {
should.not.exist(err);
server.getTxNote({
txid: '123',
}, function(err, note) {
should.not.exist(err);
should.exist(note);
note.editedBy.should.equal(server.copayerId);
var creator = note.editedBy;
helpers.getAuthServer(wallet.copayers[1].id, function(server) {
server.getTxNote({
txid: '123',
}, function(err, note) {
should.not.exist(err);
should.exist(note);
note.body.should.equal('note body');
note.editedBy.should.equal(creator);
done();
});
});
});
});
});
it('should be possible to remove a note', function(done) {
server.editTxNote({
txid: '123',
body: 'note body'
}, function(err) {
should.not.exist(err);
server.getTxNote({
txid: '123',
}, function(err, note) {
should.not.exist(err);
should.exist(note);
server.editTxNote({
txid: '123',
body: null,
}, function(err) {
should.not.exist(err);
server.getTxNote({
txid: '123',
}, function(err, note) {
should.not.exist(err);
should.not.exist(note);
done();
});
});
});
});
});
it('should include the note in tx history listing', function(done) {
helpers.createAddresses(server, wallet, 1, 1, function(mainAddresses, changeAddress) {
server._normalizeTxHistory = sinon.stub().returnsArg(0);
var txs = [{
txid: '123',
confirmations: 1,
fees: 100,
time: 20,
inputs: [{
address: 'external',
amount: 500,
}],
outputs: [{
address: mainAddresses[0].address,
amount: 200,
}],
}];
helpers.stubHistory(txs);
server.editTxNote({
txid: '123',
body: 'just some note'
}, function(err) {
should.not.exist(err);
server.getTxHistory({}, function(err, txs) {
should.not.exist(err);
should.exist(txs);
txs.length.should.equal(1);
var tx = txs[0];
should.exist(tx.note);
tx.note.body.should.equal('just some note');
tx.note.editedBy.should.equal(server.copayerId);
should.exist(tx.note.editedOn);
done();
});
});
});
});
it('should get all notes edited past a given date', function(done) {
var clock = sinon.useFakeTimers('Date');
async.series([
function(next) {
server.getTxNotes({}, function(err, notes) {
should.not.exist(err);
notes.should.be.empty;
next();
});
},
function(next) {
server.editTxNote({
txid: '123',
body: 'note body'
}, next);
},
function(next) {
server.getTxNotes({
minTs: 0,
}, function(err, notes) {
should.not.exist(err);
notes.length.should.equal(1);
notes[0].txid.should.equal('123');
next();
});
},
function(next) {
clock.tick(60 * 1000);
server.editTxNote({
txid: '456',
body: 'another note'
}, next);
},
function(next) {
server.getTxNotes({
minTs: 0,
}, function(err, notes) {
should.not.exist(err);
notes.length.should.equal(2);
_.difference(_.pluck(notes, 'txid'), ['123', '456']).should.be.empty;
next();
});
},
function(next) {
server.getTxNotes({
minTs: 50,
}, function(err, notes) {
should.not.exist(err);
notes.length.should.equal(1);
notes[0].txid.should.equal('456');
next();
});
},
function(next) {
clock.tick(60 * 1000);
server.editTxNote({
txid: '123',
body: 'an edit'
}, next);
},
function(next) {
server.getTxNotes({
minTs: 100,
}, function(err, notes) {
should.not.exist(err);
notes.length.should.equal(1);
notes[0].txid.should.equal('123');
notes[0].body.should.equal('an edit');
next();
});
},
function(next) {
server.getTxNotes({}, function(err, notes) {
should.not.exist(err);
notes.length.should.equal(2);
next();
});
},
], function(err) {
should.not.exist(err);
clock.restore();
done();
});
});
});
describe('#getSendMaxInfo', function() {
@ -5138,137 +5542,6 @@ describe('Wallet service', function() {
});
});
describe('#removeWallet', function() {
var server, wallet, clock;
beforeEach(function(done) {
helpers.createAndJoinWallet(1, 1, function(s, w) {
server = s;
wallet = w;
helpers.stubUtxos(server, wallet, _.range(2), function() {
var txOpts = {
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
amount: helpers.toSatoshi(0.1),
};
async.eachSeries(_.range(2), function(i, next) {
server.createTxLegacy(txOpts, function(err, tx) {
next();
});
}, done);
});
});
});
it('should delete a wallet', function(done) {
server.removeWallet({}, function(err) {
should.not.exist(err);
server.getWallet({}, function(err, w) {
should.exist(err);
err.code.should.equal('WALLET_NOT_FOUND');
should.not.exist(w);
async.parallel([
function(next) {
server.storage.fetchAddresses(wallet.id, function(err, items) {
items.length.should.equal(0);
next();
});
},
function(next) {
server.storage.fetchTxs(wallet.id, {}, function(err, items) {
items.length.should.equal(0);
next();
});
},
function(next) {
server.storage.fetchNotifications(wallet.id, null, 0, function(err, items) {
items.length.should.equal(0);
next();
});
},
], function(err) {
should.not.exist(err);
done();
});
});
});
});
// creates 2 wallet, and deletes only 1.
it('should delete a wallet, and only that wallet', function(done) {
var server2, wallet2;
async.series([
function(next) {
helpers.createAndJoinWallet(1, 1, {
offset: 1
}, function(s, w) {
server2 = s;
wallet2 = w;
helpers.stubUtxos(server2, wallet2, _.range(1, 3), function() {
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.1, TestData.copayers[1].privKey_1H_0, {
message: 'some message'
});
async.eachSeries(_.range(2), function(i, next) {
server2.createTxLegacy(txOpts, function(err, tx) {
should.not.exist(err);
next(err);
});
}, next);
});
});
},
function(next) {
server.removeWallet({}, next);
},
function(next) {
server.getWallet({}, function(err, wallet) {
should.exist(err);
err.code.should.equal('WALLET_NOT_FOUND');
next();
});
},
function(next) {
server2.getWallet({}, function(err, wallet) {
should.not.exist(err);
should.exist(wallet);
wallet.id.should.equal(wallet2.id);
next();
});
},
function(next) {
server2.getMainAddresses({}, function(err, addresses) {
should.not.exist(err);
should.exist(addresses);
addresses.length.should.above(0);
next();
});
},
function(next) {
server2.getTxs({}, function(err, txs) {
should.not.exist(err);
should.exist(txs);
txs.length.should.equal(2);
next();
});
},
function(next) {
server2.getNotifications({}, function(err, notifications) {
should.not.exist(err);
should.exist(notifications);
notifications.length.should.above(0);
next();
});
},
], function(err) {
should.not.exist(err);
done();
});
});
});
describe('#removePendingTx', function() {
var server, wallet, txp;
beforeEach(function(done) {
@ -6409,7 +6682,7 @@ describe('Wallet service', function() {
});
});
describe('Subscribe/unsubscribe', function() {
describe('Push notifications', function() {
var server, wallet;
beforeEach(function(done) {
helpers.createAndJoinWallet(2, 3, function(s, w) {