UNPKG

bitcore-node

Version:

A blockchain indexing node with extended capabilities using bitcore

268 lines 13.4 kB
"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; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.MoralisP2PWorker = void 0; const crypto_rpc_1 = require("crypto-rpc"); const logger_1 = __importStar(require("../../../logger")); const events_1 = require("../../../models/events"); const state_1 = require("../../../models/state"); const walletAddress_1 = require("../../../models/walletAddress"); const webhook_1 = require("../../../models/webhook"); const block_1 = require("../../../providers/chain-state/evm/models/block"); const transaction_1 = require("../../../providers/chain-state/evm/models/transaction"); const p2p_1 = require("../../../services/p2p"); const utils_1 = require("../../../utils"); const csp_1 = require("../api/csp"); class MoralisP2PWorker extends p2p_1.BaseP2PWorker { constructor({ chain, network, chainConfig }) { super({ chain, network, chainConfig }); this.chain = chain; this.network = network; this.chainConfig = chainConfig; this.csp = new csp_1.MoralisStateProvider(this.chain); this.addressSub = undefined; this.chainId = undefined; this.webhookTail = undefined; this.bestBlock = 0; this.chainNetworkStr = `${this.chain}:${this.network}`; } async start() { this.refreshSyncingNode(); } async stop() { this.stopping = true; clearInterval(this.syncInterval); await this.webhookTail?.close(); await this.unregisterSyncingNode(); this.web3?.currentProvider?.disconnect?.(); } async getWeb3() { let connectionErrors = []; try { try { await this.web3.eth.getBlockNumber(); } catch (e) { const providerConfigs = this.chainConfig.providers || (this.chainConfig.provider ? [this.chainConfig.provider] : []); for (const config of providerConfigs) { try { const rpc = new crypto_rpc_1.CryptoRpc({ chain: this.chain, network: this.network, isEVM: true, ...config }).get(this.chain); await rpc.web3.eth.getBlockNumber(); this.web3 = rpc.web3; return this.web3; } catch (e) { connectionErrors.push(e); } } logger_1.default.error('Unable to connect to web3 %o:%o instance: %o', connectionErrors); // Notice we don't unset this.web3. At worst, the old connection starts working again. } } catch (e) { logger_1.default.error('Error getting web3 %o:%o instance: %o', this.chain, this.network, e); } return this.web3; } async getChainId() { if (this.chainId == null) { const web3 = await this.getWeb3(); this.chainId = await web3.eth.getChainId(); } return this.chainId; } async sync() { const { value: state } = await state_1.StateStorage.getSingletonState(); let currentHeight = state?.verifiedBlockHeight?.[this.chain]?.[this.network] || 0; let syncing = false; this.syncInterval = setInterval(async () => { if (this.stopping || !this.isSyncingNode) { return; } if (syncing) { return; } syncing = true; const startTime = Date.now(); let msgInterval; let block = null; try { const web3 = await this.getWeb3(); this.bestBlock = await web3.eth.getBlockNumber(); currentHeight = Math.max(this.bestBlock - (this.chainConfig.maxBlocksToSync || 50), currentHeight || 0); const startHeight = currentHeight; if (this.bestBlock - currentHeight <= 0) { return; } logger_1.default.info(`Syncing ${this.bestBlock - currentHeight} blocks for ${this.chain} ${this.network}`); msgInterval = setInterval(() => { const blocksProcessed = currentHeight - startHeight; const elapsedMinutes = (Date.now() - startTime) / (60 * 1000); logger_1.default.info(`${(0, logger_1.timestamp)()} | Syncing... | Chain: ${this.chain} | Network: ${this.network} |${(blocksProcessed / elapsedMinutes) .toFixed(2) .padStart(8)} blocks/min | Height: ${currentHeight.toString().padStart(7)}`); }, 1000 * 2); // 2 seconds do { block = await web3.eth.getBlock(currentHeight, true); if (block && !this.stopping) { const blockEvent = block_1.EVMBlockStorage.convertRawBlock(this.chain, this.network, block); await events_1.EventStorage.signalBlock(blockEvent); // .catch((e) => logger.error(`Error signaling ${this.chainNetworkStr} block event: %o`, e.stack || e.message || e)); for (const tx of block.transactions) { const txEvent = transaction_1.EVMTransactionStorage.convertRawTx(this.chain, this.network, tx, blockEvent); await events_1.EventStorage.signalTx(txEvent); // .catch((e) => logger.error(`Error signaling ${this.chainNetworkStr} tx event: %o`, e.stack || e.message || e)); } await state_1.StateStorage.setVerifiedBlockHeight({ chain: this.chain, network: this.network, height: currentHeight }); currentHeight = block.number + 1; } } while (block); clearInterval(msgInterval); // clear before log below logger_1.default.info(`${this.chainNetworkStr} up to date.`); } catch (err) { logger_1.default.error(`Error syncing ${this.chainNetworkStr}: %o`, err.stack || err.message || err); } finally { syncing = false; clearInterval(msgInterval); // clear here too in case of a catch } }, 1000 * (this.chainConfig.syncIntervalSecs || 10)); // default 10 seconds // Listen for webhooks and process any unprocessed ones in the db await this.syncAddressActivity(); // Subscribe to external wallet activity provider while (!(await this.subscribeToAddressActivity()) && !this.stopping) { // subscription might fail if the API service is offline (webhook endpoint) logger_1.default.warn(`Retrying ${this.chainNetworkStr} address subscription in 10 seconds...`); await (0, utils_1.wait)(10 * 1000); } ; } async subscribeToAddressActivity() { try { const chainId = await this.getChainId(); const subs = await this.csp.getAddressSubscriptions(); this.addressSub = subs?.find(sub => sub.chainIds.includes('0x' + chainId.toString(16))); if (this.addressSub?.status === 'error') { await this.csp.deleteAddressSubscription({ sub: this.addressSub }); this.addressSub = undefined; } if (!this.addressSub) { // Create new subscription if none exists this.addressSub = await this.csp.createAddressSubscription({ chain: this.chain, network: this.network, chainId }); } logger_1.default[this.addressSub?.status === 'active' ? 'info' : 'warn'](`Address subscription for ${this.chainNetworkStr} is: ${this.addressSub.status}`); // todo check and update url // if (!sub.webhookUrl.startsWith(provider.baseWebhookUrl)) { // // todo update url // } const ONE_DAY = 1000 * 60 * 60 * 24; const now = Date.now(); { // Remove inactive addresses const state = await state_1.StateStorage.getSingletonState(); const lastUpdate = state.value?.lastAddressSubscriptionUpdate?.[this.chain]?.[this.network]; const inactiveAddresses = await walletAddress_1.WalletAddressStorage.collection.find({ chain: this.chain, network: this.network, lastQueryTime: { $gte: lastUpdate, $lt: now - ONE_DAY * 60 } }).toArray(); this.addressSub = await this.csp.updateAddressSubscription({ sub: this.addressSub, addressesToRemove: inactiveAddresses.map(a => a.address) }); } { // Add new addresses const activeAddresses = await walletAddress_1.WalletAddressStorage.collection.find({ chain: this.chain, network: this.network, lastQueryTime: { $gte: now - ONE_DAY * 60 } }).toArray(); this.addressSub = await this.csp.updateAddressSubscription({ sub: this.addressSub, addressesToAdd: activeAddresses.map(a => a.address), }); } // Set subscription to active this.addressSub = await this.csp.updateAddressSubscription({ sub: this.addressSub, status: 'active' }); logger_1.default[this.addressSub?.status === 'active' ? 'info' : 'warn'](`Address subscription for ${this.chainNetworkStr} is: ${this.addressSub.status}`); return true; } catch (err) { logger_1.default.error(`Error subscribing to ${this.chainNetworkStr} address activity: %o`, err.stack || err.message || err); return false; } } async syncAddressActivity() { // if this is a reconnect, remove old listeners this.webhookTail?.removeAllListeners(); this.webhookTail = webhook_1.WebhookStorage.getTail({ chain: this.chain, network: this.network }); logger_1.default.info(`Webhook tail initiated for ${this.chainNetworkStr}`); this.webhookTail.on('error', (err) => { logger_1.default.error(`Webhook ${this.chainNetworkStr} tail error: %o`, err.stack || err.message || err); }); this.webhookTail.on('close', async () => { // clearInterval(heightInterval); if (!this.stopping) { logger_1.default.error(`Webhook tail for ${this.chainNetworkStr} unexpectedly closed. Trying to reconnect in 5 seconds...`); await (0, utils_1.wait)(5000); // wait 5 seconds before trying to reconnect this.syncAddressActivity(); } }); this.webhookTail.on('data', async (webhook) => { try { let coinEvents = []; if (webhook.source !== 'moralis') { return; } coinEvents = this.csp.webhookToCoinEvents({ chain: this.chain, network: this.network, webhook }); const result = await Promise.allSettled(coinEvents.map(evt => events_1.EventStorage.signalAddressCoin(evt))); const someFulfilled = result.some(r => r.status === 'fulfilled' || (false && logger_1.default.error(`Error signaling ${this.chainNetworkStr} for webhook ${webhook._id?.toString()}: %o`, r.reason))); if (someFulfilled) { await webhook_1.WebhookStorage.setProcessed({ webhook }); } } catch (err) { logger_1.default.error(`Error processing ${this.chainNetworkStr} webhook ${webhook._id?.toString()}: %o`, err.stack || err.message || err); } }); } } exports.MoralisP2PWorker = MoralisP2PWorker; //# sourceMappingURL=p2p.js.map