diff --git a/packages/bitcore-client/bin/multi-sig/wallet-derive-address b/packages/bitcore-client/bin/multi-sig/wallet-derive-address index f8ea4dddb45..f0f5b154def 100755 --- a/packages/bitcore-client/bin/multi-sig/wallet-derive-address +++ b/packages/bitcore-client/bin/multi-sig/wallet-derive-address @@ -3,7 +3,7 @@ const fs = require('fs'); const bitcoreLib = require('bitcore-lib'); const program = require('commander'); -const Wallet = require('../../lib/wallet'); +const { Wallet } = require('../../lib/wallet'); const promptly = require('promptly'); program diff --git a/packages/bitcore-client/bin/wallet-balance b/packages/bitcore-client/bin/wallet-balance index 056c75dbd69..0cb62468dec 100755 --- a/packages/bitcore-client/bin/wallet-balance +++ b/packages/bitcore-client/bin/wallet-balance @@ -1,7 +1,7 @@ #!/usr/bin/env node const program = require('../ts_build/program'); -const Wallet = require('../ts_build/wallet'); +const { Wallet } = require('../ts_build/wallet'); try { program diff --git a/packages/bitcore-client/bin/wallet-broadcast b/packages/bitcore-client/bin/wallet-broadcast index 87f95fc3b37..76641513b33 100755 --- a/packages/bitcore-client/bin/wallet-broadcast +++ b/packages/bitcore-client/bin/wallet-broadcast @@ -1,7 +1,7 @@ #!/usr/bin/env node const program = require('../ts_build/program'); -const Wallet = require('../ts_build/wallet'); +const { Wallet } = require('../ts_build/wallet'); program .version(require('../package.json').version) diff --git a/packages/bitcore-client/bin/wallet-create b/packages/bitcore-client/bin/wallet-create index c4a0648da3e..4fc42149da0 100755 --- a/packages/bitcore-client/bin/wallet-create +++ b/packages/bitcore-client/bin/wallet-create @@ -1,7 +1,7 @@ #!/usr/bin/env node const program = require('../ts_build/program'); -const Wallet = require('../ts_build/wallet'); +const { Wallet } = require('../ts_build/wallet'); const promptly = require('promptly'); program diff --git a/packages/bitcore-client/bin/wallet-decrypt b/packages/bitcore-client/bin/wallet-decrypt index 1a74876eaff..8cf8cce5a2e 100755 --- a/packages/bitcore-client/bin/wallet-decrypt +++ b/packages/bitcore-client/bin/wallet-decrypt @@ -1,7 +1,7 @@ #!/usr/bin/env node const program = require('../ts_build/program'); -const Wallet = require('../ts_build/wallet'); +const { Wallet } = require('../ts_build/wallet'); const promptly = require('promptly'); program diff --git a/packages/bitcore-client/bin/wallet-derive b/packages/bitcore-client/bin/wallet-derive index 5d819077e94..3ba0aee0cbd 100755 --- a/packages/bitcore-client/bin/wallet-derive +++ b/packages/bitcore-client/bin/wallet-derive @@ -5,7 +5,7 @@ const bitcoreLibs = { BCH: require('bitcore-lib-cash') }; const program = require('../ts_build/program'); -const Wallet = require('../ts_build/wallet'); +const { Wallet } = require('../ts_build/wallet'); const promptly = require('promptly'); program diff --git a/packages/bitcore-client/bin/wallet-import b/packages/bitcore-client/bin/wallet-import index b142f3cd8fa..d80de4e11c6 100755 --- a/packages/bitcore-client/bin/wallet-import +++ b/packages/bitcore-client/bin/wallet-import @@ -2,7 +2,7 @@ const fs = require('fs'); const program = require('../ts_build/program'); -const Wallet = require('../ts_build/wallet'); +const { Wallet } = require('../ts_build/wallet'); const promptly = require('promptly'); program diff --git a/packages/bitcore-client/bin/wallet-list b/packages/bitcore-client/bin/wallet-list index 3405dfcfad7..4e422b268c8 100755 --- a/packages/bitcore-client/bin/wallet-list +++ b/packages/bitcore-client/bin/wallet-list @@ -1,7 +1,7 @@ #!/usr/bin/env node 'use strict'; -const Storage = require('../ts_build/storage'); +const { Storage } = require('../ts_build/storage'); const program = require('../ts_build/program'); program diff --git a/packages/bitcore-client/bin/wallet-register b/packages/bitcore-client/bin/wallet-register index fed9326a353..99b941e5200 100755 --- a/packages/bitcore-client/bin/wallet-register +++ b/packages/bitcore-client/bin/wallet-register @@ -1,7 +1,7 @@ #!/usr/bin/env node const program = require('../ts_build/program'); -const Wallet = require('../ts_build/wallet'); +const { Wallet } = require('../ts_build/wallet'); program .version(require('../package.json').version) diff --git a/packages/bitcore-client/bin/wallet-sign b/packages/bitcore-client/bin/wallet-sign index d1ae233d669..588359f2b14 100755 --- a/packages/bitcore-client/bin/wallet-sign +++ b/packages/bitcore-client/bin/wallet-sign @@ -1,7 +1,7 @@ #!/usr/bin/env node const program = require('../ts_build/program'); -const Wallet = require('../ts_build/wallet'); +const { Wallet } = require('../ts_build/wallet'); const promptly = require('promptly'); program diff --git a/packages/bitcore-client/bin/wallet-transaction-list b/packages/bitcore-client/bin/wallet-transaction-list index 3979cef0a01..e97f3520233 100755 --- a/packages/bitcore-client/bin/wallet-transaction-list +++ b/packages/bitcore-client/bin/wallet-transaction-list @@ -1,7 +1,7 @@ #!/usr/bin/env node const program = require('../ts_build/program'); -const Wallet = require('../ts_build/wallet'); +const { Wallet } = require('../ts_build/wallet'); program .version(require('../package.json').version) diff --git a/packages/bitcore-client/bin/wallet-tx b/packages/bitcore-client/bin/wallet-tx index 327b181d6f4..a7311254e81 100755 --- a/packages/bitcore-client/bin/wallet-tx +++ b/packages/bitcore-client/bin/wallet-tx @@ -1,7 +1,7 @@ #!/usr/bin/env node const program = require('../ts_build/program'); -const Wallet = require('../ts_build/wallet'); +const { Wallet } = require('../ts_build/wallet'); program .version(require('../package.json').version) diff --git a/packages/bitcore-client/bin/wallet-utxos b/packages/bitcore-client/bin/wallet-utxos index df5f269125b..b1a37f282fa 100755 --- a/packages/bitcore-client/bin/wallet-utxos +++ b/packages/bitcore-client/bin/wallet-utxos @@ -1,7 +1,7 @@ #!/usr/bin/env node const program = require('../ts_build/program'); -const Wallet = require('../ts_build/wallet'); +const { Wallet } = require('../ts_build/wallet'); program .version(require('../package.json').version) diff --git a/packages/bitcore-node/src/config.ts b/packages/bitcore-node/src/config.ts index deceb89c3dc..ab129b5b8c4 100644 --- a/packages/bitcore-node/src/config.ts +++ b/packages/bitcore-node/src/config.ts @@ -1,6 +1,6 @@ import { homedir, cpus } from 'os'; import parseArgv from './utils/parseArgv'; -import ConfigType from './types/Config'; +import { ConfigType } from "./types/Config"; let program = parseArgv([], ['config']); function findConfig(): ConfigType | undefined { @@ -57,18 +57,31 @@ const Config = function(): ConfigType { dbName: process.env.DB_NAME || 'bitcore', dbPort: process.env.DB_PORT || '27017', numWorkers: cpus().length, - api: { - rateLimiter: { - whitelist: [ - '::ffff:127.0.0.1' - ] + chains: {}, + services: { + api: { + enabled: true, + rateLimiter: { + whitelist: ['::ffff:127.0.0.1'] + }, + wallets: { + allowCreationBeforeCompleteSync: false, + allowUnauthenticatedCalls: false + } + }, + event: { + enabled: true + }, + p2p: { + enabled: true }, - wallets: { - allowCreationBeforeCompleteSync: false, - allowUnauthenticatedCalls: false + socket: { + enabled: true + }, + storage: { + enabled: true } - }, - chains: {} + } }; let foundConfig = findConfig(); diff --git a/packages/bitcore-node/src/models/base.ts b/packages/bitcore-node/src/models/base.ts index cd4e77738bd..e3a3d3cc6b7 100644 --- a/packages/bitcore-node/src/models/base.ts +++ b/packages/bitcore-node/src/models/base.ts @@ -13,15 +13,15 @@ export abstract class BaseModel { key: keyof T; }>; - constructor(private collectionName: string) { + constructor(private collectionName: string, private storageService = Storage) { this.handleConnection(); } private async handleConnection() { - Storage.connection.on('CONNECTED', async () => { - if (Storage.db != undefined) { + this.storageService.connection.on('CONNECTED', async () => { + if (this.storageService.db != undefined) { this.connected = true; - this.db = Storage.db; + this.db = this.storageService.db; await this.onConnect(); } }); @@ -30,8 +30,8 @@ export abstract class BaseModel { abstract async onConnect(); get collection(): Collection> { - if (Storage.db) { - return Storage.db.collection(this.collectionName); + if (this.storageService.db) { + return this.storageService.db.collection(this.collectionName); } else { throw new Error('Not connected to the database yet'); } diff --git a/packages/bitcore-node/src/models/block.ts b/packages/bitcore-node/src/models/block.ts index e6d7dd6b350..05ea099a19a 100644 --- a/packages/bitcore-node/src/models/block.ts +++ b/packages/bitcore-node/src/models/block.ts @@ -11,13 +11,14 @@ import { SpentHeightIndicators } from '../types/Coin'; import { EventModel } from './events'; import config from '../config'; import { Event } from '../services/event'; +import { StorageService } from "../services/storage"; export { IBlock }; @LoggifyClass -export class Block extends BaseModel { - constructor() { - super('blocks'); +export class BlockSchema extends BaseModel { + constructor(storage?: StorageService) { + super('blocks', storage); } chainTips: Mapping> = {}; @@ -227,4 +228,4 @@ export class Block extends BaseModel { } } -export let BlockModel = new Block(); +export let BlockModel = new BlockSchema(); diff --git a/packages/bitcore-node/src/models/coin.ts b/packages/bitcore-node/src/models/coin.ts index 7b60111a715..3c9db75b33b 100644 --- a/packages/bitcore-node/src/models/coin.ts +++ b/packages/bitcore-node/src/models/coin.ts @@ -3,6 +3,7 @@ import { BaseModel, MongoBound } from './base'; import { ObjectID } from 'mongodb'; import { SpentHeightIndicators, CoinJSON } from '../types/Coin'; import { valueOrDefault } from '../utils/check'; +import { StorageService } from '../services/storage'; export type ICoin = { network: string; @@ -21,9 +22,9 @@ export type ICoin = { }; @LoggifyClass -class Coin extends BaseModel { - constructor() { - super('coins'); +class CoinSchema extends BaseModel { + constructor(storage?: StorageService) { + super('coins', storage); } allowedPaging = [ @@ -166,4 +167,4 @@ class Coin extends BaseModel { return JSON.stringify(transform); } } -export let CoinModel = new Coin(); +export let CoinModel = new CoinSchema(); diff --git a/packages/bitcore-node/src/models/events.ts b/packages/bitcore-node/src/models/events.ts index f75edf474cd..937b9bc9837 100644 --- a/packages/bitcore-node/src/models/events.ts +++ b/packages/bitcore-node/src/models/events.ts @@ -2,6 +2,7 @@ import { BaseModel } from './base'; import { ITransaction } from './transaction'; import { IBlock } from '../types/Block'; import { ICoin } from './coin'; +import { StorageService } from '../services/storage'; export namespace IEvent { export type BlockEvent = IBlock; @@ -13,9 +14,9 @@ interface IEvent { type: 'block' | 'tx' | 'coin'; emitTime: Date; } -class Event extends BaseModel { - constructor() { - super('events'); +export class EventSchema extends BaseModel { + constructor(storage?: StorageService) { + super('events', storage); } allowedPaging = []; @@ -53,4 +54,4 @@ class Event extends BaseModel { return this.collection.find({ type: 'coin', emitTime: { $gte: lastSeen } }).addCursorFlag('noCursorTimeout', true); } } -export const EventModel = new Event(); +export const EventModel = new EventSchema(); diff --git a/packages/bitcore-node/src/models/rateLimit.ts b/packages/bitcore-node/src/models/rateLimit.ts index 7292dac74d6..3d3051712c3 100644 --- a/packages/bitcore-node/src/models/rateLimit.ts +++ b/packages/bitcore-node/src/models/rateLimit.ts @@ -1,5 +1,6 @@ import { BaseModel } from './base'; import { ObjectID } from 'mongodb'; +import { StorageService } from '../services/storage'; export type IRateLimit = { _id?: ObjectID; @@ -11,9 +12,9 @@ export type IRateLimit = { expireAt?: Date; }; -export class RateLimit extends BaseModel { - constructor() { - super('ratelimits'); +export class RateLimitSchema extends BaseModel { + constructor(storage?: StorageService) { + super('ratelimits', storage); } allowedPaging = []; @@ -25,9 +26,9 @@ export class RateLimit extends BaseModel { incrementAndCheck(identifier: string, method: string) { return Promise.all([ this.collection.findOneAndUpdate( - { identifier, method, period: 'second', time: {$gt: new Date(Date.now() - 1000)} }, + { identifier, method, period: 'second', time: { $gt: new Date(Date.now() - 1000) } }, { - $setOnInsert: { time: new Date(), expireAt: new Date(Date.now() + 10 * 1000) }, + $setOnInsert: { time: new Date(), expireAt: new Date(Date.now() + 10 * 1000) }, $inc: { count: 1 } }, { upsert: true, returnOriginal: false } @@ -47,9 +48,9 @@ export class RateLimit extends BaseModel { $inc: { count: 1 } }, { upsert: true, returnOriginal: false } - ), + ) ]); } } -export let RateLimitModel = new RateLimit(); +export let RateLimitModel = new RateLimitSchema(); diff --git a/packages/bitcore-node/src/models/state.ts b/packages/bitcore-node/src/models/state.ts index cc0e44d3f11..9cc1e82177e 100644 --- a/packages/bitcore-node/src/models/state.ts +++ b/packages/bitcore-node/src/models/state.ts @@ -1,3 +1,4 @@ +import { StorageService } from '../services/storage'; import { BaseModel } from './base'; import { ObjectID } from 'mongodb'; @@ -6,13 +7,13 @@ export type IState = { initialSyncComplete: any; }; -export class State extends BaseModel { - constructor() { - super('state'); +export class StateSchema extends BaseModel { + constructor(storage?: StorageService) { + super('state', storage); } allowedPaging = []; onConnect() {} } -export let StateModel = new State(); +export let StateModel = new StateSchema(); diff --git a/packages/bitcore-node/src/models/transaction.ts b/packages/bitcore-node/src/models/transaction.ts index 408e70a14bf..a8505ad7faa 100644 --- a/packages/bitcore-node/src/models/transaction.ts +++ b/packages/bitcore-node/src/models/transaction.ts @@ -7,12 +7,12 @@ import { LoggifyClass } from '../decorators/Loggify'; import { Bitcoin } from '../types/namespaces/Bitcoin'; import { BaseModel, MongoBound } from './base'; import logger from '../logger'; -import config from '../config'; -import { StreamingFindOptions, Storage } from '../services/storage'; +import { StreamingFindOptions, Storage, StorageService } from '../services/storage'; import * as lodash from 'lodash'; import { Socket } from '../services/socket'; import { TransactionJSON } from '../types/Transaction'; import { SpentHeightIndicators } from '../types/Coin'; +import { Config } from '../services/config'; const Chain = require('../chain'); @@ -35,9 +35,9 @@ export type ITransaction = { }; @LoggifyClass -export class Transaction extends BaseModel { - constructor() { - super('transactions'); +export class TransactionSchema extends BaseModel { + constructor(storage?: StorageService) { + super('transactions', storage); } allowedPaging = [ @@ -82,7 +82,7 @@ export class Transaction extends BaseModel { logger.debug('Minting Coins', mintOps.length); if (mintOps.length) { await Promise.all( - partition(mintOps, mintOps.length / config.maxPoolSize).map(mintBatch => + partition(mintOps, mintOps.length / Config.current.maxPoolSize).map(mintBatch => CoinModel.collection.bulkWrite(mintBatch, { ordered: false }) ) ); @@ -91,7 +91,7 @@ export class Transaction extends BaseModel { logger.debug('Spending Coins', spendOps.length); if (spendOps.length) { await Promise.all( - partition(spendOps, spendOps.length / config.maxPoolSize).map(spendBatch => + partition(spendOps, spendOps.length / Config.current.maxPoolSize).map(spendBatch => CoinModel.collection.bulkWrite(spendBatch, { ordered: false }) ) ); @@ -101,7 +101,7 @@ export class Transaction extends BaseModel { const txOps = await this.addTransactions({ ...params, mintOps }); logger.debug('Writing Transactions', txOps.length); await Promise.all( - partition(txOps, txOps.length / config.maxPoolSize).map(txBatch => + partition(txOps, txOps.length / Config.current.maxPoolSize).map(txBatch => this.collection.bulkWrite(txBatch, { ordered: false }) ) ); @@ -334,7 +334,7 @@ export class Transaction extends BaseModel { } } - if (initialSyncComplete || config.api.wallets.allowCreationBeforeCompleteSync) { + if (initialSyncComplete || Config.for('api').wallets.allowCreationBeforeCompleteSync) { let mintOpsAddresses = {}; for (const mintOp of mintOps) { mintOpsAddresses[mintOp.updateOne.update.$set.address] = true; @@ -489,4 +489,4 @@ export class Transaction extends BaseModel { return JSON.stringify(transaction); } } -export let TransactionModel = new Transaction(); +export let TransactionModel = new TransactionSchema(); diff --git a/packages/bitcore-node/src/models/wallet.ts b/packages/bitcore-node/src/models/wallet.ts index 36033696050..7f376e159c8 100644 --- a/packages/bitcore-node/src/models/wallet.ts +++ b/packages/bitcore-node/src/models/wallet.ts @@ -2,6 +2,7 @@ import { WalletAddressModel } from '../models/walletAddress'; import { BaseModel } from './base'; import { TransformOptions } from '../types/TransformOptions'; import { ObjectID } from 'mongodb'; +import { StorageService } from '../services/storage'; export type IWallet = { _id?: ObjectID; @@ -13,9 +14,9 @@ export type IWallet = { path: string; }; -export class Wallet extends BaseModel { - constructor() { - super('wallets'); +export class WalletSchema extends BaseModel { + constructor(storage?: StorageService) { + super('wallets', storage); } allowedPaging = []; @@ -32,10 +33,13 @@ export class Wallet extends BaseModel { } async updateCoins(wallet: IWallet) { - let addressModels = await WalletAddressModel.collection.find({ wallet: wallet._id }).addCursorFlag('noCursorTimeout', true).toArray(); + let addressModels = await WalletAddressModel.collection + .find({ wallet: wallet._id }) + .addCursorFlag('noCursorTimeout', true) + .toArray(); let addresses = addressModels.map(model => model.address); return WalletAddressModel.updateCoins({ wallet, addresses }); } } -export let WalletModel = new Wallet(); +export let WalletModel = new WalletSchema(); diff --git a/packages/bitcore-node/src/models/walletAddress.ts b/packages/bitcore-node/src/models/walletAddress.ts index f3f4a3801e4..830f6b53ee8 100644 --- a/packages/bitcore-node/src/models/walletAddress.ts +++ b/packages/bitcore-node/src/models/walletAddress.ts @@ -4,6 +4,7 @@ import { ObjectID } from 'mongodb'; import { BaseModel } from './base'; import { IWallet } from './wallet'; import { TransactionModel } from './transaction'; +import { StorageService } from '../services/storage'; export type IWalletAddress = { wallet: ObjectID; @@ -12,9 +13,9 @@ export type IWalletAddress = { network: string; }; -export class WalletAddress extends BaseModel { - constructor() { - super('walletaddresses'); +export class WalletAddressSchema extends BaseModel { + constructor(storage?: StorageService) { + super('walletaddresses', storage); } allowedPaging = []; @@ -79,7 +80,7 @@ export class WalletAddress extends BaseModel { }; const ProcessBatch = batch => { - return Promise.all([ AddAddresses(batch), UpdateCoins(batch)]); + return Promise.all([AddAddresses(batch), UpdateCoins(batch)]); }; for (const address of addresses) { @@ -124,4 +125,4 @@ export class WalletAddress extends BaseModel { } } -export let WalletAddressModel = new WalletAddress(); +export let WalletAddressModel = new WalletAddressSchema(); diff --git a/packages/bitcore-node/src/providers/chain-state/internal/internal.ts b/packages/bitcore-node/src/providers/chain-state/internal/internal.ts index 6fbb5c301d0..cf415df0378 100644 --- a/packages/bitcore-node/src/providers/chain-state/internal/internal.ts +++ b/packages/bitcore-node/src/providers/chain-state/internal/internal.ts @@ -1,5 +1,4 @@ -import { TransactionJSON } from "../../../types/Transaction"; -import config from '../../../config'; +import { TransactionJSON } from '../../../types/Transaction'; import through2 from 'through2'; import { MongoBound } from '../../../models/base'; @@ -17,6 +16,7 @@ import { ListTransactionsStream } from './transforms'; import { StringifyJsonStream } from '../../../utils/stringifyJsonStream'; import { StateModel } from '../../../models/state'; import { SpentHeightIndicators, CoinJSON } from '../../../types/Coin'; +import { Config } from '../../../services/config'; @LoggifyClass export class InternalStateProvider implements CSP.IChainStateService { @@ -27,7 +27,7 @@ export class InternalStateProvider implements CSP.IChainStateService { } getRPC(chain: string, network: string) { - const RPC_PEER = config.chains[chain][network].rpc; + const RPC_PEER = Config.current.chains[chain][network].rpc; if (!RPC_PEER) { throw new Error(`RPC not configured for ${chain} ${network}`); } @@ -230,7 +230,7 @@ export class InternalStateProvider implements CSP.IChainStateService { const state = await StateModel.collection.findOne({}); const initialSyncComplete = state && state.initialSyncComplete && state.initialSyncComplete.includes(`${chain}:${network}`); - if (!initialSyncComplete && !config.api.wallets.allowCreationBeforeCompleteSync) { + if (!initialSyncComplete && !Config.for('api').wallets.allowCreationBeforeCompleteSync) { throw 'Wallet creation not permitted before intitial sync is complete'; } const wallet: IWallet = { diff --git a/packages/bitcore-node/src/routes/api/wallet.ts b/packages/bitcore-node/src/routes/api/wallet.ts index 578fa7ab9fb..75400092887 100644 --- a/packages/bitcore-node/src/routes/api/wallet.ts +++ b/packages/bitcore-node/src/routes/api/wallet.ts @@ -1,3 +1,4 @@ +import { Config } from "../../services/config"; import { Request, Response, Router } from 'express'; import { ChainNetwork } from '../../types/ChainNetwork'; import { IWallet } from '../../models/wallet'; @@ -5,7 +6,6 @@ import { RequestHandler } from 'express-serve-static-core'; import { ChainStateProvider } from '../../providers/chain-state'; import logger from '../../logger'; import { MongoBound } from '../../models/base'; -import config from '../../config'; const router = Router({ mergeParams: true }); const secp256k1 = require('secp256k1'); const bitcoreLib = require('bitcore-lib'); @@ -53,7 +53,7 @@ const authenticate: RequestHandler = async (req: PreAuthRequest, res: Response, return res.status(404).send('Wallet not found'); } Object.assign(req, { wallet }); - if(config.api.wallets.allowUnauthenticatedCalls) { + if(Config.for('api').wallets.allowUnauthenticatedCalls) { return next(); } try { diff --git a/packages/bitcore-node/src/routes/middleware.ts b/packages/bitcore-node/src/routes/middleware.ts index 045eca8a04d..200aabbf6fe 100644 --- a/packages/bitcore-node/src/routes/middleware.ts +++ b/packages/bitcore-node/src/routes/middleware.ts @@ -1,7 +1,7 @@ import logger from '../logger'; import * as express from 'express'; import { RateLimitModel } from '../models/rateLimit'; -import config from '../config'; +import { Config } from "../services/config"; type TimedRequest = { startTime?: Date; @@ -71,7 +71,7 @@ export function RateLimiter(method: string, perSecond: number, perMinute: number return async (req: express.Request, res: express.Response, next: express.NextFunction) => { try { const identifier = req.header('CF-Connecting-IP') || req.socket.remoteAddress || ''; - if (config.api.rateLimiter.whitelist.includes(identifier)) { + if (Config.for('api').rateLimiter.whitelist.includes(identifier)) { return next(); } let [perSecondResult, perMinuteResult, perHourResult] = await RateLimitModel.incrementAndCheck( diff --git a/packages/bitcore-node/src/server.ts b/packages/bitcore-node/src/server.ts index b22ed3ddaa3..5dab5d970e4 100755 --- a/packages/bitcore-node/src/server.ts +++ b/packages/bitcore-node/src/server.ts @@ -1,43 +1,45 @@ -import { P2pService } from './services/p2p'; +import { P2P } from './services/p2p'; import { Storage } from './services/storage'; import { Worker } from './services/worker'; import { Api } from './services/api'; import cluster = require('cluster'); import parseArgv from './utils/parseArgv'; +import { Event } from './services/event'; let args = parseArgv([], ['DEBUG']); -process.on('unhandledRejection', (error) => { - console.error('Unhandled Rejection at:', error.stack || error) +process.on('unhandledRejection', error => { + console.error('Unhandled Rejection at:', error.stack || error); }); const startServices = async () => { - await Storage.start({}); await Worker.start(); - P2pService.startConfiguredChains(); + await P2P.start(); }; -const runMaster = async() => { +const runMaster = async () => { await startServices(); // start the API on master if we are in debug - if(args.DEBUG){ - Api.start(); + if (args.DEBUG) { + await Api.start(); } }; -const runWorker = async() => { +const runWorker = async () => { // don't run any workers when in debug mode - if(!args.DEBUG){ + if (!args.DEBUG) { // Api will automatically start storage if it isn't already running - Api.start(); + await Api.start(); } -} +}; -const start = async() => { - if(cluster.isMaster){ +const start = async () => { + await Storage.start({}); + await Event.start(); + if (cluster.isMaster) { await runMaster(); - } else{ + } else { await runWorker(); } -} +}; start(); diff --git a/packages/bitcore-node/src/services/api.ts b/packages/bitcore-node/src/services/api.ts index b8ab637f580..95990b66c7e 100644 --- a/packages/bitcore-node/src/services/api.ts +++ b/packages/bitcore-node/src/services/api.ts @@ -1,39 +1,59 @@ import * as http from 'http'; -import SocketIO = require('socket.io'); -import mongoose from 'mongoose'; import app from '../routes'; import logger from '../logger'; import config from '../config'; import { LoggifyClass } from '../decorators/Loggify'; -import { Storage } from './storage'; -import { Socket } from './socket'; +import { Storage, StorageService } from './storage'; +import { Socket, SocketService } from './socket'; +import { ConfigService, Config } from './config'; +import { ConfigType } from '../types/Config'; @LoggifyClass export class ApiService { port: number; timeout: number; + configService: ConfigService; + serviceConfig: ConfigType['services']['api']; + storageService: StorageService; + socketService: SocketService; + httpServer: http.Server; - constructor(options) { - const { port, timeout } = options; - - this.port = port || 3000; - this.timeout = timeout || 600000; + constructor({ + port = 3000, + timeout = 600000, + configService = Config, + storageService = Storage, + socketService = Socket + } = {}) { + this.port = port; + this.timeout = timeout; + this.configService = configService; + this.serviceConfig = this.configService.for('api'); + this.storageService = storageService; + this.socketService = socketService; + this.httpServer = new http.Server(app); } - async start() { - if (mongoose.connection.readyState !== 1) { - await Storage.start({}); + async start({ config = Config.current } = {}) { + if (!config.services.api.enabled) { + return; + } + if (!this.storageService.connected) { + await this.storageService.start({}); } - const httpServer = new http.Server(app); - const io = SocketIO(httpServer); - httpServer.listen(this.port, () => { + this.httpServer.timeout = this.timeout; + this.httpServer.listen(this.port, () => { logger.info(`API server started on port ${this.port}`); - Socket.setServer(io); + this.socketService.start({ server: this.httpServer, config }); }); - httpServer.timeout = this.timeout; + return this.httpServer; } - stop() {} + stop() { + return new Promise(resolve => { + this.httpServer.close(resolve); + }); + } } // TOOO: choose a place in the config for the API timeout and include it here diff --git a/packages/bitcore-node/src/services/config.ts b/packages/bitcore-node/src/services/config.ts new file mode 100644 index 00000000000..f58a2ccf585 --- /dev/null +++ b/packages/bitcore-node/src/services/config.ts @@ -0,0 +1,33 @@ +import { ConfigType } from '../types/Config'; +import config from '../config'; + +type RecursivePartial = { [P in keyof T]?: RecursivePartial }; +type ServiceName = keyof ConfigType['services']; + +export class ConfigService { + _config: ConfigType; + + constructor({ _config = config } = {}) { + this._config = _config; + } + + public get current() { + return this._config; + } + + public updateConfig(partialConfig: RecursivePartial) { + const newConfig = Object.assign({}, this.current, partialConfig); + this._config = newConfig; + console.log('Writing', newConfig); + } + + public for(service: T): ConfigType['services'][T] { + return this.current.services[service]; + } + + public isEnabled(service: ServiceName) { + return this.current.services[service].enabled; + } +} + +export const Config = new ConfigService(); diff --git a/packages/bitcore-node/src/services/event.ts b/packages/bitcore-node/src/services/event.ts index a756b52c8be..c2a2be9af27 100644 --- a/packages/bitcore-node/src/services/event.ts +++ b/packages/bitcore-node/src/services/event.ts @@ -1,30 +1,56 @@ +import logger from '../logger'; +import { StorageService } from './storage'; import { LoggifyClass } from '../decorators/Loggify'; -import { EventModel, IEvent } from '../models/events'; +import { EventModel, IEvent, EventSchema } from '../models/events'; import { PassThrough } from 'stream'; import { Storage } from './storage'; +import { Config, ConfigService } from './config'; +import { ConfigType } from '../types/Config'; @LoggifyClass export class EventService { txStream = new PassThrough({ objectMode: true }); blockStream = new PassThrough({ objectMode: true }); addressCoinStream = new PassThrough({ objectMode: true }); + storageService: StorageService; + configService: ConfigService; + serviceConfig: ConfigType['services']['event']; + eventModel: EventSchema; + stopped = false; - constructor() { + constructor({ storageService = Storage, eventModel = EventModel, configService = Config } = {}) { + this.storageService = storageService; + this.configService = configService; + this.eventModel = eventModel; this.signalTx = this.signalTx.bind(this); this.signalBlock = this.signalBlock.bind(this); this.signalAddressCoin = this.signalAddressCoin.bind(this); - Storage.connection.on('CONNECTED', () => { + this.serviceConfig = this.configService.for('event'); + } + + start(config: ConfigType = this.configService.current) { + if (!config.services.event.enabled) { + return; + } + logger.info('Starting Event Service'); + this.stopped = false; + this.storageService.connection.on('CONNECTED', () => { this.wireup(); }); } + stop() { + logger.info('Stopping Event Service'); + this.stopped = true; + } + async wireup() { let lastBlockUpdate = new Date(); let lastTxUpdate = new Date(); let lastAddressTxUpdate = new Date(); const retryTxCursor = async () => { - const txCursor = EventModel.getTxTail(lastTxUpdate); + const txCursor = this.eventModel.getTxTail(lastTxUpdate); while (await txCursor.hasNext()) { const txEvent = await txCursor.next(); if (txEvent) { @@ -33,12 +59,14 @@ export class EventService { lastTxUpdate = new Date(); } } - setTimeout(retryTxCursor, 5000); + if (!this.stopped) { + setTimeout(retryTxCursor, 5000); + } }; retryTxCursor(); const retryBlockCursor = async () => { - const blockCursor = EventModel.getBlockTail(lastBlockUpdate); + const blockCursor = this.eventModel.getBlockTail(lastBlockUpdate); while (await blockCursor.hasNext()) { const blockEvent = await blockCursor.next(); if (blockEvent) { @@ -47,12 +75,14 @@ export class EventService { lastBlockUpdate = new Date(); } } - setTimeout(retryBlockCursor, 5000); + if (!this.stopped) { + setTimeout(retryBlockCursor, 5000); + } }; retryBlockCursor(); const retryAddressTxCursor = async () => { - const addressTxCursor = EventModel.getCoinTail(lastAddressTxUpdate); + const addressTxCursor = this.eventModel.getCoinTail(lastAddressTxUpdate); while (await addressTxCursor.hasNext()) { const addressTx = await addressTxCursor.next(); if (addressTx) { @@ -61,21 +91,23 @@ export class EventService { lastAddressTxUpdate = new Date(); } } - setTimeout(retryAddressTxCursor, 5000); + if (!this.stopped) { + setTimeout(retryAddressTxCursor, 5000); + } }; retryAddressTxCursor(); } async signalBlock(block: IEvent.BlockEvent) { - await EventModel.signalBlock(block); + await this.eventModel.signalBlock(block); } async signalTx(tx: IEvent.TxEvent) { - await EventModel.signalTx(tx); + await this.eventModel.signalTx(tx); } async signalAddressCoin(payload: IEvent.CoinEvent) { - await EventModel.signalAddressCoin(payload); + await this.eventModel.signalAddressCoin(payload); } } diff --git a/packages/bitcore-node/src/services/p2p.ts b/packages/bitcore-node/src/services/p2p.ts index e4fc7e83257..d5b3d0b52a6 100644 --- a/packages/bitcore-node/src/services/p2p.ts +++ b/packages/bitcore-node/src/services/p2p.ts @@ -1,16 +1,62 @@ -import config from '../config'; import logger from '../logger'; import { EventEmitter } from 'events'; -import { BlockModel } from '../models/block'; +import { BlockModel, BlockSchema } from '../models/block'; import { ChainStateProvider } from '../providers/chain-state'; import { TransactionModel } from '../models/transaction'; import { Bitcoin } from '../types/namespaces/Bitcoin'; import { StateModel } from '../models/state'; import { SpentHeightIndicators } from '../types/Coin'; +import { Config, ConfigService } from './config'; +import { ConfigType } from '../types/Config'; const Chain = require('../chain'); const LRU = require('lru-cache'); -export class P2pService { +export class P2pManager { + workers = new Array(); + + private configService: ConfigService; + + constructor({ configService = Config } = {}) { + this.configService = configService; + } + + async stop() { + logger.info('Stopping P2P Manager'); + for (const worker of this.workers) { + await worker.stop(); + } + } + + async start({ config = this.configService.current, blockModel = BlockModel } = {}) { + if (!config.services.p2p.enabled) { + return; + } + logger.info('Starting P2P Manager'); + const p2pWorkers = new Array(); + for (let chain of Object.keys(config.chains)) { + for (let network of Object.keys(config.chains[chain])) { + const chainConfig = config.chains[chain][network]; + if (chainConfig.chainSource && chainConfig.chainSource !== 'p2p') { + continue; + } + const p2pWorker = new P2pWorker({ + chain, + network, + chainConfig, + blockModel + }); + p2pWorkers.push(p2pWorker); + try { + p2pWorker.start(config); + } catch (e) { + logger.error('P2P Worker died with', e); + } + } + } + } +} + +export class P2pWorker { private chain: string; private network: string; private bitcoreLib: any; @@ -20,10 +66,14 @@ export class P2pService { private syncing: boolean; private messages: any; private pool: any; + private connectInterval?: NodeJS.Timer; private invCache: any; private initialSyncComplete: boolean; - constructor(params) { - const { chain, network, chainConfig } = params; + private stopped: boolean; + private blockModel: BlockSchema; + constructor({ chain, network, chainConfig, blockModel = BlockModel }) { + this.blockModel = blockModel; + this.stopped = true; this.chain = chain; this.network = network; this.bitcoreLib = Chain[this.chain].lib; @@ -130,25 +180,17 @@ export class P2pService { async connect() { this.pool.connect(); - setInterval(this.pool.connect.bind(this.pool), 5000); + this.connectInterval = setInterval(this.pool.connect.bind(this.pool), 5000); return new Promise(resolve => { this.pool.once('peerready', () => resolve()); }); } - static startConfiguredChains() { - for (let chain of Object.keys(config.chains)) { - for (let network of Object.keys(config.chains[chain])) { - const chainConfig = config.chains[chain][network]; - if (chainConfig.chainSource && chainConfig.chainSource !== 'p2p') { - continue; - } - new P2pService({ - chain, - network, - chainConfig - }).start(); - } + async disconnect() { + this.pool.removeAllListeners(); + this.pool.disconnect(); + if (this.connectInterval) { + clearInterval(this.connectInterval); } } @@ -201,7 +243,7 @@ export class P2pService { async processBlock(block): Promise { return new Promise(async (resolve, reject) => { try { - await BlockModel.addBlock({ + await this.blockModel.addBlock({ chain: this.chain, network: this.network, forkHeight: this.chainConfig.forkHeight, @@ -237,7 +279,7 @@ export class P2pService { } async sync() { - if (this.syncing) { + if (this.syncing || this.stopped) { return; } this.syncing = true; @@ -271,6 +313,9 @@ export class P2pService { logger.info(`Syncing ${headers.length} blocks for ${chain} ${network}`); for (const header of headers) { try { + if (!this.stopped) { + throw new Error('Stopped'); + } const block = await this.getBlock(header.hash); await this.processBlock(block); currentHeight++; @@ -285,7 +330,9 @@ export class P2pService { } catch (err) { logger.error(`Error syncing ${chain} ${network}`, err); this.syncing = false; - return this.sync(); + if (!this.stopped) { + return this.sync(); + } } } headers = await getHeaders(); @@ -300,10 +347,22 @@ export class P2pService { return true; } - async start() { + async stop() { + logger.debug(`Stopping worker for chain ${this.chain}`); + this.stopped = true; + await this.disconnect(); + } + + async start(config: ConfigType) { + if (!config.services.p2p.enabled) { + return; + } + this.stopped = false; logger.debug(`Started worker for chain ${this.chain}`); this.setupListeners(); await this.connect(); this.sync(); } } + +export const P2P = new P2pManager(); diff --git a/packages/bitcore-node/src/services/socket.ts b/packages/bitcore-node/src/services/socket.ts index 4ab829bf15f..6bf35630d1b 100644 --- a/packages/bitcore-node/src/services/socket.ts +++ b/packages/bitcore-node/src/services/socket.ts @@ -1,8 +1,12 @@ +import logger from '../logger'; import SocketIO = require('socket.io'); +import * as http from 'http'; import { LoggifyClass } from '../decorators/Loggify'; -import { EventModel, IEvent } from '../models/events'; -import { Event } from './event'; +import { EventModel, IEvent, EventSchema } from '../models/events'; +import { Event, EventService } from './event'; import { ObjectID } from 'mongodb'; +import { Config, ConfigService } from './config'; +import { ConfigType } from '../types/Config'; function SanitizeWallet(x: { wallets: ObjectID[] }) { const sanitized = Object.assign({}, x, { wallets: undefined }); @@ -14,18 +18,32 @@ function SanitizeWallet(x: { wallets: ObjectID[] }) { @LoggifyClass export class SocketService { + httpServer?: http.Server; io?: SocketIO.Server; id: number = Math.random(); + configService: ConfigService; + serviceConfig: ConfigType['services']['socket']; + eventService: EventService; + eventModel: EventSchema; - constructor() { - this.setServer = this.setServer.bind(this); + constructor({ eventService = Event, eventModel = EventModel, configService = Config } = {}) { + this.eventService = eventService; + this.configService = configService; + this.serviceConfig = this.configService.for('socket'); + this.eventModel = eventModel; + this.start = this.start.bind(this); this.signalTx = this.signalTx.bind(this); this.signalBlock = this.signalBlock.bind(this); this.signalAddressCoin = this.signalAddressCoin.bind(this); } - setServer(io: SocketIO.Server) { - this.io = io; + start({ server, config = this.configService.current }: { server: http.Server; config: ConfigType }) { + if (!config.services.socket.enabled) { + return; + } + logger.info('Starting Socket Service'); + this.httpServer = server; + this.io = SocketIO(server); this.io.sockets.on('connection', socket => { socket.on('room', room => { socket.join(room); @@ -34,8 +52,19 @@ export class SocketService { this.wireup(); } + stop() { + logger.info('Stopping Socket Service'); + return new Promise(resolve => { + if (this.io) { + this.io.close(resolve); + } else { + resolve(); + } + }); + } + async wireup() { - Event.txStream.on('data', (tx: IEvent.TxEvent) => { + this.eventService.txStream.on('data', (tx: IEvent.TxEvent) => { if (this.io) { const { chain, network } = tx; const sanitizedTx = SanitizeWallet(tx); @@ -43,14 +72,14 @@ export class SocketService { } }); - Event.blockStream.on('data', (block: IEvent.BlockEvent) => { + this.eventService.blockStream.on('data', (block: IEvent.BlockEvent) => { if (this.io) { const { chain, network } = block; this.io.sockets.in(`/${chain}/${network}/inv`).emit('block', block); } }); - Event.addressCoinStream.on('data', (addressCoin: IEvent.CoinEvent) => { + this.eventService.addressCoinStream.on('data', (addressCoin: IEvent.CoinEvent) => { if (this.io) { const { coin, address } = addressCoin; const { chain, network } = coin; diff --git a/packages/bitcore-node/src/services/storage.ts b/packages/bitcore-node/src/services/storage.ts index 2458585d0f2..9d7b41108a2 100644 --- a/packages/bitcore-node/src/services/storage.ts +++ b/packages/bitcore-node/src/services/storage.ts @@ -2,13 +2,14 @@ import { EventEmitter } from 'events'; import { Request, Response } from 'express'; import { TransformableModel } from '../types/TransformableModel'; import logger from '../logger'; -import config from '../config'; import { LoggifyClass } from '../decorators/Loggify'; import { ObjectID } from 'mongodb'; import { MongoClient, Db, Cursor } from 'mongodb'; import { MongoBound } from '../models/base'; import '../models'; import { StreamingFindOptions } from '../types/Query'; +import { ConfigType } from '../types/Config'; +import { Config, ConfigService } from './config'; export { StreamingFindOptions }; @@ -18,10 +19,15 @@ export class StorageService { db?: Db; connected: boolean = false; connection = new EventEmitter(); + configService: ConfigService; - start(args: any): Promise { + constructor({ configService = Config } = {}) { + this.configService = configService; + } + + start(args: Partial = {}): Promise { return new Promise((resolve, reject) => { - let options = Object.assign({}, config, args); + let options = Object.assign({}, this.configService.current, args); let { dbName, dbHost, dbPort } = options; const connectUrl = `mongodb://${dbHost}:${dbPort}/${dbName}?socketTimeoutMS=3600000&noDelay=true`; let attemptConnect = async () => { @@ -29,7 +35,7 @@ export class StorageService { connectUrl, { keepAlive: true, - poolSize: config.maxPoolSize, + poolSize: options.maxPoolSize, useNewUrlParser: true } ); @@ -174,9 +180,12 @@ export class StorageService { ) { const { query, options } = this.getFindOptions(model, originalOptions); const finalQuery = Object.assign({}, originalQuery, query); - let cursor = model.collection.find(finalQuery, options).addCursorFlag('noCursorTimeout', true).stream({ - transform: transform || model._apiTransform - }); + let cursor = model.collection + .find(finalQuery, options) + .addCursorFlag('noCursorTimeout', true) + .stream({ + transform: transform || model._apiTransform + }); if (options.sort) { cursor = cursor.sort(options.sort); } diff --git a/packages/bitcore-node/src/types/Config.d.ts b/packages/bitcore-node/src/types/Config.d.ts index 9d63ffdb079..8971f5334b1 100644 --- a/packages/bitcore-node/src/types/Config.d.ts +++ b/packages/bitcore-node/src/types/Config.d.ts @@ -1,4 +1,4 @@ -export default interface Config { +export interface ConfigType { maxPoolSize: number; port: number; dbHost: string; @@ -7,15 +7,30 @@ export default interface Config { numWorkers: number; chains: { - [currency: string]: any; + [currency: string]: { [network: string]: any }; }; - api: { - rateLimiter: { - whitelist: [string]; - }, - wallets: { - allowCreationBeforeCompleteSync?: boolean; - allowUnauthenticatedCalls?: boolean; + services: { + api: { + enabled: boolean; + rateLimiter: { + whitelist: [string]; + }; + wallets: { + allowCreationBeforeCompleteSync?: boolean; + allowUnauthenticatedCalls?: boolean; + }; + }; + event: { + enabled: boolean; + }; + p2p: { + enabled: boolean; + }; + socket: { + enabled: boolean; + }; + storage: { + enabled: boolean; }; }; }