UNPKG

@abcpros/bitcore-wallet-service

Version:
385 lines 16.6 kB
"use strict"; var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (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 (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __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.BlockchainMonitor = void 0; var async = __importStar(require("async")); var lodash_1 = __importDefault(require("lodash")); require("source-map-support/register"); var blockchainexplorer_1 = require("./blockchainexplorer"); var index_1 = require("./chain/index"); var lock_1 = require("./lock"); var logger_1 = __importDefault(require("./logger")); var messagebroker_1 = require("./messagebroker"); var model_1 = require("./model"); var server_1 = require("./server"); var storage_1 = require("./storage"); var $ = require('preconditions').singleton(); var Common = require('./common'); var Constants = Common.Constants; var Utils = Common.Utils; var Defaults = Common.Defaults; var throttledNewBlocks = lodash_1.default.throttle(function (that, coin, network, hash) { that._notifyNewBlock(coin, network, hash); }, Defaults.NEW_BLOCK_THROTTLE_TIME_MIN * 60 * 1000); var BlockchainMonitor = (function () { function BlockchainMonitor() { } BlockchainMonitor.prototype.start = function (opts, cb) { var _this = this; opts = opts || {}; this.N = opts.N || 100; this.Ni = this.Nix = 0; this.last = this.lastTx = []; async.parallel([ function (done) { _this.explorers = { xpi: {}, xec: {} }; var coinNetworkPairs = []; lodash_1.default.each(lodash_1.default.values(Constants.COINS), function (coin) { lodash_1.default.each(lodash_1.default.values(Constants.NETWORKS), function (network) { coinNetworkPairs.push({ coin: coin, network: network }); }); }); lodash_1.default.each(coinNetworkPairs, function (pair) { var explorer; if (opts.blockchainExplorers && opts.blockchainExplorers[pair.coin] && opts.blockchainExplorers[pair.coin][pair.network]) { explorer = opts.blockchainExplorers[pair.coin][pair.network]; } else { var config = {}; if (opts.blockchainExplorerOpts && opts.blockchainExplorerOpts[pair.coin] && opts.blockchainExplorerOpts[pair.coin][pair.network]) { config = opts.blockchainExplorerOpts[pair.coin][pair.network]; } else { return; } explorer = blockchainexplorer_1.BlockChainExplorer({ provider: config.provider, coin: pair.coin, network: pair.network, url: config.url, userAgent: server_1.WalletService.getServiceVersion() }); } $.checkState(explorer, 'Failed State: explorer undefined at <start()>'); if (_this.explorers[pair.coin]) { _this._initExplorer(pair.coin, pair.network, explorer); _this.explorers[pair.coin][pair.network] = explorer; } }); done(); }, function (done) { if (opts.storage) { _this.storage = opts.storage; done(); } else { _this.storage = new storage_1.Storage(); _this.storage.connect(__assign(__assign({}, opts.storageOpts), { secondaryPreferred: true }), done); } }, function (done) { _this.messageBroker = opts.messageBroker || new messagebroker_1.MessageBroker(opts.messageBrokerOpts); done(); }, function (done) { _this.lock = opts.lock || new lock_1.Lock(_this.storage); done(); } ], function (err) { if (err) { logger_1.default.error(err); } return cb(err); }); }; BlockchainMonitor.prototype._initExplorer = function (coin, network, explorer) { explorer.initSocket({ onBlock: lodash_1.default.bind(this._handleNewBlock, this, coin, network), onIncomingPayments: lodash_1.default.bind(this._handleIncomingPayments, this, coin, network) }); }; BlockchainMonitor.prototype._handleThirdPartyBroadcasts = function (coin, network, data, processIt) { var _this = this; if (!data || !data.txid) return; if (!processIt) { if (this.lastTx.indexOf(data.txid) >= 0) { return; } this.lastTx[this.Nix++] = data.txid; if (this.Nix >= this.N) this.Nix = 0; logger_1.default.debug("\tChecking " + coin + "/" + network + " txid: " + data.txid); } this.storage.fetchTxByHash(data.txid, function (err, txp) { if (err) { logger_1.default.error('Could not fetch tx from the db'); return; } if (!txp || txp.status != 'accepted') return; var walletId = txp.walletId; if (!processIt) { logger_1.default.debug('Detected broadcast ' + data.txid + ' of an accepted txp [' + txp.id + '] for wallet ' + walletId + ' [' + txp.amount + 'sat ]'); return setTimeout(_this._handleThirdPartyBroadcasts.bind(_this, coin, network, data, true), 20 * 1000); } logger_1.default.debug('Processing accepted txp [' + txp.id + '] for wallet ' + walletId + ' [' + txp.amount + 'sat ]'); txp.setBroadcasted(); _this.storage.storeTx(_this.walletId, txp, function (err) { if (err) logger_1.default.error('Could not save TX'); var args = { txProposalId: txp.id, txid: data.txid, amount: txp.getTotalAmount() }; var notification = model_1.Notification.create({ type: 'NewOutgoingTxByThirdParty', data: args, walletId: walletId }); _this._storeAndBroadcastNotification(notification); }); }); }; BlockchainMonitor.prototype._handleIncomingPayments = function (coin, network, data) { var _this = this; if (!data) return; var out = data.out; if (!out || !out.address || out.address.length < 10) return; if (coin != 'eth') { if (!(out.amount > 0)) return; if (this.last.indexOf(out.address) >= 0) { logger_1.default.debug('The incoming tx"s out ' + out.address + ' was already processed'); return; } this.last[this.Ni++] = out.address; if (this.Ni >= this.N) this.Ni = 0; } else if (coin == 'eth') { if (this.lastTx.indexOf(data.txid) >= 0) { logger_1.default.debug('The incoming tx ' + data.txid + ' was already processed'); return; } this.lastTx[this.Nix++] = data.txid; if (this.Nix >= this.N) this.Nix = 0; } logger_1.default.debug("Checking " + coin + ":" + network + ":" + out.address + " " + out.amount); this.storage.fetchAddressByCoin(coin, out.address, function (err, address) { if (err) { logger_1.default.error('Could not fetch addresses from the db'); return; } if (!address || address.isChange) { return _this._handleThirdPartyBroadcasts(coin, network, data, null); } var walletId = address.walletId; var fromTs = Date.now() - 24 * 3600 * 1000; _this.storage.fetchNotifications(walletId, null, fromTs, function (err, notifications) { if (err) return; var alreadyNotified = lodash_1.default.some(notifications, function (n) { return n.type == 'NewIncomingTx' && n.data && n.data.txid == data.txid; }); if (alreadyNotified) { logger_1.default.debug('The incoming tx ' + data.txid + ' was already notified'); return; } logger_1.default.debug('Incoming tx for wallet ' + walletId + ' [' + out.amount + 'amount -> ' + out.address + ']'); var notification = model_1.Notification.create({ type: 'NewIncomingTx', data: { txid: data.txid, address: out.address, amount: out.amount, tokenAddress: out.tokenAddress, multisigContractAddress: out.multisigContractAddress, network: network }, walletId: walletId }); if (network !== 'testnet') { _this.storage.fetchWallet(walletId, function (err, wallet) { if (err) return; async.each(wallet.copayers, function (c, next) { var sub = model_1.TxConfirmationSub.create({ copayerId: c.id, walletId: walletId, txid: data.txid, amount: out.amount, isCreator: false }); _this.storage.storeTxConfirmationSub(sub, next); }, function (err) { if (err) logger_1.default.error(err); }); }); } _this._storeAndBroadcastNotification(notification, function () { return; }); }); }); }; BlockchainMonitor.prototype._notifyNewBlock = function (coin, network, hash) { logger_1.default.debug(" ** NOTIFY New " + coin + "/" + network + " block " + hash); var notification = model_1.Notification.create({ type: 'NewBlock', walletId: coin + ":" + network, data: { hash: hash, coin: coin, network: network } }); this._storeAndBroadcastNotification(notification, function () { }); }; BlockchainMonitor.prototype._handleTxConfirmations = function (coin, network, hash) { var _this = this; if (!index_1.ChainService.notifyConfirmations(coin, network)) return; var processTriggeredSubs = function (subs, cb) { async.mapSeries(subs, function (sub, cb) { logger_1.default.debug('New tx confirmation ' + sub.txid); sub.isActive = false; async.waterfall([ function (next) { _this.storage.storeTxConfirmationSub(sub, function (err) { if (err) return cb(err); var notification = model_1.Notification.create({ type: 'TxConfirmation', walletId: sub.walletId, creatorId: sub.copayerId, isCreator: sub.isCreator, data: { txid: sub.txid, coin: coin, network: network, amount: sub.amount } }); next(null, notification); }); }, function (notification, next) { _this._storeAndBroadcastNotification(notification, next); } ], cb); }, cb); }; var explorer = this.explorers[coin][network]; if (!explorer) return; explorer.getTxidsInBlock(hash, function (err, txids) { if (err) { logger_1.default.error('Could not fetch txids from block ' + hash, err); return; } _this.storage.fetchActiveTxConfirmationSubs(null, function (err, subs) { if (err) return; if (lodash_1.default.isEmpty(subs)) return; var indexedSubs = lodash_1.default.groupBy(subs, 'txid'); var triggered = []; lodash_1.default.each(txids, function (txid) { if (indexedSubs[txid]) { lodash_1.default.each(indexedSubs[txid], function (indexedSub) { triggered.push(indexedSub); }); } }); processTriggeredSubs(lodash_1.default.uniqBy(triggered, 'walletId'), function (err) { if (err) { logger_1.default.error('Could not process tx confirmations', err); } return; }); }); }); }; BlockchainMonitor.prototype._handleNewBlock = function (coin, network, hash) { var cacheKey = storage_1.Storage.BCHEIGHT_KEY + ':' + coin + ':' + network; this.storage.clearGlobalCache(cacheKey, function () { }); if (coin == 'xrp') { return; } if (network == 'testnet') { throttledNewBlocks(this, coin, network, hash); } else { this._notifyNewBlock(coin, network, hash); this._handleTxConfirmations(coin, network, hash); } }; BlockchainMonitor.prototype._storeAndBroadcastNotification = function (notification, cb) { var _this = this; this.storage.storeNotification(notification.walletId, notification, function () { _this.messageBroker.send(notification); if (cb) return cb(); }); }; return BlockchainMonitor; }()); exports.BlockchainMonitor = BlockchainMonitor; //# sourceMappingURL=blockchainmonitor.js.map