From 0a612d0c37a2ecbe037569ce31fda8094eee6ed7 Mon Sep 17 00:00:00 2001 From: acolytec3 Date: Mon, 7 Aug 2023 16:24:51 -0400 Subject: [PATCH] Collapse `EthereumService` abstraction service (#2943) * Collapse ethereumservice into service * Clean up ethereumservice references --- packages/client/src/rpc/modules/admin.ts | 4 +- packages/client/src/rpc/modules/eth.ts | 6 +- packages/client/src/rpc/modules/net.ts | 4 +- packages/client/src/rpc/modules/web3.ts | 4 +- .../client/src/service/ethereumservice.ts | 152 ------------------ .../client/src/service/fullethereumservice.ts | 7 +- packages/client/src/service/index.ts | 1 - .../src/service/lightethereumservice.ts | 20 ++- packages/client/src/service/service.ts | 81 +++++++++- 9 files changed, 107 insertions(+), 172 deletions(-) delete mode 100644 packages/client/src/service/ethereumservice.ts diff --git a/packages/client/src/rpc/modules/admin.ts b/packages/client/src/rpc/modules/admin.ts index 443114c6c4..c251c6566e 100644 --- a/packages/client/src/rpc/modules/admin.ts +++ b/packages/client/src/rpc/modules/admin.ts @@ -6,7 +6,7 @@ import { middleware } from '../validation' import type { Chain } from '../../blockchain' import type { EthereumClient } from '../../client' import type { RlpxServer } from '../../net/server' -import type { EthereumService } from '../../service' +import type { Service } from '../../service' /** * admin_* RPC module @@ -21,7 +21,7 @@ export class Admin { * @param client Client to which the module binds */ constructor(client: EthereumClient) { - const service = client.services.find((s) => s.name === 'eth') as EthereumService + const service = client.services.find((s) => s.name === 'eth') as Service this._chain = service.chain this._client = client diff --git a/packages/client/src/rpc/modules/eth.ts b/packages/client/src/rpc/modules/eth.ts index 317a839f5c..11f6afb7ee 100644 --- a/packages/client/src/rpc/modules/eth.ts +++ b/packages/client/src/rpc/modules/eth.ts @@ -19,7 +19,7 @@ import type { EthereumClient } from '../..' import type { Chain } from '../../blockchain' import type { ReceiptsManager } from '../../execution/receipt' import type { EthProtocol } from '../../net/protocol' -import type { EthereumService, FullEthereumService } from '../../service' +import type { FullEthereumService, Service } from '../../service' import type { RpcTx } from '../types' import type { Block, JsonRpcBlock } from '@ethereumjs/block' import type { Log } from '@ethereumjs/evm' @@ -203,7 +203,7 @@ const jsonRpcReceipt = async ( */ export class Eth { private client: EthereumClient - private service: EthereumService + private service: Service private receiptsManager: ReceiptsManager | undefined private _chain: Chain private _vm: VM | undefined @@ -215,7 +215,7 @@ export class Eth { */ constructor(client: EthereumClient) { this.client = client - this.service = client.services.find((s) => s.name === 'eth') as EthereumService + this.service = client.services.find((s) => s.name === 'eth') as Service this._chain = this.service.chain this._vm = (this.service as FullEthereumService).execution?.vm this.receiptsManager = (this.service as FullEthereumService).execution?.receiptsManager diff --git a/packages/client/src/rpc/modules/net.ts b/packages/client/src/rpc/modules/net.ts index 48c2add491..531f36ae07 100644 --- a/packages/client/src/rpc/modules/net.ts +++ b/packages/client/src/rpc/modules/net.ts @@ -5,7 +5,7 @@ import { middleware } from '../validation' import type { EthereumClient } from '../..' import type { Chain } from '../../blockchain' import type { PeerPool } from '../../net/peerpool' -import type { EthereumService } from '../../service/ethereumservice' +import type { Service } from '../../service/service' /** * net_* RPC module @@ -21,7 +21,7 @@ export class Net { * @param client Client to which the module binds */ constructor(client: EthereumClient) { - const service = client.services.find((s) => s.name === 'eth') as EthereumService + const service = client.services.find((s) => s.name === 'eth') as Service this._chain = service.chain this._client = client this._peerPool = service.pool diff --git a/packages/client/src/rpc/modules/web3.ts b/packages/client/src/rpc/modules/web3.ts index 0ce4c6c5e5..9f1e6753d0 100644 --- a/packages/client/src/rpc/modules/web3.ts +++ b/packages/client/src/rpc/modules/web3.ts @@ -6,7 +6,7 @@ import { middleware, validators } from '../validation' import type { EthereumClient } from '../..' import type { Chain } from '../../blockchain' -import type { EthereumService } from '../../service' +import type { Service } from '../../service' /** * web3_* RPC module @@ -20,7 +20,7 @@ export class Web3 { * @param client Client to which the module binds */ constructor(client: EthereumClient) { - const service = client.services.find((s) => s.name === 'eth') as EthereumService + const service = client.services.find((s) => s.name === 'eth') as Service this._chain = service.chain this.clientVersion = middleware(this.clientVersion.bind(this), 0, []) diff --git a/packages/client/src/service/ethereumservice.ts b/packages/client/src/service/ethereumservice.ts deleted file mode 100644 index eb340c7774..0000000000 --- a/packages/client/src/service/ethereumservice.ts +++ /dev/null @@ -1,152 +0,0 @@ -import { Chain } from '../blockchain' -import { FlowControl } from '../net/protocol/flowcontrol' -import { getV8Engine } from '../util' - -import { Service } from './service' - -import type { Synchronizer } from '../sync' -import type { V8Engine } from '../util' -import type { ServiceOptions } from './service' -import type { AbstractLevel } from 'abstract-level' - -export interface EthereumServiceOptions extends ServiceOptions { - /* Blockchain */ - chain: Chain - - /* Blockchain database */ - chainDB?: AbstractLevel - - /* State database */ - stateDB?: AbstractLevel - - /* Meta database (receipts, logs, indexes) */ - metaDB?: AbstractLevel - - /* Sync retry interval in ms (default: 8000) */ - interval?: number - - /* Protocol timeout in ms (default: 6000) */ - timeout?: number -} - -/** - * Ethereum service - * @memberof module:service - */ -export class EthereumService extends Service { - public flow: FlowControl - public chain: Chain - public interval: number - public timeout: number - public synchronizer?: Synchronizer - - // A handle to v8Engine lib for mem stats, assigned on open if running in node - private v8Engine: V8Engine | null = null - - /** - * Interval for client stats output (e.g. memory) (in ms) - */ - private STATS_INTERVAL = 30000 - - /** - * Shutdown the client when memory threshold is reached (in percent) - * - */ - private MEMORY_SHUTDOWN_THRESHOLD = 92 - - private _statsInterval: NodeJS.Timeout | undefined /* global NodeJS */ - - /** - * Create new ETH service - */ - constructor(options: EthereumServiceOptions) { - super(options) - - this.flow = new FlowControl() - // @ts-ignore TODO replace with async create constructor - this.chain = options.chain ?? new Chain(options) - this.interval = options.interval ?? 8000 - this.timeout = options.timeout ?? 6000 - } - - /** - * Service name - */ - get name() { - return 'eth' - } - - /** - * Open eth service. Must be called before service is started. - */ - async open() { - if (this.opened) { - return false - } - await super.open() - await this.chain.open() - await this.synchronizer?.open() - return true - } - - /** - * Starts service and ensures blockchain is synchronized. - */ - async start(): Promise { - if (this.running) { - return false - } - await super.start() - void this.synchronizer?.start() - - if (this.v8Engine === null) { - this.v8Engine = await getV8Engine() - } - - this._statsInterval = setInterval( - // eslint-disable-next-line @typescript-eslint/await-thenable - await this.stats.bind(this), - this.STATS_INTERVAL - ) - - return true - } - - stats() { - if (this.v8Engine !== null) { - const { used_heap_size, heap_size_limit } = this.v8Engine.getHeapStatistics() - - const heapUsed = Math.round(used_heap_size / 1000 / 1000) // MB - const percentage = Math.round((100 * used_heap_size) / heap_size_limit) - this.config.logger.info(`Memory stats usage=${heapUsed} MB percentage=${percentage}%`) - - if (percentage >= this.MEMORY_SHUTDOWN_THRESHOLD && !this.config.shutdown) { - this.config.logger.error('EMERGENCY SHUTDOWN DUE TO HIGH MEMORY LOAD...') - process.kill(process.pid, 'SIGINT') - } - } - } - - /** - * Stop service. Interrupts blockchain synchronization if in progress. - */ - async stop(): Promise { - if (!this.running) { - return false - } - clearInterval(this._statsInterval) - await this.synchronizer?.stop() - await super.stop() - return true - } - - /** - * Close service. - */ - async close() { - if (this.opened) { - await this.synchronizer?.close() - } - await super.close() - } -} diff --git a/packages/client/src/service/fullethereumservice.ts b/packages/client/src/service/fullethereumservice.ts index 68cd5d1647..8308c6e6eb 100644 --- a/packages/client/src/service/fullethereumservice.ts +++ b/packages/client/src/service/fullethereumservice.ts @@ -13,16 +13,15 @@ import { BeaconSynchronizer, FullSynchronizer, SnapSynchronizer } from '../sync' import { Skeleton } from '../sync/skeleton' import { Event } from '../types' -import { EthereumService } from './ethereumservice' +import { Service, type ServiceOptions } from './service' import { TxPool } from './txpool' import type { Peer } from '../net/peer/peer' import type { Protocol } from '../net/protocol' -import type { EthereumServiceOptions } from './ethereumservice' import type { Block } from '@ethereumjs/block' import type { BlobEIP4844Transaction } from '@ethereumjs/tx' -interface FullEthereumServiceOptions extends EthereumServiceOptions { +interface FullEthereumServiceOptions extends ServiceOptions { /** Serve LES requests (default: false) */ lightserv?: boolean } @@ -31,7 +30,7 @@ interface FullEthereumServiceOptions extends EthereumServiceOptions { * Full Ethereum service * @memberof module:service */ -export class FullEthereumService extends EthereumService { +export class FullEthereumService extends Service { public synchronizer?: BeaconSynchronizer | FullSynchronizer | SnapSynchronizer public lightserv: boolean public miner: Miner | undefined diff --git a/packages/client/src/service/index.ts b/packages/client/src/service/index.ts index db8586bf08..ce6c774454 100644 --- a/packages/client/src/service/index.ts +++ b/packages/client/src/service/index.ts @@ -2,7 +2,6 @@ * @module service */ -export * from './ethereumservice' export * from './fullethereumservice' export * from './lightethereumservice' export * from './service' diff --git a/packages/client/src/service/lightethereumservice.ts b/packages/client/src/service/lightethereumservice.ts index d25e552c46..ee1511c7d3 100644 --- a/packages/client/src/service/lightethereumservice.ts +++ b/packages/client/src/service/lightethereumservice.ts @@ -1,22 +1,22 @@ import { LesProtocol } from '../net/protocol/lesprotocol' import { LightSynchronizer } from '../sync/lightsync' -import { EthereumService } from './ethereumservice' +import { Service } from './service' import type { Peer } from '../net/peer/peer' -import type { EthereumServiceOptions } from './ethereumservice' +import type { ServiceOptions } from './service' /** * Light Ethereum service * @memberof module:service */ -export class LightEthereumService extends EthereumService { +export class LightEthereumService extends Service { public synchronizer: LightSynchronizer /** * Create new LES service */ - constructor(options: EthereumServiceOptions) { + constructor(options: ServiceOptions) { super(options) this.config.logger.info('Light sync mode') @@ -49,4 +49,16 @@ export class LightEthereumService extends EthereumService { * @param peer peer */ async handle(_message: any, _protocol: string, _peer: Peer) {} + + /** + * Stop service + */ + async stop(): Promise { + if (!this.running) { + return false + } + await this.synchronizer?.stop() + await super.stop() + return true + } } diff --git a/packages/client/src/service/service.ts b/packages/client/src/service/service.ts index 3857d740a5..258a4bfa27 100644 --- a/packages/client/src/service/service.ts +++ b/packages/client/src/service/service.ts @@ -1,13 +1,36 @@ +import { Chain } from '../blockchain' import { PeerPool } from '../net/peerpool' +import { FlowControl } from '../net/protocol' import { Event } from '../types' +import { type V8Engine, getV8Engine } from '../util' import type { Config } from '../config' import type { Peer } from '../net/peer/peer' import type { Protocol } from '../net/protocol' +import type { Synchronizer } from '../sync' +import type { AbstractLevel } from 'abstract-level' export interface ServiceOptions { /* Config */ config: Config + + /* Blockchain */ + chain: Chain + + /* Blockchain database */ + chainDB?: AbstractLevel + + /* State database */ + stateDB?: AbstractLevel + + /* Meta database (receipts, logs, indexes) */ + metaDB?: AbstractLevel + + /* Sync retry interval in ms (default: 8000) */ + interval?: number + + /* Protocol timeout in ms (default: 6000) */ + timeout?: number } /** @@ -19,7 +42,27 @@ export class Service { public opened: boolean public running: boolean public pool: PeerPool + public flow: FlowControl + public chain: Chain + public interval: number + public timeout: number + public synchronizer?: Synchronizer + + // A handle to v8Engine lib for mem stats, assigned on open if running in node + private v8Engine: V8Engine | null = null + + /** + * Interval for client stats output (e.g. memory) (in ms) + */ + private STATS_INTERVAL = 30000 + + /** + * Shutdown the client when memory threshold is reached (in percent) + * + */ + private MEMORY_SHUTDOWN_THRESHOLD = 92 + private _statsInterval: NodeJS.Timeout | undefined /* global NodeJS */ /** * Create new service and associated peer pool */ @@ -45,6 +88,11 @@ export class Service { } }) + this.flow = new FlowControl() + // @ts-ignore TODO replace with async create constructor + this.chain = options.chain ?? new Chain(options) + this.interval = options.interval ?? 8000 + this.timeout = options.timeout ?? 6000 this.opened = false this.running = false } @@ -53,8 +101,7 @@ export class Service { * Service name */ get name() { - return '' - //throw new Error('Unimplemented') + return 'eth' } /** @@ -85,6 +132,8 @@ export class Service { ) await this.pool.open() + await this.chain.open() + await this.synchronizer?.open() this.opened = true return true } @@ -107,6 +156,16 @@ export class Service { return false } await this.pool.start() + void this.synchronizer?.start() + if (this.v8Engine === null) { + this.v8Engine = await getV8Engine() + } + + this._statsInterval = setInterval( + // eslint-disable-next-line @typescript-eslint/await-thenable + await this.stats.bind(this), + this.STATS_INTERVAL + ) this.running = true this.config.logger.info(`Started ${this.name} service.`) return true @@ -118,13 +177,31 @@ export class Service { async stop(): Promise { if (this.opened) { await this.close() + await this.synchronizer?.close() } await this.pool.stop() + clearInterval(this._statsInterval) + await this.synchronizer?.stop() this.running = false this.config.logger.info(`Stopped ${this.name} service.`) return true } + stats() { + if (this.v8Engine !== null) { + const { used_heap_size, heap_size_limit } = this.v8Engine.getHeapStatistics() + + const heapUsed = Math.round(used_heap_size / 1000 / 1000) // MB + const percentage = Math.round((100 * used_heap_size) / heap_size_limit) + this.config.logger.info(`Memory stats usage=${heapUsed} MB percentage=${percentage}%`) + + if (percentage >= this.MEMORY_SHUTDOWN_THRESHOLD && !this.config.shutdown) { + this.config.logger.error('EMERGENCY SHUTDOWN DUE TO HIGH MEMORY LOAD...') + process.kill(process.pid, 'SIGINT') + } + } + } + /** * Handles incoming request from connected peer * @param message message object