bitcore-wallet-service
Version:
A service for Mutisig HD Bitcoin Wallets
1,398 lines • 45.4 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Storage = void 0;
const async = __importStar(require("async"));
const lodash_1 = __importDefault(require("lodash"));
const mongodb = __importStar(require("mongodb"));
const bchaddresstranslator_1 = require("./bchaddresstranslator");
const common_1 = require("./common");
const logger_1 = __importDefault(require("./logger"));
const model_1 = require("./model");
const $ = require('preconditions').singleton();
const collections = {
WALLETS: 'wallets',
TXS: 'txs',
ADDRESSES: 'addresses',
ADVERTISEMENTS: 'advertisements',
NOTIFICATIONS: 'notifications',
COPAYERS_LOOKUP: 'copayers_lookup',
PREFERENCES: 'preferences',
EMAIL_QUEUE: 'email_queue',
CACHE: 'cache',
FIAT_RATES2: 'fiat_rates2',
TX_NOTES: 'tx_notes',
SESSIONS: 'sessions',
PUSH_NOTIFICATION_SUBS: 'push_notification_subs',
TX_CONFIRMATION_SUBS: 'tx_confirmation_subs',
LOCKS: 'locks'
};
const Defaults = common_1.Common.Defaults;
const Utils = common_1.Common.Utils;
const ObjectID = mongodb.ObjectID;
var objectIdDate = function (date) {
return Math.floor(date / 1000).toString(16) + '0000000000000000';
};
class Storage {
constructor(opts = {}) {
this.walletCheck = async (params) => {
const { walletId } = params;
return new Promise(resolve => {
const addressStream = this.db.collection(collections.ADDRESSES).find({ walletId });
let sum = 0;
let lastAddress;
addressStream.on('data', walletAddress => {
if (walletAddress.address) {
lastAddress = walletAddress.address.replace(/:.*$/, '');
const addressSum = Buffer.from(lastAddress).reduce((tot, cur) => (tot + cur) % Number.MAX_SAFE_INTEGER);
sum = (sum + addressSum) % Number.MAX_SAFE_INTEGER;
}
});
addressStream.on('end', () => {
resolve({ lastAddress, sum });
});
});
};
opts = opts || {};
this.db = opts.db;
}
static createIndexes(db) {
logger_1.default.info('Creating DB indexes');
if (!db.collection) {
logger_1.default.error('DB not ready: [storage.ts] no db.collection');
return;
}
db.collection(collections.WALLETS).createIndex({
id: 1
});
db.collection(collections.COPAYERS_LOOKUP).createIndex({
copayerId: 1
});
db.collection(collections.COPAYERS_LOOKUP).createIndex({
walletId: 1
});
db.collection(collections.TXS).createIndex({
walletId: 1,
id: 1
});
db.collection(collections.TXS).createIndex({
walletId: 1,
isPending: 1,
txid: 1
});
db.collection(collections.TXS).createIndex({
walletId: 1,
createdOn: -1
});
db.collection(collections.TXS).createIndex({
txid: 1
});
db.collection(collections.NOTIFICATIONS).createIndex({
walletId: 1,
id: 1
});
db.collection(collections.ADVERTISEMENTS).createIndex({
advertisementId: 1,
title: 1
}, { unique: true });
db.collection(collections.ADDRESSES).createIndex({
walletId: 1,
createdOn: 1
});
db.collection(collections.ADDRESSES).createIndex({
address: 1,
coin: 1
}, { unique: true });
db.collection(collections.ADDRESSES).createIndex({
address: 1,
beRegistered: 1
});
db.collection(collections.ADDRESSES).createIndex({
walletId: 1,
address: 1
});
db.collection(collections.EMAIL_QUEUE).createIndex({
id: 1
});
db.collection(collections.EMAIL_QUEUE).createIndex({
notificationId: 1
});
db.collection(collections.CACHE).createIndex({
walletId: 1,
type: 1,
key: 1
});
db.collection(collections.TX_NOTES).createIndex({
walletId: 1,
txid: 1
});
db.collection(collections.PREFERENCES).createIndex({
walletId: 1
});
db.collection(collections.FIAT_RATES2).createIndex({
coin: 1,
code: 1,
ts: 1
});
db.collection(collections.PUSH_NOTIFICATION_SUBS).createIndex({
copayerId: 1
});
db.collection(collections.TX_CONFIRMATION_SUBS).createIndex({
copayerId: 1,
txid: 1
});
db.collection(collections.TX_CONFIRMATION_SUBS).createIndex({
isActive: 1,
txid: 1,
copayerId: 1
});
db.collection(collections.SESSIONS).createIndex({
copayerId: 1
});
}
connect(opts, cb) {
opts = opts || {};
if (this.db)
return cb();
const config = opts.mongoDb || {};
if (opts.secondaryPreferred) {
if (config.uri.indexOf('?') > 0) {
config.uri = config.uri + '&';
}
else {
config.uri = config.uri + '?';
}
config.uri = config.uri + 'readPreference=secondaryPreferred';
logger_1.default.info('Read operations set to secondaryPreferred');
}
if (!config.dbname) {
logger_1.default.error('No dbname at config.');
return cb(new Error('No dbname at config.'));
}
mongodb.MongoClient.connect(config.uri, { useUnifiedTopology: true }, (err, client) => {
if (err) {
logger_1.default.error('Unable to connect to the mongoDB. Check the credentials.');
return cb(err);
}
this.db = client.db(config.dbname);
this.client = client;
logger_1.default.info(`Connection established to db: ${config.uri}`);
Storage.createIndexes(this.db);
return cb();
});
}
disconnect(cb) {
if (this.client) {
this.client.close(err => {
if (err)
return cb(err);
this.db = null;
this.client = null;
return cb();
});
}
else {
return cb();
}
}
fetchWallet(id, cb) {
if (!this.db)
return cb('not ready');
this.db.collection(collections.WALLETS).findOne({
id
}, (err, result) => {
if (err)
return cb(err);
if (!result)
return cb();
return cb(null, model_1.Wallet.fromObj(result));
});
}
storeWallet(wallet, cb) {
this.db.collection(collections.WALLETS).replaceOne({
id: wallet.id
}, wallet.toObject(), {
w: 1,
upsert: true
}, cb);
}
storeWalletAndUpdateCopayersLookup(wallet, cb) {
const copayerLookups = lodash_1.default.map(wallet.copayers, copayer => {
try {
$.checkState(copayer.requestPubKeys, 'Failed state: copayer.requestPubkeys undefined at <storeWalletAndUpdateCopayersLookup()>');
}
catch (e) {
return cb(e);
}
return {
copayerId: copayer.id,
walletId: wallet.id,
requestPubKeys: copayer.requestPubKeys
};
});
this.db.collection(collections.COPAYERS_LOOKUP).deleteMany({
walletId: wallet.id
}, {
w: 1
}, err => {
if (err)
return cb(err);
this.db.collection(collections.COPAYERS_LOOKUP).insertMany(copayerLookups, {
w: 1
}, err => {
if (err)
return cb(err);
return this.storeWallet(wallet, cb);
});
});
}
fetchCopayerLookup(copayerId, cb) {
this.db.collection(collections.COPAYERS_LOOKUP).findOne({
copayerId
}, (err, result) => {
if (err)
return cb(err);
if (!result)
return cb();
if (!result.requestPubKeys) {
result.requestPubKeys = [
{
key: result.requestPubKey,
signature: result.signature
}
];
}
return cb(null, result);
});
}
_completeTxData(walletId, txs, cb) {
this.fetchWallet(walletId, (err, wallet) => {
if (err)
return cb(err);
lodash_1.default.each([].concat(txs), tx => {
tx.derivationStrategy = wallet.derivationStrategy || 'BIP45';
tx.creatorName = wallet.getCopayer(tx.creatorId).name;
lodash_1.default.each(tx.actions, action => {
action.copayerName = wallet.getCopayer(action.copayerId).name;
});
if (tx.status == 'accepted')
tx.raw = tx.getRawTx();
});
return cb(null, txs);
});
}
fetchTx(walletId, txProposalId, cb) {
if (!this.db)
return cb();
this.db.collection(collections.TXS).findOne({
id: txProposalId,
walletId
}, (err, result) => {
if (err)
return cb(err);
if (!result)
return cb();
return this._completeTxData(walletId, model_1.TxProposal.fromObj(result), cb);
});
}
fetchTxByHash(hash, cb) {
if (!this.db)
return cb();
this.db.collection(collections.TXS).findOne({
txid: hash
}, (err, result) => {
if (err)
return cb(err);
if (!result)
return cb();
return this._completeTxData(result.walletId, model_1.TxProposal.fromObj(result), cb);
});
}
fetchLastTxs(walletId, creatorId, limit, cb) {
this.db
.collection(collections.TXS)
.find({
walletId,
creatorId
}, {
limit: limit || 5
})
.sort({
createdOn: -1
})
.toArray((err, result) => {
if (err)
return cb(err);
if (!result)
return cb();
const txs = lodash_1.default.map(result, tx => {
return model_1.TxProposal.fromObj(tx);
});
return cb(null, txs);
});
}
fetchEthPendingTxs(multisigTxpsInfo) {
return new Promise((resolve, reject) => {
this.db
.collection(collections.TXS)
.find({
txid: { $in: multisigTxpsInfo.map(txpInfo => txpInfo.transactionHash) }
})
.sort({
createdOn: -1
})
.toArray(async (err, result) => {
if (err)
return reject(err);
if (!result)
return reject();
const multisigTxpsInfoByTransactionHash = lodash_1.default.groupBy(multisigTxpsInfo, 'transactionHash');
const actionsById = {};
const txs = lodash_1.default.compact(lodash_1.default.map(result, tx => {
if (!tx.multisigContractAddress) {
return undefined;
}
tx.status = 'pending';
tx.multisigTxId = multisigTxpsInfoByTransactionHash[tx.txid][0].transactionId;
tx.actions.forEach(action => {
if (lodash_1.default.some(multisigTxpsInfoByTransactionHash[tx.txid], { event: 'ExecutionFailure' })) {
action.type = 'failed';
}
});
if (tx.amount === 0) {
actionsById[tx.multisigTxId] = [...tx.actions, ...(actionsById[tx.multisigTxId] || [])];
return undefined;
}
return model_1.TxProposal.fromObj(tx);
}));
txs.forEach((tx) => {
if (actionsById[tx.multisigTxId]) {
tx.actions = [...tx.actions, ...(actionsById[tx.multisigTxId] || [])];
}
});
return resolve(txs);
});
});
}
fetchPendingTxs(walletId, cb) {
this.db
.collection(collections.TXS)
.find({
walletId,
isPending: true
})
.sort({
createdOn: -1
})
.toArray((err, result) => {
if (err)
return cb(err);
if (!result)
return cb();
const txs = lodash_1.default.map(result, tx => {
return model_1.TxProposal.fromObj(tx);
});
return this._completeTxData(walletId, txs, cb);
});
}
fetchTxs(walletId, opts, cb) {
opts = opts || {};
const tsFilter = {};
if (lodash_1.default.isNumber(opts.minTs))
tsFilter.$gte = opts.minTs;
if (lodash_1.default.isNumber(opts.maxTs))
tsFilter.$lte = opts.maxTs;
const filter = {
walletId
};
if (!lodash_1.default.isEmpty(tsFilter))
filter.createdOn = tsFilter;
const mods = {};
if (lodash_1.default.isNumber(opts.limit))
mods.limit = opts.limit;
this.db
.collection(collections.TXS)
.find(filter, mods)
.sort({
createdOn: -1
})
.toArray((err, result) => {
if (err)
return cb(err);
if (!result)
return cb();
const txs = lodash_1.default.map(result, tx => {
return model_1.TxProposal.fromObj(tx);
});
return this._completeTxData(walletId, txs, cb);
});
}
fetchBroadcastedTxs(walletId, opts, cb) {
opts = opts || {};
const tsFilter = {};
if (lodash_1.default.isNumber(opts.minTs))
tsFilter.$gte = opts.minTs;
if (lodash_1.default.isNumber(opts.maxTs))
tsFilter.$lte = opts.maxTs;
const filter = {
walletId,
status: 'broadcasted'
};
if (!lodash_1.default.isEmpty(tsFilter))
filter.broadcastedOn = tsFilter;
const mods = {};
if (lodash_1.default.isNumber(opts.limit))
mods.limit = opts.limit;
this.db
.collection(collections.TXS)
.find(filter, mods)
.sort({
createdOn: -1
})
.toArray((err, result) => {
if (err)
return cb(err);
if (!result)
return cb();
const txs = lodash_1.default.map(result, tx => {
return model_1.TxProposal.fromObj(tx);
});
return this._completeTxData(walletId, txs, cb);
});
}
fetchNotifications(walletId, notificationId, minTs, cb) {
function makeId(timestamp) {
return lodash_1.default.padStart(timestamp, 14, '0') + lodash_1.default.repeat('0', 4);
}
let minId = makeId(minTs);
if (notificationId) {
minId = notificationId > minId ? notificationId : minId;
}
this.db
.collection(collections.NOTIFICATIONS)
.find({
walletId,
id: {
$gt: minId
}
})
.sort({
id: 1
})
.toArray((err, result) => {
if (err)
return cb(err);
if (!result)
return cb();
const notifications = lodash_1.default.map(result, notification => {
return model_1.Notification.fromObj(notification);
});
return cb(null, notifications);
});
}
storeNotification(walletId, notification, cb) {
if (!this.db) {
logger_1.default.warn('Trying to store a notification with close DB %o', notification);
return;
}
this.db.collection(collections.NOTIFICATIONS).insertOne(notification, {
w: 1
}, cb);
}
storeTx(walletId, txp, cb) {
this.db.collection(collections.TXS).replaceOne({
id: txp.id,
walletId
}, txp.toObject(), {
w: 1,
upsert: true
}, cb);
}
removeTx(walletId, txProposalId, cb) {
this.db.collection(collections.TXS).deleteOne({
id: txProposalId,
walletId
}, {
w: 1
}, cb);
}
removeWallet(walletId, cb) {
async.parallel([
next => {
this.db.collection(collections.WALLETS).deleteOne({
id: walletId
}, next);
},
next => {
const otherCollections = lodash_1.default.without(lodash_1.default.values(collections), collections.WALLETS);
async.each(otherCollections, (col, next) => {
this.db.collection(col).deleteMany({
walletId
}, next);
}, next);
}
], cb);
}
fetchAddresses(walletId, cb) {
this.db
.collection(collections.ADDRESSES)
.find({
walletId
})
.sort({
createdOn: 1
})
.toArray((err, result) => {
if (err)
return cb(err);
if (!result)
return cb();
return cb(null, Utils.sortAsc(result.map(model_1.Address.fromObj), 'createdOn', 'path'));
});
}
migrateToCashAddr(walletId, cb) {
const cursor = this.db.collection(collections.ADDRESSES).find({
walletId
});
cursor.on('end', () => {
console.log(`Migration to cash address of ${walletId} Finished`);
return this.clearWalletCache(walletId, cb);
});
cursor.on('err', err => {
return cb(err);
});
cursor.on('data', doc => {
cursor.pause();
let x;
try {
x = bchaddresstranslator_1.BCHAddressTranslator.translate(doc.address, 'cashaddr');
}
catch (e) {
return cb(e);
}
this.db.collection(collections.ADDRESSES).updateMany({ _id: doc._id }, { $set: { address: x } });
cursor.resume();
});
}
fetchUnsyncAddresses(walletId, cb) {
this.db
.collection(collections.ADDRESSES)
.find({
walletId,
beRegistered: null
})
.toArray((err, result) => {
if (err)
return cb(err);
if (!result)
return cb();
return cb(null, result);
});
}
fetchNewAddresses(walletId, fromTs, cb) {
this.db
.collection(collections.ADDRESSES)
.find({
walletId,
createdOn: {
$gte: fromTs
}
})
.sort({
createdOn: 1
})
.toArray((err, result) => {
if (err)
return cb(err);
if (!result)
return cb();
return cb(null, result.map(model_1.Address.fromObj));
});
}
storeAddress(address, cb) {
this.db.collection(collections.ADDRESSES).replaceOne({
walletId: address.walletId,
address: address.address
}, address, {
w: 1,
upsert: false
}, cb);
}
markSyncedAddresses(addresses, cb) {
this.db.collection(collections.ADDRESSES).updateMany({
address: { $in: addresses }
}, { $set: { beRegistered: true } }, {
w: 1,
upsert: false
}, cb);
}
deregisterWallet(walletId, cb) {
this.db.collection(collections.WALLETS).updateOne({
id: walletId
}, { $set: { beRegistered: null } }, {
w: 1,
upsert: false
}, () => {
this.db.collection(collections.ADDRESSES).updateMany({
walletId
}, { $set: { beRegistered: null } }, {
w: 1,
upsert: false
}, () => {
this.clearWalletCache(walletId, cb);
});
});
}
storeAddressAndWallet(wallet, addresses, cb) {
const clonedAddresses = [].concat(addresses);
if (lodash_1.default.isEmpty(addresses))
return cb();
let duplicate;
this.db.collection(collections.ADDRESSES).insertMany(clonedAddresses, {
w: 1
}, err => {
if (err) {
if (!err.toString().match(/E11000/)) {
return cb(err);
}
else {
duplicate = true;
logger_1.default.warn('Found duplicate address: ' + clonedAddresses.map(a => a.address).join(','));
}
}
this.storeWallet(wallet, err => {
return cb(err, duplicate);
});
});
}
fetchAddressByWalletId(walletId, address, cb) {
this.db.collection(collections.ADDRESSES).findOne({
walletId,
address
}, (err, result) => {
if (err)
return cb(err);
if (!result)
return cb();
return cb(null, model_1.Address.fromObj(result));
});
}
fetchAddressesByWalletId(walletId, addresses, cb) {
this.db
.collection(collections.ADDRESSES)
.find({
walletId,
address: { $in: addresses }
}, {})
.toArray((err, result) => {
if (err)
return cb(err);
if (!result)
return cb();
return cb(null, result);
});
}
fetchAddressByChain(chain, address, cb) {
if (!this.db)
return cb();
this.db
.collection(collections.ADDRESSES)
.find({
address
})
.toArray((err, result) => {
if (err)
return cb(err);
if (!result || lodash_1.default.isEmpty(result))
return cb();
if (result.length > 1) {
result = lodash_1.default.find(result, address => {
return chain == (address.chain || address.coin || 'btc');
});
}
else {
result = lodash_1.default.head(result);
}
if (!result)
return cb();
return cb(null, model_1.Address.fromObj(result));
});
}
fetchPreferences(walletId, copayerId, cb) {
this.db
.collection(collections.PREFERENCES)
.find({
walletId
})
.toArray((err, result) => {
if (err)
return cb(err);
if (copayerId) {
result = lodash_1.default.find(result, {
copayerId
});
}
if (!result)
return cb();
const preferences = lodash_1.default.map([].concat(result), r => {
return model_1.Preferences.fromObj(r);
});
if (copayerId) {
return cb(null, preferences[0]);
}
else {
return cb(null, preferences);
}
});
}
storePreferences(preferences, cb) {
this.db.collection(collections.PREFERENCES).replaceOne({
walletId: preferences.walletId,
copayerId: preferences.copayerId
}, preferences, {
w: 1,
upsert: true
}, cb);
}
storeEmail(email, cb) {
this.db.collection(collections.EMAIL_QUEUE).replaceOne({
id: email.id
}, email, {
w: 1,
upsert: true
}, cb);
}
fetchUnsentEmails(cb) {
this.db
.collection(collections.EMAIL_QUEUE)
.find({
status: 'fail'
})
.toArray((err, result) => {
if (err)
return cb(err);
if (!result || lodash_1.default.isEmpty(result))
return cb(null, []);
const emails = lodash_1.default.map(result, x => {
return model_1.Email.fromObj(x);
});
return cb(null, emails);
});
}
fetchEmailByNotification(notificationId, cb) {
this.db.collection(collections.EMAIL_QUEUE).findOne({
notificationId
}, (err, result) => {
if (err)
return cb(err);
if (!result)
return cb();
return cb(null, model_1.Email.fromObj(result));
});
}
getTxHistoryCacheStatusV8(walletId, cb) {
this.db.collection(collections.CACHE).findOne({
walletId,
type: 'historyCacheStatusV8',
key: null
}, (err, result) => {
if (err)
return cb(err);
if (!result)
return cb(null, {
tipId: null,
tipIndex: null
});
return cb(null, {
updatedOn: result.updatedOn,
updatedHeight: result.updatedHeight,
tipIndex: result.tipIndex,
tipTxId: result.tipTxId,
tipHeight: result.tipHeight
});
});
}
getWalletAddressChecked(walletId, cb) {
this.db.collection(collections.CACHE).findOne({
walletId,
type: 'addressChecked',
key: null
}, (err, result) => {
if (err || !result)
return cb(err);
return cb(null, result.totalAddresses);
});
}
setWalletAddressChecked(walletId, totalAddresses, cb) {
this.db.collection(collections.CACHE).replaceOne({
walletId,
type: 'addressChecked',
key: null
}, {
walletId,
type: 'addressChecked',
key: null,
totalAddresses
}, {
w: 1,
upsert: true
}, cb);
}
getTxHistoryCacheV8(walletId, skip, limit, cb) {
$.checkArgument(skip >= 0);
$.checkArgument(limit >= 0);
this.getTxHistoryCacheStatusV8(walletId, (err, cacheStatus) => {
if (err)
return cb(err);
if (lodash_1.default.isNull(cacheStatus.tipId))
return cb(null, []);
let firstPosition = cacheStatus.tipIndex - skip - limit + 1;
const lastPosition = cacheStatus.tipIndex - skip + 1;
if (firstPosition < 0)
firstPosition = 0;
if (lastPosition <= 0)
return cb(null, []);
this.db
.collection(collections.CACHE)
.find({
walletId,
type: 'historyCacheV8',
key: {
$gte: firstPosition,
$lt: lastPosition
}
})
.sort({
key: -1
})
.toArray((err, result) => {
if (err)
return cb(err);
if (!result)
return cb();
const txs = lodash_1.default.map(result, 'tx');
return cb(null, txs);
});
});
}
clearWalletCache(walletId, cb) {
this.db.collection(collections.CACHE).deleteMany({
walletId
}, {}, cb);
}
storeTxHistoryStreamV8(walletId, streamKey, items, cb) {
this.db.collection(collections.CACHE).replaceOne({
walletId,
type: 'historyStream',
key: null
}, {
walletId,
type: 'historyStream',
key: null,
streamKey,
items
}, {
w: 1,
upsert: true
}, cb);
}
clearTxHistoryStreamV8(walletId, cb) {
this.db.collection(collections.CACHE).deleteMany({
walletId,
type: 'historyStream',
key: null
}, {}, cb);
}
getTxHistoryStreamV8(walletId, cb) {
this.db.collection(collections.CACHE).findOne({
walletId,
type: 'historyStream',
key: null
}, (err, result) => {
if (err || !result)
return cb(err);
return cb(null, result);
});
}
storeTxHistoryCacheV8(walletId, tipIndex, items, updateHeight, cb) {
let index = lodash_1.default.isNull(tipIndex) ? 0 : tipIndex + 1;
let pos;
lodash_1.default.each(items.reverse(), item => {
item.position = index++;
});
async.each(items, (item, next) => {
pos = item.position;
delete item.position;
this.db.collection(collections.CACHE).insertOne({
walletId,
type: 'historyCacheV8',
key: pos,
tx: item
}, next);
}, err => {
if (err)
return cb(err);
const first = lodash_1.default.first(items);
const last = lodash_1.default.last(items);
try {
$.checkState(last.txid, 'Failed state: missing txid in tx to be cached at <storeHistoryCacheV8()>');
$.checkState(last.blockheight, 'Failed state: missing blockheight in tx to be cached at <storeHistoryCacheV8()>');
$.checkState(first.blockheight, 'Failed state: missing blockheight in tx to be cached at <storeHistoryCacheV8()>');
$.checkState(last.blockheight >= 0, 'Failed state: blockheight <=0 om tx to be cached at <storeHistoryCacheV8()>');
$.checkState(first.blockheight <= last.blockheight, 'Failed state: tx to be cached are in wrong order (lastest should be first)');
}
catch (e) {
return cb(e);
}
logger_1.default.debug(`Cache Last Item: ${last.txid} blockh: ${last.blockheight} updatedh: ${updateHeight}`);
this.db.collection(collections.CACHE).replaceOne({
walletId,
type: 'historyCacheStatusV8',
key: null
}, {
walletId,
type: 'historyCacheStatusV8',
key: null,
updatedOn: Date.now(),
updatedHeight: updateHeight,
tipIndex: pos,
tipTxId: last.txid,
tipHeight: last.blockheight
}, {
w: 1,
upsert: true
}, cb);
});
}
storeFiatRate(coin, rates, cb) {
const now = Date.now();
async.each(rates, (rate, next) => {
let i = {
ts: now,
coin,
code: rate.code,
value: rate.value
};
this.db.collection(collections.FIAT_RATES2).insertOne(i, {
w: 1
}, next);
}, cb);
}
fetchFiatRate(coin, code, ts, cb) {
this.db
.collection(collections.FIAT_RATES2)
.find({
coin,
code,
ts: {
$lte: ts
}
})
.sort({
ts: -1
})
.limit(1)
.toArray((err, result) => {
if (err || lodash_1.default.isEmpty(result))
return cb(err);
return cb(null, result[0]);
});
}
fetchHistoricalRates(coin, code, ts, cb) {
this.db
.collection(collections.FIAT_RATES2)
.find({
coin,
code,
ts: {
$gte: ts
}
})
.sort({
ts: -1
})
.toArray((err, result) => {
if (err || lodash_1.default.isEmpty(result))
return cb(err);
return cb(null, result);
});
}
fetchTxNote(walletId, txid, cb) {
this.db.collection(collections.TX_NOTES).findOne({
walletId,
txid
}, (err, result) => {
if (err)
return cb(err);
if (!result)
return cb();
return this._completeTxNotesData(walletId, model_1.TxNote.fromObj(result), cb);
});
}
_completeTxNotesData(walletId, notes, cb) {
this.fetchWallet(walletId, (err, wallet) => {
if (err)
return cb(err);
lodash_1.default.each([].concat(notes), note => {
note.editedByName = wallet.getCopayer(note.editedBy).name;
});
return cb(null, notes);
});
}
fetchTxNotes(walletId, opts, cb) {
const filter = {
walletId
};
if (lodash_1.default.isNumber(opts.minTs))
filter.editedOn = {
$gte: opts.minTs
};
this.db
.collection(collections.TX_NOTES)
.find(filter)
.toArray((err, result) => {
if (err)
return cb(err);
const notes = lodash_1.default.compact(lodash_1.default.map(result, note => {
return model_1.TxNote.fromObj(note);
}));
return this._completeTxNotesData(walletId, notes, cb);
});
}
storeTxNote(txNote, cb) {
this.db.collection(collections.TX_NOTES).replaceOne({
txid: txNote.txid,
walletId: txNote.walletId
}, txNote.toObject(), {
w: 1,
upsert: true
}, cb);
}
getSession(copayerId, cb) {
this.db.collection(collections.SESSIONS).findOne({
copayerId
}, (err, result) => {
if (err || !result)
return cb(err);
return cb(null, model_1.Session.fromObj(result));
});
}
storeSession(session, cb) {
this.db.collection(collections.SESSIONS).replaceOne({
copayerId: session.copayerId
}, session.toObject(), {
w: 1,
upsert: true
}, cb);
}
fetchPushNotificationSubs(copayerId, cb) {
this.db
.collection(collections.PUSH_NOTIFICATION_SUBS)
.find({
copayerId
})
.toArray((err, result) => {
if (err)
return cb(err);
if (!result)
return cb();
const tokens = lodash_1.default.map([].concat(result), r => {
return model_1.PushNotificationSub.fromObj(r);
});
return cb(null, tokens);
});
}
fetchLatestPushNotificationSubs(cb) {
const fromDate = new Date().getTime() - Defaults.PUSH_NOTIFICATION_SUBS_TIME;
this.db
.collection(collections.PUSH_NOTIFICATION_SUBS)
.find({
_id: {
$gte: new ObjectID(objectIdDate(fromDate))
}
})
.sort({ _id: -1 })
.toArray((err, result) => {
if (err)
return cb(err);
if (!result)
return cb();
const tokens = lodash_1.default.map([].concat(result), r => {
return model_1.PushNotificationSub.fromObj(r);
});
return cb(null, tokens);
});
}
storePushNotificationSub(pushNotificationSub, cb) {
this.db.collection(collections.PUSH_NOTIFICATION_SUBS).replaceOne({
copayerId: pushNotificationSub.copayerId,
token: pushNotificationSub.token
}, pushNotificationSub, {
w: 1,
upsert: true
}, cb);
}
removePushNotificationSub(copayerId, token, cb) {
this.db.collection(collections.PUSH_NOTIFICATION_SUBS).deleteMany({
copayerId,
token
}, {
w: 1
}, cb);
}
storePushNotificationBrazeSub(pushNotificationSub, cb) {
this.db.collection(collections.PUSH_NOTIFICATION_SUBS).replaceOne({
copayerId: pushNotificationSub.copayerId,
externalUserId: pushNotificationSub.externalUserId
}, pushNotificationSub, {
w: 1,
upsert: true
}, cb);
}
removePushNotificationBrazeSub(copayerId, externalUserId, cb) {
this.db.collection(collections.PUSH_NOTIFICATION_SUBS).deleteMany({
copayerId,
externalUserId
}, {
w: 1
}, cb);
}
streamActiveTxConfirmationSubs(copayerId, txids) {
if (!this.db) {
logger_1.default.warn('Trying to fetch notifications with closed DB');
return;
}
const filter = {
isActive: true,
txid: { $in: txids }
};
if (copayerId)
filter.copayerId = copayerId;
return this.db
.collection(collections.TX_CONFIRMATION_SUBS)
.find(filter)
.addCursorFlag('noCursorTimeout', true);
}
storeTxConfirmationSub(txConfirmationSub, cb) {
this.db.collection(collections.TX_CONFIRMATION_SUBS).replaceOne({
copayerId: txConfirmationSub.copayerId,
txid: txConfirmationSub.txid
}, txConfirmationSub, {
w: 1,
upsert: true
}, cb);
}
removeTxConfirmationSub(copayerId, txid, cb) {
this.db.collection(collections.TX_CONFIRMATION_SUBS).deleteMany({
copayerId,
txid
}, {
w: 1
}, cb);
}
_dump(cb, fn) {
fn = fn || console.log;
cb = cb || function () { };
this.db.collections((err, collections) => {
if (err)
return cb(err);
async.eachSeries(collections, (col, next) => {
col.find().toArray((err, items) => {
fn('--------', col.s.name);
fn(items);
fn('------------------------------------------------------------------\n\n');
next(err);
});
}, cb);
});
}
checkAndUseGlobalCache(key, duration, cb) {
const now = Date.now();
this.db.collection(collections.CACHE).findOne({
key,
walletId: null,
type: null
}, (err, ret) => {
if (err)
return cb(err);
if (!ret)
return cb();
const validFor = ret.ts + duration - now;
return cb(null, validFor > 0 ? ret.result : null, ret.result);
});
}
storeGlobalCache(key, values, cb) {
const now = Date.now();
this.db.collection(collections.CACHE).updateOne({
key,
walletId: null,
type: null
}, {
$set: {
ts: now,
result: values
}
}, {
w: 1,
upsert: true
}, cb);
}
clearGlobalCache(key, cb) {
this.db.collection(collections.CACHE).deleteMany({
key,
walletId: null,
type: null
}, {
w: 1
}, cb);
}
acquireLock(key, expireTs, cb) {
this.db.collection(collections.LOCKS).insertOne({
_id: key,
expireOn: expireTs
}, {}, cb);
}
releaseLock(key, cb) {
this.db.collection(collections.LOCKS).deleteMany({
_id: key
}, {}, cb);
}
clearExpiredLock(key, cb) {
this.db.collection(collections.LOCKS).findOne({
_id: key
}, (err, ret) => {
if (err || !ret)
return;
if (ret.expireOn < Date.now()) {
logger_1.default.info('Releasing expired lock : ' + key);
return this.releaseLock(key, cb);
}
return cb();
});
}
fetchTestingAdverts(cb) {
this.db
.collection(collections.ADVERTISEMENTS)
.find({
isTesting: true
})
.toArray((err, result) => {
if (err)
return cb(err);
if (!result)
return cb();
return cb(null, result.map(model_1.Advertisement.fromObj));
});
}
fetchActiveAdverts(cb) {
this.db
.collection(collections.ADVERTISEMENTS)
.find({
isAdActive: true
})
.toArray((err, result) => {
if (err)
return cb(err);
if (!result)
return cb();
return cb(null, result.map(model_1.Advertisement.fromObj));
});
}
fetchAdvertsByCountry(country, cb) {
this.db
.collection(collections.ADVERTISEMENTS)
.find({
country
})
.toArray((err, result) => {
if (err)
return cb(err);
if (!result)
return cb();
return cb(null, result.map(model_1.Advertisement.fromObj));
});
}
fetchAllAdverts(cb) {
this.db.collection(collections.ADVERTISEMENTS).find({});
}
removeAdvert(adId, cb) {
this.db.collection(collections.ADVERTISEMENTS).deleteOne({
advertisementId: adId
}, {
w: 1
}, cb);
}
storeAdvert(advert, cb) {
this.db.collection(collections.ADVERTISEMENTS).updateOne({
advertisementId: advert.advertisementId
}, { $set: advert }, {
upsert: true
}, cb);
}
fetchAdvert(adId, cb) {
this.db.collection(collections.ADVERTISEMENTS).findOne({
advertisementId: adId
}, (err, result) => {
if (err)
return cb(err);
if (!result)
return cb();
return cb(null, model_1.Advertisement.fromObj(result));
});
}
activateAdvert(adId, cb) {
this.db.collection(collections.ADVERTISEMENTS).updateOne({
advertisementId: adId
}, { $set: { isAdActive: true, isTesting: false } }, {
upsert: true
}, cb);
}
deactivateAdvert(adId, cb) {
this.db.collection(collections.ADVERTISEMENTS).updateOne({
advertisementId: adId
}, {
$set: { isAdActive: false, isTesting: true }
}, {
upsert: true
}, cb);
}
}
exports.Storage = Storage;
Storage.BCHEIGHT_KEY = 'bcheight';
Storage.collections = collections;
//# sourceMappingURL=storage.js.map