@abcpros/bitcore-wallet-service
Version:
A service for Mutisig HD Bitcoin Wallets
426 lines (388 loc) • 13.9 kB
JavaScript
;
var _ = require('lodash');
var async = require('async');
var chai = require('chai');
var mongodb = require('mongodb');
var should = chai.should();
var { Storage } = require('../ts_build/lib/storage');
var Model = require('../ts_build/lib/model');
var config = require('./test-config');
var helpers = require('./integration/helpers');
var db, client, storage;
function resetDb(cb) {
if (!client) return cb();
let db1 = client.db(config.mongoDb.dbname);
db1.dropDatabase(function(err) {
return cb();
});
};
describe('Storage', function() {
before(function(done) {
mongodb.MongoClient.connect(config.mongoDb.uri,{ useUnifiedTopology: true }, function(err, inclient) {
if (err) throw err;
client = inclient;
let db1 = client.db(config.mongoDb.dbname);
storage = new Storage({
db: db1,
});
done();
});
});
beforeEach(function(done) {
resetDb(done);
});
describe('Store & fetch wallet', function() {
it('should correctly store and fetch wallet', function(done) {
var wallet = Model.Wallet.create({
id: '123',
name: 'my wallet',
m: 2,
n: 3,
coin: 'btc',
network: 'livenet',
});
should.exist(wallet);
storage.storeWallet(wallet, function(err) {
should.not.exist(err);
storage.fetchWallet('123', function(err, w) {
should.not.exist(err);
should.exist(w);
w.id.should.equal(wallet.id);
w.name.should.equal(wallet.name);
w.m.should.equal(wallet.m);
w.n.should.equal(wallet.n);
done();
})
});
});
it('should not return error if wallet not found', function(done) {
storage.fetchWallet('123', function(err, w) {
should.not.exist(err);
should.not.exist(w);
done();
});
});
});
describe('Copayer lookup', function() {
it('should correctly store and fetch copayer lookup', function(done) {
var wallet = Model.Wallet.create({
id: '123',
name: 'my wallet',
m: 2,
n: 3,
coin: 'btc',
network: 'livenet',
});
_.each(_.range(3), function(i) {
var copayer = Model.Copayer.create({
coin: 'btc',
name: 'copayer ' + i,
xPubKey: 'xPubKey ' + i,
requestPubKey: 'requestPubKey ' + i,
signature: 'xxx',
});
wallet.addCopayer(copayer);
});
should.exist(wallet);
storage.storeWalletAndUpdateCopayersLookup(wallet, function(err) {
should.not.exist(err);
storage.fetchCopayerLookup(wallet.copayers[1].id, function(err, lookup) {
should.not.exist(err);
should.exist(lookup);
lookup.walletId.should.equal('123');
lookup.requestPubKeys[0].key.should.equal('requestPubKey 1');
lookup.requestPubKeys[0].signature.should.equal('xxx');
done();
})
});
});
it('should not return error if copayer not found', function(done) {
storage.fetchCopayerLookup('2', function(err, lookup) {
should.not.exist(err);
should.not.exist(lookup);
done();
});
});
});
describe('Advertisments', function() {
// not yet implemented
});
describe('Transaction proposals', function() {
var wallet, proposals;
beforeEach(function(done) {
wallet = Model.Wallet.create({
id: '123',
name: 'my wallet',
m: 2,
n: 3,
coin: 'btc',
network: 'livenet',
});
_.each(_.range(3), function(i) {
var copayer = Model.Copayer.create({
coin: 'btc',
name: 'copayer ' + i,
xPubKey: 'xPubKey ' + i,
requestPubKey: 'requestPubKey ' + i,
signature: 'signarture ' + i,
});
wallet.addCopayer(copayer);
});
should.exist(wallet);
storage.storeWalletAndUpdateCopayersLookup(wallet, function(err) {
should.not.exist(err);
proposals = _.map(_.range(4), function(i) {
var tx = Model.TxProposal.create({
walletId: '123',
coin: 'btc',
network: 'livenet',
outputs: [{
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
amount: i + 100,
}],
feePerKb: 100e2,
creatorId: wallet.copayers[0].id,
});
if (i % 2 == 0) {
tx.status = 'pending';
tx.isPending().should.be.true;
} else {
tx.status = 'rejected';
tx.isPending().should.be.false;
}
tx.txid = 'txid' + i;
return tx;
});
async.each(proposals, function(tx, next) {
storage.storeTx('123', tx, next);
}, function(err) {
should.not.exist(err);
done();
});
});
});
it('should fetch tx', function(done) {
storage.fetchTx('123', proposals[0].id, function(err, tx) {
should.not.exist(err);
should.exist(tx);
tx.id.should.equal(proposals[0].id);
tx.walletId.should.equal(proposals[0].walletId);
tx.creatorName.should.equal('copayer 0');
done();
});
});
it('should fetch tx by hash', function(done) {
storage.fetchTxByHash('txid0', function(err, tx) {
should.not.exist(err);
should.exist(tx);
tx.id.should.equal(proposals[0].id);
tx.walletId.should.equal(proposals[0].walletId);
tx.creatorName.should.equal('copayer 0');
done();
});
});
it('should fetch all pending txs', function(done) {
storage.fetchPendingTxs('123', function(err, txs) {
should.not.exist(err);
should.exist(txs);
txs.length.should.equal(2);
txs = _.sortBy(txs, 'amount');
txs[0].amount.should.equal(100);
txs[1].amount.should.equal(102);
done();
});
});
it('should remove tx', function(done) {
storage.removeTx('123', proposals[0].id, function(err) {
should.not.exist(err);
storage.fetchTx('123', proposals[0].id, function(err, tx) {
should.not.exist(err);
should.not.exist(tx);
storage.fetchTxs('123', {}, function(err, txs) {
should.not.exist(err);
should.exist(txs);
txs.length.should.equal(3);
_.some(txs, {
id: proposals[0].id
}).should.be.false;
done();
});
});
});
});
});
describe('History Cache v8', () => {
it('should fail is TX does not have blochchain height', (done) => {
let tipIndex = 80; // current cache tip
let items = [{ txid: '1234' }]; // a single tx.
let updateHeight = 1000;
storage.storeTxHistoryCacheV8('xx', tipIndex, items, updateHeight, (err) => {
err.toString().should.contain('missing blockheight');
done();
});
});
it('should store a single tx on the cache and update status correctly', (done) => {
let tipIndex = 80; // current cache tip
let items = [{ txid: '1234', blockheight: 800 }]; // a single tx.
let updateHeight = 1000;
storage.storeTxHistoryCacheV8('xx', tipIndex, items, updateHeight, (err) => {
should.not.exist(err);
storage.getTxHistoryCacheStatusV8('xx', (err, inCacheStatus) => {
should.not.exist(err);
inCacheStatus.tipIndex.should.equal(81);
inCacheStatus.tipTxId.should.equal('1234');
inCacheStatus.tipHeight.should.equal(800);
inCacheStatus.updatedHeight.should.equal(1000);
done();
});
});
});
it('should store a 5 txs on the cache and update status correctly', (done) => {
let tipIndex = 80; // current cache tip
let items = [
{ txid: '1234', blockheight: 803 }, /// <=== Latests
{ txid: '1235', blockheight: 802 },
{ txid: '1236', blockheight: 801 },
{ txid: '1237', blockheight: 801 },
{ txid: '1238', blockheight: 800 },
];
let updateHeight = 1000;
storage.storeTxHistoryCacheV8('xx', tipIndex, items, updateHeight, (err) => {
should.not.exist(err);
storage.getTxHistoryCacheStatusV8('xx', (err, inCacheStatus) => {
should.not.exist(err);
inCacheStatus.tipIndex.should.equal(85);
inCacheStatus.tipTxId.should.equal('1234');
inCacheStatus.tipHeight.should.equal(803);
inCacheStatus.updatedHeight.should.equal(1000);
done();
});
});
});
it('should prevent to store txs on wrong order', (done) => {
let tipIndex = 80; // current cache tip
let items = [
{ txid: '1234', blockheight: 803 }, /// <=== Latests
{ txid: '1235', blockheight: 802 },
{ txid: '1236', blockheight: 801 },
{ txid: '1237', blockheight: 801 },
{ txid: '1238', blockheight: 800 },
];
let updateHeight = 1000;
storage.storeTxHistoryCacheV8('xx', tipIndex, items.reverse(), updateHeight, (err) => {
err.toString().should.contain('wrong order');
done();
});
});
it('should store a 100 txs on the cache and update status correctly', (done) => {
let tipIndex = 80; // current cache tip
let items = helpers.createTxsV8(101, 1000);
// this is done by _normalizeV8TxHistory
_.each(items, (x) => { x.blockheight = x.height; });
// remove unconfirmed
items.shift();
let updateHeight = 50000;
storage.storeTxHistoryCacheV8('xx', tipIndex, items, updateHeight, (err) => {
should.not.exist(err);
storage.getTxHistoryCacheStatusV8('xx', (err, inCacheStatus) => {
should.not.exist(err);
inCacheStatus.tipIndex.should.equal(80 + 100);
inCacheStatus.tipTxId.should.equal('txid1');
inCacheStatus.tipHeight.should.equal(1000);
inCacheStatus.updatedHeight.should.equal(updateHeight);
done();
});
});
});
it('should store a 1tx on the cache and retreive them correctly', (done) => {
let tipIndex = 80; // current cache tip
let items = [{ txid: '1234', blockheight: 800, amount: 100 }]; // a single tx.
let updateHeight = 1000;
storage.storeTxHistoryCacheV8('xx', tipIndex, items, updateHeight, (err) => {
should.not.exist(err);
storage.getTxHistoryCacheV8('xx', 0, 5, (err, txs) => {
should.not.exist(err);
txs.length.should.equal(1);
txs[0].blockheight.should.equal(800);
done();
});
});
});
it('should clear all cache on deregistration', (done) => {
let tipIndex = 80; // current cache tip
let items = [{ txid: '1234', blockheight: 800, amount: 100 }]; // a single tx.
let updateHeight = 1000;
storage.storeTxHistoryCacheV8('xx', tipIndex, items, updateHeight, (err) => {
should.not.exist(err);
storage.deregisterWallet('xx', (err) => {
should.not.exist(err);
storage.getTxHistoryCacheV8('xx', 0, 5, (err, txs) => {
should.not.exist(err);
txs.length.should.equal(0);
done();
});
});
});
});
it('should store a 5 txs on the cache and retreive them correctly', (done) => {
let tipIndex = 80; // current cache tip
let items = [
{ txid: '1234', blockheight: 803 }, /// <=== Latests
{ txid: '1235', blockheight: 802 },
{ txid: '1236', blockheight: 801 },
{ txid: '1237', blockheight: 800 },
{ txid: '1238', blockheight: 800 },
];
let updateHeight = 1000;
storage.storeTxHistoryCacheV8('xx', tipIndex, items, updateHeight, (err) => {
should.not.exist(err);
storage.getTxHistoryCacheV8('xx', 0, 5, (err, txs) => {
should.not.exist(err);
txs.length.should.equal(5);
txs[0].blockheight.should.equal(803);
txs[4].blockheight.should.equal(800);
txs[4].txid.should.equal('1238'); //should preserve order
done();
});
});
});
it('should store a 10 txs on the cache and retreive them correctly', (done) => {
let tipIndex = 80; // current cache tip
let items = [
{ txid: '1234', blockheight: 803 }, /// <=== Latests
{ txid: '1235', blockheight: 802 },
{ txid: '1236', blockheight: 801 },
{ txid: '1237', blockheight: 800 },
{ txid: '1238', blockheight: 800 },
];
let updateHeight = 1000;
storage.storeTxHistoryCacheV8('xx', tipIndex, items, updateHeight, (err) => {
should.not.exist(err);
// time passes
updateHeight = 2000;
let items2 = [
{ txid: '124', blockheight: 1803 }, /// <=== Latests
{ txid: '125', blockheight: 1802 },
{ txid: '126', blockheight: 1801 },
{ txid: '127', blockheight: 1800 },
{ txid: '128', blockheight: 1800 },
];
storage.getTxHistoryCacheStatusV8('xx', (err, inCacheStatus) => {
should.not.exist(err);
inCacheStatus.tipIndex.should.equal(85);
storage.storeTxHistoryCacheV8('xx', inCacheStatus.tipIndex, items2, updateHeight, (err) => {
should.not.exist(err);
storage.getTxHistoryCacheV8('xx', 0, 100, (err, txs) => {
should.not.exist(err);
txs.length.should.equal(10);
txs[0].blockheight.should.equal(1803);
txs[9].blockheight.should.equal(800);
txs[9].txid.should.equal('1238'); //should preserve order
done();
});
});
});
});
});
});
});