From dc154fd3c0b134e65e158e4b34267d87de97b8b3 Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Thu, 17 Oct 2024 21:05:19 -0400 Subject: [PATCH] Switch events to eventemitter3 - the saga (#3746) * switch events to eventemitter3 * lint * remove cruft * make client play nice * fix tests * Move blockchain off of asyncEventEmitter * Modify evm emit function to handle async events * update vm to use async events * Allow sync and async listeners * remove asynceventemitter * typo and make typescript happy * typo * add events benchmark * address feedback * Call main function in example outside of own definition --------- Co-authored-by: Amir --- package-lock.json | 22 +- packages/blockchain/package.json | 3 +- packages/blockchain/src/blockchain.ts | 8 +- packages/blockchain/src/types.ts | 7 +- packages/client/bin/cli.ts | 3 +- packages/client/package.json | 7 +- packages/client/src/config.ts | 9 +- packages/client/src/net/peer/peer.ts | 2 +- packages/client/src/net/protocol/sender.ts | 2 +- packages/client/src/sync/fullsync.ts | 4 +- packages/client/src/sync/sync.ts | 4 +- packages/client/src/types.ts | 29 --- .../client/test/integration/mocks/mockpeer.ts | 2 +- .../test/integration/mocks/mocksender.ts | 2 +- .../client/test/integration/mocks/network.ts | 2 +- packages/client/test/miner/miner.spec.ts | 6 +- .../client/test/net/peer/rlpxpeer.spec.ts | 2 +- packages/client/test/net/peerpool.spec.ts | 2 +- .../test/net/protocol/boundprotocol.spec.ts | 2 +- .../test/net/protocol/rlpxsender.spec.ts | 15 +- .../client/test/net/server/rlpxserver.spec.ts | 2 +- packages/client/test/sim/simutils.ts | 1 - packages/common/package.json | 3 +- packages/common/src/common.ts | 2 +- packages/devp2p/package.json | 2 + packages/devp2p/src/dpt/dpt.ts | 2 +- packages/devp2p/src/dpt/kbucket.ts | 2 +- packages/devp2p/src/dpt/server.ts | 2 +- packages/devp2p/src/ext/kbucket.ts | 2 +- packages/devp2p/src/protocol/protocol.ts | 2 +- packages/devp2p/src/rlpx/peer.ts | 2 +- packages/devp2p/src/rlpx/rlpx.ts | 2 +- packages/devp2p/test/rlpx.spec.ts | 2 +- packages/evm/README.md | 46 ++-- packages/evm/examples/eventListener.ts | 23 ++ packages/evm/examples/runCode.ts | 3 +- packages/evm/examples/withBlockchain.ts | 3 +- packages/evm/package.json | 9 +- packages/evm/src/evm.ts | 19 +- packages/evm/src/types.ts | 7 +- packages/evm/test/customOpcodes.spec.ts | 5 +- packages/util/src/asyncEventEmitter.ts | 212 ------------------ packages/util/src/index.ts | 1 - packages/util/test/asyncEventEmitter.spec.ts | 112 --------- packages/util/test/bench/events.bench.ts | 26 +++ packages/vm/README.md | 48 ++-- packages/vm/examples/eventListener.ts | 34 +++ packages/vm/package.json | 5 +- packages/vm/src/types.ts | 2 +- packages/vm/src/vm.ts | 28 ++- packages/vm/test/api/EIPs/eip-1153.spec.ts | 3 +- packages/vm/test/api/EIPs/eip-2929.spec.ts | 3 +- .../api/EIPs/eip-2930-accesslists.spec.ts | 3 +- .../vm/test/api/EIPs/eip-3198-BaseFee.spec.ts | 4 +- packages/vm/test/api/EIPs/eip-3529.spec.ts | 7 +- packages/vm/test/api/EIPs/eip-3541.spec.ts | 7 +- packages/vm/test/api/EIPs/eip-3855.spec.ts | 8 +- ...t-difficulty-opcode-with-prevrando.spec.ts | 5 +- .../api/EIPs/eip-4895-withdrawals.spec.ts | 3 +- packages/vm/test/api/EIPs/eip-7002.spec.ts | 3 +- packages/vm/test/api/events.spec.ts | 24 +- packages/vm/test/api/runBlock.spec.ts | 3 +- packages/vm/test/api/runTx.spec.ts | 2 +- packages/vm/test/t8n/t8ntool.ts | 14 +- .../tester/runners/GeneralStateTestsRunner.ts | 6 +- 65 files changed, 319 insertions(+), 518 deletions(-) create mode 100644 packages/evm/examples/eventListener.ts delete mode 100644 packages/util/src/asyncEventEmitter.ts delete mode 100644 packages/util/test/asyncEventEmitter.spec.ts create mode 100644 packages/util/test/bench/events.bench.ts create mode 100644 packages/vm/examples/eventListener.ts diff --git a/package-lock.json b/package-lock.json index 1739b76f3c..f0631e97fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17282,6 +17282,7 @@ "@ethereumjs/rlp": "^5.0.2", "@ethereumjs/util": "^9.1.0", "debug": "^4.3.3", + "eventemitter3": "^5.0.1", "lru-cache": "10.1.0" }, "devDependencies": { @@ -17330,6 +17331,7 @@ "cors": "^2.8.5", "debug": "^4.3.3", "ethereum-cryptography": "^3.0.0", + "eventemitter3": "^5.0.1", "jayson": "^4.0.0", "level": "^8.0.0", "mcl-wasm": "^1.5.0", @@ -17455,7 +17457,8 @@ "version": "4.4.0", "license": "MIT", "dependencies": { - "@ethereumjs/util": "^9.1.0" + "@ethereumjs/util": "^9.1.0", + "eventemitter3": "^5.0.1" }, "devDependencies": { "@polkadot/util": "^12.6.2", @@ -17542,6 +17545,7 @@ "@scure/base": "^1.1.7", "debug": "^4.3.3", "ethereum-cryptography": "^3.0.0", + "eventemitter3": "^5.0.1", "lru-cache": "10.1.0", "scanf": "1.1.2", "snappyjs": "^0.6.1" @@ -17551,6 +17555,7 @@ "@ethereumjs/tx": "^5.4.0", "@types/debug": "^4.1.9", "@types/k-bucket": "^5.0.0", + "@types/node": "^22.7.6", "chalk": "^4.1.2", "testdouble": "^3.8.2" }, @@ -17558,6 +17563,15 @@ "node": ">=18" } }, + "packages/devp2p/node_modules/@types/node": { + "version": "22.7.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.6.tgz", + "integrity": "sha512-/d7Rnj0/ExXDMcioS78/kf1lMzYk4BZV8MZGTBKzTGZ6/406ukkbYlIsZmMPhcR5KlkunDHQLrtAVmSq7r+mSw==", + "dev": true, + "dependencies": { + "undici-types": "~6.19.2" + } + }, "packages/devp2p/node_modules/lru-cache": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz", @@ -17594,7 +17608,8 @@ "@ethereumjs/util": "^9.1.0", "@types/debug": "^4.1.9", "debug": "^4.3.3", - "ethereum-cryptography": "^3.0.0" + "ethereum-cryptography": "^3.0.0", + "eventemitter3": "^5.0.1" }, "devDependencies": { "@paulmillr/trusted-setups": "^0.1.2", @@ -17910,7 +17925,8 @@ "@ethereumjs/tx": "^5.4.0", "@ethereumjs/util": "^9.1.0", "debug": "^4.3.3", - "ethereum-cryptography": "^3.0.0" + "ethereum-cryptography": "^3.0.0", + "eventemitter3": "^5.0.1" }, "devDependencies": { "@ethereumjs/blockchain": "^7.3.0", diff --git a/packages/blockchain/package.json b/packages/blockchain/package.json index 5fcd199e9a..2e9d1d77f5 100644 --- a/packages/blockchain/package.json +++ b/packages/blockchain/package.json @@ -49,10 +49,11 @@ "dependencies": { "@ethereumjs/block": "^5.3.0", "@ethereumjs/common": "^4.4.0", - "@ethereumjs/rlp": "^5.0.2", "@ethereumjs/mpt": "^6.2.2", + "@ethereumjs/rlp": "^5.0.2", "@ethereumjs/util": "^9.1.0", "debug": "^4.3.3", + "eventemitter3": "^5.0.1", "lru-cache": "10.1.0" }, "devDependencies": { diff --git a/packages/blockchain/src/blockchain.ts b/packages/blockchain/src/blockchain.ts index 8e1e7c4f70..df03607bd3 100644 --- a/packages/blockchain/src/blockchain.ts +++ b/packages/blockchain/src/blockchain.ts @@ -1,7 +1,6 @@ import { Block, BlockHeader, createBlock } from '@ethereumjs/block' import { Common, ConsensusAlgorithm, ConsensusType, Hardfork, Mainnet } from '@ethereumjs/common' import { - AsyncEventEmitter, BIGINT_0, BIGINT_1, BIGINT_8, @@ -15,6 +14,7 @@ import { equalsBytes, } from '@ethereumjs/util' import debugDefault from 'debug' +import { EventEmitter } from 'eventemitter3' import { CasperConsensus } from './consensus/casper.js' import { @@ -28,7 +28,7 @@ import { DBManager } from './db/manager.js' import { DBTarget } from './db/operation.js' import type { - BlockchainEvents, + BlockchainEvent, BlockchainInterface, BlockchainOptions, Consensus, @@ -53,7 +53,7 @@ import type { Debugger } from 'debug' export class Blockchain implements BlockchainInterface { db: DB dbManager: DBManager - events: AsyncEventEmitter + events: EventEmitter private _genesisBlock?: Block /** The genesis block of this blockchain */ private _customGenesisState?: GenesisState /** Custom genesis state */ @@ -129,7 +129,7 @@ export class Blockchain implements BlockchainInterface { this.dbManager = new DBManager(this.db, this.common) - this.events = new AsyncEventEmitter() + this.events = new EventEmitter() this._consensusDict = {} this._consensusDict[ConsensusAlgorithm.Casper] = new CasperConsensus() diff --git a/packages/blockchain/src/types.ts b/packages/blockchain/src/types.ts index 0eee200bf8..052cddf3cf 100644 --- a/packages/blockchain/src/types.ts +++ b/packages/blockchain/src/types.ts @@ -1,11 +1,12 @@ import type { Blockchain } from './index.js' import type { Block, BlockHeader } from '@ethereumjs/block' import type { Common, ConsensusAlgorithm } from '@ethereumjs/common' -import type { AsyncEventEmitter, DB, DBObject, GenesisState } from '@ethereumjs/util' +import type { DB, DBObject, GenesisState } from '@ethereumjs/util' +import type { EventEmitter } from 'eventemitter3' export type OnBlock = (block: Block, reorg: boolean) => Promise | void -export type BlockchainEvents = { +export type BlockchainEvent = { deletedCanonicalBlocks: (data: Block[], resolve?: (result?: any) => void) => void } @@ -87,7 +88,7 @@ export interface BlockchainInterface { /** * Optional events emitter */ - events?: AsyncEventEmitter + events?: EventEmitter } export interface GenesisOptions { diff --git a/packages/client/bin/cli.ts b/packages/client/bin/cli.ts index 0fc6c1bcab..b4714937c0 100755 --- a/packages/client/bin/cli.ts +++ b/packages/client/bin/cli.ts @@ -807,6 +807,7 @@ async function inputAccounts() { // @ts-ignore Looks like there is a type incompatibility in NodeJS ReadStream vs what this package expects // TODO: See whether package needs to be updated or not input: process.stdin, + // @ts-ignore output: process.stdout, }) @@ -1179,7 +1180,7 @@ async function run() { ignoreStatelessInvalidExecs: args.ignoreStatelessInvalidExecs, prometheusMetrics, }) - config.events.setMaxListeners(50) + config.events.on(Event.SERVER_LISTENING, (details) => { const networkDir = config.getNetworkDirectory() // Write the transport into a file diff --git a/packages/client/package.json b/packages/client/package.json index 019cf8f0a4..80f368529c 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -62,14 +62,15 @@ "@ethereumjs/ethash": "3.0.4", "@ethereumjs/evm": "3.1.0", "@ethereumjs/genesis": "0.2.3", + "@ethereumjs/mpt": "6.2.2", "@ethereumjs/rlp": "5.0.2", "@ethereumjs/statemanager": "2.4.0", - "@ethereumjs/mpt": "6.2.2", "@ethereumjs/tx": "5.4.0", "@ethereumjs/util": "9.1.0", "@ethereumjs/vm": "8.1.0", "@js-sdsl/ordered-map": "^4.4.2", "@multiformats/multiaddr": "^12.2.1", + "@paulmillr/trusted-setups": "^0.1.2", "@polkadot/wasm-crypto": "^7.3.2", "@scure/base": "^1.1.7", "abstract-level": "^1.0.3", @@ -79,12 +80,12 @@ "cors": "^2.8.5", "debug": "^4.3.3", "ethereum-cryptography": "^3.0.0", + "eventemitter3": "^5.0.1", "jayson": "^4.0.0", - "@paulmillr/trusted-setups": "^0.1.2", - "micro-eth-signer": "^0.11.0", "level": "^8.0.0", "mcl-wasm": "^1.5.0", "memory-level": "^1.0.0", + "micro-eth-signer": "^0.11.0", "prom-client": "^15.1.0", "rustbn-wasm": "^0.4.0", "verkle-cryptography-wasm": "^0.4.8", diff --git a/packages/client/src/config.ts b/packages/client/src/config.ts index e4291b9aac..476934ac69 100644 --- a/packages/client/src/config.ts +++ b/packages/client/src/config.ts @@ -1,15 +1,16 @@ import { Common, Hardfork, Mainnet } from '@ethereumjs/common' import { genPrivateKey } from '@ethereumjs/devp2p' import { type Address, BIGINT_0, BIGINT_1, BIGINT_2, BIGINT_256 } from '@ethereumjs/util' +import { EventEmitter } from 'eventemitter3' import { Level } from 'level' import { getLogger } from './logging.js' import { RlpxServer } from './net/server/index.js' -import { Event, EventBus } from './types.js' +import { Event } from './types.js' import { isBrowser, short } from './util/index.js' import type { Logger } from './logging.js' -import type { EventBusType, MultiaddrLike, PrometheusMetrics } from './types.js' +import type { EventParams, MultiaddrLike, PrometheusMetrics } from './types.js' import type { BlockHeader } from '@ethereumjs/block' import type { VM, VMProfilerOpts } from '@ethereumjs/vm' import type { Multiaddr } from '@multiformats/multiaddr' @@ -355,7 +356,7 @@ export class Config { * Central event bus for events emitted by the different * components of the client */ - public readonly events: EventBusType + public readonly events: EventEmitter public static readonly CHAIN_DEFAULT = Mainnet public static readonly SYNCMODE_DEFAULT = SyncMode.Full @@ -478,7 +479,7 @@ export class Config { public readonly metrics: PrometheusMetrics | undefined constructor(options: ConfigOptions = {}) { - this.events = new EventBus() as EventBusType + this.events = new EventEmitter() this.syncmode = options.syncmode ?? Config.SYNCMODE_DEFAULT this.vm = options.vm diff --git a/packages/client/src/net/peer/peer.ts b/packages/client/src/net/peer/peer.ts index ec1c7fa003..9f1bf7e425 100644 --- a/packages/client/src/net/peer/peer.ts +++ b/packages/client/src/net/peer/peer.ts @@ -1,5 +1,5 @@ import { BIGINT_0, short } from '@ethereumjs/util' -import { EventEmitter } from 'events' +import { EventEmitter } from 'eventemitter3' import { BoundEthProtocol, BoundLesProtocol, BoundSnapProtocol } from '../protocol/index.js' diff --git a/packages/client/src/net/protocol/sender.ts b/packages/client/src/net/protocol/sender.ts index 6a172f31e0..571c8a6543 100644 --- a/packages/client/src/net/protocol/sender.ts +++ b/packages/client/src/net/protocol/sender.ts @@ -1,4 +1,4 @@ -import { EventEmitter } from 'events' +import { EventEmitter } from 'eventemitter3' /** * Base class for transport specific message sender/receiver. Subclasses should diff --git a/packages/client/src/sync/fullsync.ts b/packages/client/src/sync/fullsync.ts index e08ef25e5c..27473cb432 100644 --- a/packages/client/src/sync/fullsync.ts +++ b/packages/client/src/sync/fullsync.ts @@ -70,8 +70,8 @@ export class FullSynchronizer extends Synchronizer { const syncEvent: Promise = new Promise((resolve) => { // This event listener listens for other instances of the fetcher that might be syncing from a different peer // and reach the head of the chain before the current fetcher. - this.config.events.once(Event.SYNC_SYNCHRONIZED, (height?: number) => { - this.resolveSync(height) + this.config.events.once(Event.SYNC_SYNCHRONIZED, (chainHeight?: bigint) => { + this.resolveSync(chainHeight) resolve(true) }) }) diff --git a/packages/client/src/sync/sync.ts b/packages/client/src/sync/sync.ts index 53ce2d018c..197a56919c 100644 --- a/packages/client/src/sync/sync.ts +++ b/packages/client/src/sync/sync.ts @@ -145,9 +145,9 @@ export abstract class Synchronizer { abstract syncWithPeer(peer?: Peer): Promise - resolveSync(height?: number) { + resolveSync(height?: bigint) { this.clearFetcher() - const heightStr = typeof height === 'number' && height !== 0 ? ` height=${height}` : '' + const heightStr = typeof height === 'bigint' && height !== BIGINT_0 ? ` height=${height}` : '' this.config.logger.debug(`Finishing up sync with the current fetcher ${heightStr}`) return true } diff --git a/packages/client/src/types.ts b/packages/client/src/types.ts index aa5faab06c..2ddc63f491 100644 --- a/packages/client/src/types.ts +++ b/packages/client/src/types.ts @@ -1,5 +1,3 @@ -import { EventEmitter } from 'events' - import type { SyncMode } from './index.js' import type { Peer } from './net/peer/index.js' import type { Server } from './net/server/index.js' @@ -56,33 +54,6 @@ export interface EventParams { [Event.PROTOCOL_MESSAGE]: [messageDetails: any, protocolName: string, sendingPeer: Peer] } -export declare interface EventBus { - emit(event: T, ...args: EventParams[T]): boolean - on(event: T, listener: (...args: EventParams[T]) => void): this -} - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -export class EventBus extends EventEmitter {} -export type EventBusType = EventBus & - EventBus & - EventBus & - EventBus & - EventBus & - EventBus & - EventBus & - EventBus & - EventBus & - EventBus & - EventBus & - EventBus & - EventBus & - EventBus & - EventBus & - EventBus & - EventBus & - EventBus & - EventBus - /** * Like types */ diff --git a/packages/client/test/integration/mocks/mockpeer.ts b/packages/client/test/integration/mocks/mockpeer.ts index fbaf0f5d04..fa08bb0048 100644 --- a/packages/client/test/integration/mocks/mockpeer.ts +++ b/packages/client/test/integration/mocks/mockpeer.ts @@ -1,4 +1,4 @@ -import { EventEmitter } from 'events' +import { EventEmitter } from 'eventemitter3' import { pipe } from 'it-pipe' import pushable from 'it-pushable' diff --git a/packages/client/test/integration/mocks/mocksender.ts b/packages/client/test/integration/mocks/mocksender.ts index 0190c1cf8e..34018de96e 100644 --- a/packages/client/test/integration/mocks/mocksender.ts +++ b/packages/client/test/integration/mocks/mocksender.ts @@ -1,7 +1,7 @@ import { Sender } from '../../../src/net/protocol/index.js' import type { Pushable } from './mockpeer.js' -import type EventEmitter from 'events' +import type { EventEmitter } from 'eventemitter3' export class MockSender extends Sender { public protocol: string diff --git a/packages/client/test/integration/mocks/network.ts b/packages/client/test/integration/mocks/network.ts index 1bc30a5583..a2aab0f646 100644 --- a/packages/client/test/integration/mocks/network.ts +++ b/packages/client/test/integration/mocks/network.ts @@ -1,4 +1,4 @@ -import { EventEmitter } from 'events' +import { EventEmitter } from 'eventemitter3' //@ts-ignore import DuplexPair from 'it-pair/duplex' diff --git a/packages/client/test/miner/miner.spec.ts b/packages/client/test/miner/miner.spec.ts index d4fd649b16..65acf90ed3 100644 --- a/packages/client/test/miner/miner.spec.ts +++ b/packages/client/test/miner/miner.spec.ts @@ -140,7 +140,7 @@ const customCommon = createCommonFromGethGenesis(chainData, { chain: 'devnet', hardfork: Hardfork.Berlin, }) -customCommon.events.setMaxListeners(50) + const customConfig = new Config({ accountCache: 10000, storageCache: 1000, @@ -148,10 +148,9 @@ const customConfig = new Config({ mine: true, common: customCommon, }) -customConfig.events.setMaxListeners(50) const goerliCommon = new Common({ chain: Goerli, hardfork: Hardfork.Berlin }) -goerliCommon.events.setMaxListeners(50) + const goerliConfig = new Config({ accountCache: 10000, storageCache: 1000, @@ -159,7 +158,6 @@ const goerliConfig = new Config({ mine: true, common: customCommon, }) -customConfig.events.setMaxListeners(50) const createTx = ( from = A, diff --git a/packages/client/test/net/peer/rlpxpeer.spec.ts b/packages/client/test/net/peer/rlpxpeer.spec.ts index 65525ffe18..806e2dcefc 100644 --- a/packages/client/test/net/peer/rlpxpeer.spec.ts +++ b/packages/client/test/net/peer/rlpxpeer.spec.ts @@ -1,4 +1,4 @@ -import { EventEmitter } from 'events' +import { EventEmitter } from 'eventemitter3' import { assert, describe, expect, it, vi } from 'vitest' import { Config } from '../../../src/config.js' diff --git a/packages/client/test/net/peerpool.spec.ts b/packages/client/test/net/peerpool.spec.ts index a066c3531d..0b2a0b766d 100644 --- a/packages/client/test/net/peerpool.spec.ts +++ b/packages/client/test/net/peerpool.spec.ts @@ -1,4 +1,4 @@ -import { EventEmitter } from 'events' +import { EventEmitter } from 'eventemitter3' import { assert, describe, it, vi } from 'vitest' import { Config } from '../../src/config.js' diff --git a/packages/client/test/net/protocol/boundprotocol.spec.ts b/packages/client/test/net/protocol/boundprotocol.spec.ts index ea82ec3d66..c7af0d1632 100644 --- a/packages/client/test/net/protocol/boundprotocol.spec.ts +++ b/packages/client/test/net/protocol/boundprotocol.spec.ts @@ -1,5 +1,5 @@ /// -import { EventEmitter } from 'events' +import { EventEmitter } from 'eventemitter3' import * as td from 'testdouble' import { assert, describe, it } from 'vitest' diff --git a/packages/client/test/net/protocol/rlpxsender.spec.ts b/packages/client/test/net/protocol/rlpxsender.spec.ts index 25c2e9f2f3..fc742d59fe 100644 --- a/packages/client/test/net/protocol/rlpxsender.spec.ts +++ b/packages/client/test/net/protocol/rlpxsender.spec.ts @@ -1,4 +1,4 @@ -import { EventEmitter } from 'events' +import { EventEmitter } from 'eventemitter3' import * as td from 'testdouble' import { assert, describe, it } from 'vitest' @@ -31,7 +31,7 @@ describe('should send message', async () => { describe('should receive status', async () => { const rlpxProtocol = { events: new EventEmitter() } - const sender = new RlpxSender(rlpxProtocol as Devp2pETH) + const sender = new RlpxSender(rlpxProtocol as never as Devp2pETH) sender.on('status', (status: any) => { it('status received', () => { assert.equal(status.id, 5, 'status received') @@ -52,14 +52,3 @@ describe('should receive message', async () => { }) rlpxProtocol.events.emit('message', 1, 5) }) - -describe('should catch errors', async () => { - const rlpxProtocol = { events: new EventEmitter() } - const sender = new RlpxSender(rlpxProtocol as Devp2pETH) - it('throws sendStatus error', () => { - assert.throws(() => sender.sendStatus({ id: 5 }), /not a function/, 'sendStatus error') - }) - it('throws sendMessage error', () => { - assert.throws(() => sender.sendMessage(1, 5), /not a function/, 'sendMessage error') - }) -}) diff --git a/packages/client/test/net/server/rlpxserver.spec.ts b/packages/client/test/net/server/rlpxserver.spec.ts index 73985f131b..3e4ab72d8f 100644 --- a/packages/client/test/net/server/rlpxserver.spec.ts +++ b/packages/client/test/net/server/rlpxserver.spec.ts @@ -1,6 +1,6 @@ import { equalsBytes, hexToBytes, utf8ToBytes } from '@ethereumjs/util' import { multiaddr } from '@multiformats/multiaddr' -import { EventEmitter } from 'events' +import { EventEmitter } from 'eventemitter3' import { assert, describe, expect, it, vi } from 'vitest' import { Config } from '../../../src/config.js' diff --git a/packages/client/test/sim/simutils.ts b/packages/client/test/sim/simutils.ts index ff0a2247b4..6932ccf0c0 100644 --- a/packages/client/test/sim/simutils.ts +++ b/packages/client/test/sim/simutils.ts @@ -434,7 +434,6 @@ export async function createInlineClient( customGenesisState: any, datadir: any = Config.DATADIR_DEFAULT, ) { - config.events.setMaxListeners(50) const chainDB = new Level( `${datadir}/${common.chainName()}/chainDB`, ) diff --git a/packages/common/package.json b/packages/common/package.json index c2a1a9ccf1..a1ce145f89 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -57,7 +57,8 @@ "tsc": "../../config/cli/ts-compile.sh" }, "dependencies": { - "@ethereumjs/util": "^9.1.0" + "@ethereumjs/util": "^9.1.0", + "eventemitter3": "^5.0.1" }, "devDependencies": { "@polkadot/util": "^12.6.2", diff --git a/packages/common/src/common.ts b/packages/common/src/common.ts index 7f04065540..6056e83ffb 100644 --- a/packages/common/src/common.ts +++ b/packages/common/src/common.ts @@ -7,7 +7,7 @@ import { intToBytes, toType, } from '@ethereumjs/util' -import { EventEmitter } from 'events' +import { EventEmitter } from 'eventemitter3' import { crc32 } from './crc.js' import { eipsDict } from './eips.js' diff --git a/packages/devp2p/package.json b/packages/devp2p/package.json index a1cf25722b..6630684dc5 100644 --- a/packages/devp2p/package.json +++ b/packages/devp2p/package.json @@ -65,6 +65,7 @@ "@scure/base": "^1.1.7", "debug": "^4.3.3", "ethereum-cryptography": "^3.0.0", + "eventemitter3": "^5.0.1", "lru-cache": "10.1.0", "scanf": "1.1.2", "snappyjs": "^0.6.1" @@ -74,6 +75,7 @@ "@ethereumjs/tx": "^5.4.0", "@types/debug": "^4.1.9", "@types/k-bucket": "^5.0.0", + "@types/node": "^22.7.6", "chalk": "^4.1.2", "testdouble": "^3.8.2" }, diff --git a/packages/devp2p/src/dpt/dpt.ts b/packages/devp2p/src/dpt/dpt.ts index 8a6b2341c7..cf87064841 100644 --- a/packages/devp2p/src/dpt/dpt.ts +++ b/packages/devp2p/src/dpt/dpt.ts @@ -1,7 +1,7 @@ import { bytesToInt, bytesToUnprefixedHex, randomBytes } from '@ethereumjs/util' import { keccak256 } from 'ethereum-cryptography/keccak.js' import { secp256k1 } from 'ethereum-cryptography/secp256k1.js' -import { EventEmitter } from 'events' +import { EventEmitter } from 'eventemitter3' import { DNS } from '../dns/index.js' import { devp2pDebug, pk2id } from '../util.js' diff --git a/packages/devp2p/src/dpt/kbucket.ts b/packages/devp2p/src/dpt/kbucket.ts index 1b79d15009..f5f968e620 100644 --- a/packages/devp2p/src/dpt/kbucket.ts +++ b/packages/devp2p/src/dpt/kbucket.ts @@ -1,5 +1,5 @@ import { bytesToUnprefixedHex } from '@ethereumjs/util' -import { EventEmitter } from 'events' +import { EventEmitter } from 'eventemitter3' import { KBucket as _KBucket } from '../ext/index.js' diff --git a/packages/devp2p/src/dpt/server.ts b/packages/devp2p/src/dpt/server.ts index 3fea7d07a7..9c606f2759 100644 --- a/packages/devp2p/src/dpt/server.ts +++ b/packages/devp2p/src/dpt/server.ts @@ -1,7 +1,7 @@ import { bytesToHex, bytesToUnprefixedHex } from '@ethereumjs/util' import debugDefault from 'debug' import * as dgram from 'dgram' -import { EventEmitter } from 'events' +import { EventEmitter } from 'eventemitter3' import { LRUCache } from 'lru-cache' import { createDeferred, devp2pDebug, formatLogId, pk2id } from '../util.js' diff --git a/packages/devp2p/src/ext/kbucket.ts b/packages/devp2p/src/ext/kbucket.ts index de33f2824f..9a1b09bfd9 100644 --- a/packages/devp2p/src/ext/kbucket.ts +++ b/packages/devp2p/src/ext/kbucket.ts @@ -33,7 +33,7 @@ OTHER DEALINGS IN THE SOFTWARE. // (please nevertheless include the original license reference)) import { equalsBytes, randomBytes } from '@ethereumjs/util' -import { EventEmitter } from 'events' +import { EventEmitter } from 'eventemitter3' import type { Contact, KBucketOptions, PeerInfo } from '../types.js' diff --git a/packages/devp2p/src/protocol/protocol.ts b/packages/devp2p/src/protocol/protocol.ts index 6d54e2be4f..04f0abba74 100644 --- a/packages/devp2p/src/protocol/protocol.ts +++ b/packages/devp2p/src/protocol/protocol.ts @@ -1,5 +1,5 @@ import debugDefault from 'debug' -import { EventEmitter } from 'events' +import { EventEmitter } from 'eventemitter3' import { DISCONNECT_REASON, ProtocolType } from '../types.js' import { devp2pDebug } from '../util.js' diff --git a/packages/devp2p/src/rlpx/peer.ts b/packages/devp2p/src/rlpx/peer.ts index 276cba03d3..73335724b0 100644 --- a/packages/devp2p/src/rlpx/peer.ts +++ b/packages/devp2p/src/rlpx/peer.ts @@ -10,7 +10,7 @@ import { utf8ToBytes, } from '@ethereumjs/util' import debugDefault from 'debug' -import { EventEmitter } from 'events' +import { EventEmitter } from 'eventemitter3' import * as snappy from 'snappyjs' import { DISCONNECT_REASON } from '../types.js' diff --git a/packages/devp2p/src/rlpx/rlpx.ts b/packages/devp2p/src/rlpx/rlpx.ts index 00454c1980..b230414796 100644 --- a/packages/devp2p/src/rlpx/rlpx.ts +++ b/packages/devp2p/src/rlpx/rlpx.ts @@ -8,7 +8,7 @@ import { import debugDefault from 'debug' import { keccak256 } from 'ethereum-cryptography/keccak.js' import { secp256k1 } from 'ethereum-cryptography/secp256k1.js' -import { EventEmitter } from 'events' +import { EventEmitter } from 'eventemitter3' import { LRUCache } from 'lru-cache' import * as net from 'net' import * as os from 'os' diff --git a/packages/devp2p/test/rlpx.spec.ts b/packages/devp2p/test/rlpx.spec.ts index 04262069e6..9f26bda9e0 100644 --- a/packages/devp2p/test/rlpx.spec.ts +++ b/packages/devp2p/test/rlpx.spec.ts @@ -4,7 +4,7 @@ import { Common, Mainnet } from '@ethereumjs/common' import { equalsBytes, randomBytes } from '@ethereumjs/util' import assert from 'assert' import { secp256k1 } from 'ethereum-cryptography/secp256k1.js' -import EventEmitter from 'events' +import { EventEmitter } from 'eventemitter3' import { describe, expect, it, vi } from 'vitest' import { RLPx, pk2id } from '../src/index.js' diff --git a/packages/evm/README.md b/packages/evm/README.md index 76c41a0656..69d0e83f90 100644 --- a/packages/evm/README.md +++ b/packages/evm/README.md @@ -56,14 +56,14 @@ If the EVM should run on a certain state an `@ethereumjs/statemanager` is needed import { createBlockchain } from '@ethereumjs/blockchain' import { Common, Hardfork, Mainnet } from '@ethereumjs/common' import { createEVM } from '@ethereumjs/evm' -import { DefaultStateManager } from '@ethereumjs/statemanager' +import { MerkleStateManager } from '@ethereumjs/statemanager' import { bytesToHex, hexToBytes } from '@ethereumjs/util' import type { PrefixedHexString } from '@ethereumjs/util' const main = async () => { const common = new Common({ chain: Mainnet, hardfork: Hardfork.Shanghai }) - const stateManager = new DefaultStateManager() + const stateManager = new MerkleStateManager() const blockchain = await createBlockchain() const evm = await createEVM({ @@ -79,9 +79,10 @@ const main = async () => { // Note that numbers added are hex values, so '20' would be '32' as decimal e.g. const code = [PUSH1, '03', PUSH1, '05', ADD, STOP] - evm.events.on('step', function (data) { + evm.events.on('step', function (data, resolve) { // Note that data.stack is not immutable, i.e. it is a reference to the vm's internal stack object console.log(`Opcode: ${data.opcode.name}\tStack: ${data.stack}`) + resolve?.() }) const results = await evm.runCode({ @@ -334,7 +335,7 @@ EIP-4844 comes with a new opcode `BLOBHASH` (Attention! Renamed from `DATAHASH`) ### Tracing Events -The EVM has a public property `events` which instantiates an [AsyncEventEmitter](https://github.com/ahultgren/async-eventemitter) and events are submitted along major execution steps which you can listen to. +The EVM emits events that support async listeners. You can subscribe to the following events: @@ -343,35 +344,34 @@ You can subscribe to the following events: - `step`: Emits an `InterpreterStep` right before running an EVM step. - `newContract`: Emits a `NewContractEvent` right before creating a contract. This event contains the deployment code, not the deployed code, as the creation message may not return such a code. -An example for the `step` event can be found in the initial usage example in this `README`. - -#### Asynchronous event handlers +#### Event listeners You can perform asynchronous operations from within an event handler and prevent the EVM to keep running until they finish. -In order to do that, your event handler has to accept two arguments. -The first one will be the event object, and the second one a function. -The EVM won't continue until you call this function. +If subscribing to events with an async listener, specify the second +parameter of your listener as a `resolve` function that must be called once your listener code has finished. + +See below for example usage: + +```ts +// ./examples/eventListener.ts#L7-L14 + +evm.events.on('beforeMessage', (event) => { + console.log('synchronous listener to beforeMessage', event) +}) +evm.events.on('afterMessage', (event, resolve) => { + console.log('asynchronous listener to beforeMessage', event) + // we need to call resolve() to avoid the event listener hanging + resolve?.() +}) +``` If an exception is passed to that function, or thrown from within the handler or a function called by it, the exception will bubble into the EVM and interrupt it, possibly corrupting its state. It's strongly recommended not to do that. -#### Synchronous event handlers - -If you want to perform synchronous operations, you don't need -to receive a function as the handler's second argument, nor call it. - -Note that if your event handler receives multiple arguments, the second -one will be the continuation function, and it must be called. - -If an exception is thrown from within the handler or a function called -by it, the exception will bubble into the EVM and interrupt it, possibly -corrupting its state. It's strongly recommended not to throw from within -event handlers. - ## Understanding the EVM If you want to understand your EVM runs we have added a hierarchically structured list of debug loggers for your convenience which can be activated in arbitrary combinations. We also use these loggers internally for development and testing. These loggers use the [debug](https://github.com/visionmedia/debug) library and can be activated on the CL with `DEBUG=ethjs,[Logger Selection] node [Your Script to Run].js` and produce output like the following: diff --git a/packages/evm/examples/eventListener.ts b/packages/evm/examples/eventListener.ts new file mode 100644 index 0000000000..1f7893d6fa --- /dev/null +++ b/packages/evm/examples/eventListener.ts @@ -0,0 +1,23 @@ +import { createEVM } from '@ethereumjs/evm' +import { createAddressFromString, hexToBytes } from '@ethereumjs/util' + +const main = async () => { + const evm = await createEVM() + + evm.events.on('beforeMessage', (event) => { + console.log('synchronous listener to beforeMessage', event) + }) + evm.events.on('afterMessage', (event, resolve) => { + console.log('asynchronous listener to beforeMessage', event) + // we need to call resolve() to avoid the event listener hanging + resolve?.() + }) + const res = await evm.runCall({ + to: createAddressFromString('0x0000000000000000000000000000000000000000'), + value: 0n, + data: hexToBytes('0x6001'), // PUSH1 01 -- simple bytecode to push 1 onto the stack + }) + console.log(res.execResult.executionGasUsed) // 0n +} + +void main() diff --git a/packages/evm/examples/runCode.ts b/packages/evm/examples/runCode.ts index aeca0d4d34..7cf3776991 100644 --- a/packages/evm/examples/runCode.ts +++ b/packages/evm/examples/runCode.ts @@ -21,9 +21,10 @@ const main = async () => { // Note that numbers added are hex values, so '20' would be '32' as decimal e.g. const code = [PUSH1, '03', PUSH1, '05', ADD, STOP] - evm.events.on('step', function (data) { + evm.events.on('step', function (data, resolve) { // Note that data.stack is not immutable, i.e. it is a reference to the vm's internal stack object console.log(`Opcode: ${data.opcode.name}\tStack: ${data.stack}`) + resolve?.() }) evm diff --git a/packages/evm/examples/withBlockchain.ts b/packages/evm/examples/withBlockchain.ts index 89c0c45c6c..771414f726 100644 --- a/packages/evm/examples/withBlockchain.ts +++ b/packages/evm/examples/withBlockchain.ts @@ -24,9 +24,10 @@ const main = async () => { // Note that numbers added are hex values, so '20' would be '32' as decimal e.g. const code = [PUSH1, '03', PUSH1, '05', ADD, STOP] - evm.events.on('step', function (data) { + evm.events.on('step', function (data, resolve) { // Note that data.stack is not immutable, i.e. it is a reference to the vm's internal stack object console.log(`Opcode: ${data.opcode.name}\tStack: ${data.stack}`) + resolve?.() }) const results = await evm.runCode({ diff --git a/packages/evm/package.json b/packages/evm/package.json index d4c073cc67..1ded6cb137 100644 --- a/packages/evm/package.json +++ b/packages/evm/package.json @@ -61,9 +61,11 @@ "@ethereumjs/util": "^9.1.0", "@types/debug": "^4.1.9", "debug": "^4.3.3", - "ethereum-cryptography": "^3.0.0" + "ethereum-cryptography": "^3.0.0", + "eventemitter3": "^5.0.1" }, "devDependencies": { + "@paulmillr/trusted-setups": "^0.1.2", "@types/benchmark": "^1.0.33", "@types/core-js": "^2.5.0", "@types/minimist": "^1.2.2", @@ -73,14 +75,13 @@ "level": "^8.0.0", "mcl-wasm": "^1.5.0", "memory-level": "^1.0.0", + "micro-eth-signer": "^0.11.0", "minimist": "^1.2.5", "node-dir": "^0.1.17", "rollup-plugin-visualizer": "^5.12.0", "rustbn-wasm": "^0.4.0", "solc": "^0.8.1", - "split": "^1.0.1", - "@paulmillr/trusted-setups": "^0.1.2", - "micro-eth-signer": "^0.11.0" + "split": "^1.0.1" }, "engines": { "node": ">=18" diff --git a/packages/evm/src/evm.ts b/packages/evm/src/evm.ts index 869b479db7..f5b860c137 100644 --- a/packages/evm/src/evm.ts +++ b/packages/evm/src/evm.ts @@ -2,7 +2,6 @@ import { Hardfork } from '@ethereumjs/common' import { Account, Address, - AsyncEventEmitter, BIGINT_0, BIGINT_1, KECCAK256_NULL, @@ -17,6 +16,7 @@ import { short, } from '@ethereumjs/util' import debugDefault from 'debug' +import { EventEmitter } from 'eventemitter3' import { FORMAT } from './eof/constants.js' import { isEOF } from './eof/util.js' @@ -35,7 +35,7 @@ import { DELEGATION_7702_FLAG, type EVMBLSInterface, type EVMBN254Interface, - type EVMEvents, + type EVMEvent, type EVMInterface, type EVMMockBlockchainInterface, type EVMOpts, @@ -93,7 +93,7 @@ export class EVM implements EVMInterface { protected _block?: Block public readonly common: Common - public readonly events: AsyncEventEmitter + public readonly events: EventEmitter public stateManager: StateManagerInterface public blockchain: EVMMockBlockchainInterface @@ -172,7 +172,7 @@ export class EVM implements EVMInterface { } } - this.events = new AsyncEventEmitter() + this.events = new EventEmitter() this._optsCached = opts // Supported EIPs @@ -221,7 +221,16 @@ export class EVM implements EVMInterface { this._bn254 = opts.bn254! this._emit = async (topic: string, data: any): Promise => { - return new Promise((resolve) => this.events.emit(topic as keyof EVMEvents, data, resolve)) + const listeners = this.events.listeners(topic as keyof EVMEvent) + for (const listener of listeners) { + if (listener.length === 2) { + await new Promise((resolve) => { + listener(data, resolve) + }) + } else { + listener(data) + } + } } this.performanceLogger = new EVMPerformanceLogger() diff --git a/packages/evm/src/types.ts b/packages/evm/src/types.ts index 1601ab7770..4663efbfd1 100644 --- a/packages/evm/src/types.ts +++ b/packages/evm/src/types.ts @@ -12,7 +12,8 @@ import type { ParamsDict, StateManagerInterface, } from '@ethereumjs/common' -import type { Account, Address, AsyncEventEmitter, PrefixedHexString } from '@ethereumjs/util' +import type { Account, Address, PrefixedHexString } from '@ethereumjs/util' +import type { EventEmitter } from 'eventemitter3' export type DeleteOpcode = { opcode: number @@ -136,7 +137,7 @@ interface NewContractEvent { code: Uint8Array } -export type EVMEvents = { +export type EVMEvent = { newContract: (data: NewContractEvent, resolve?: (result?: any) => void) => void beforeMessage: (data: Message, resolve?: (result?: any) => void) => void afterMessage: (data: EVMResult, resolve?: (result?: any) => void) => void @@ -164,7 +165,7 @@ export interface EVMInterface { precompiles: Map runCall(opts: EVMRunCallOpts): Promise runCode(opts: EVMRunCodeOpts): Promise - events?: AsyncEventEmitter + events?: EventEmitter } export type EVMProfilerOpts = { diff --git a/packages/evm/test/customOpcodes.spec.ts b/packages/evm/test/customOpcodes.spec.ts index 753743c78d..486b297477 100644 --- a/packages/evm/test/customOpcodes.spec.ts +++ b/packages/evm/test/customOpcodes.spec.ts @@ -3,7 +3,7 @@ import { assert, describe, it } from 'vitest' import { createEVM } from '../src/index.js' -import type { InterpreterStep, RunState } from '../src/interpreter.js' +import type { RunState } from '../src/interpreter.js' import type { AddOpcode } from '../src/types.js' describe('VM: custom opcodes', () => { @@ -28,10 +28,11 @@ describe('VM: custom opcodes', () => { const evm = await createEVM({ customOpcodes: [testOpcode] }) const gas = 123456 let correctOpcodeName = false - evm.events.on('step', (e: InterpreterStep) => { + evm.events.on('step', (e, resolve) => { if (e.pc === 0) { correctOpcodeName = e.opcode.name === testOpcode.opcodeName } + resolve?.() }) const res = await evm.runCode({ code: hexToBytes('0x21'), diff --git a/packages/util/src/asyncEventEmitter.ts b/packages/util/src/asyncEventEmitter.ts deleted file mode 100644 index e5eefc1ffe..0000000000 --- a/packages/util/src/asyncEventEmitter.ts +++ /dev/null @@ -1,212 +0,0 @@ -/** - * Ported to Typescript from original implementation below: - * https://github.com/ahultgren/async-eventemitter -- MIT licensed - * - * Type Definitions based on work by: patarapolw -- MIT licensed - * that was contributed to Definitely Typed below: - * https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/async-eventemitter - */ - -import { EventEmitter } from 'events' -type AsyncListener = - | ((data: T, callback?: (result?: R) => void) => Promise) - | ((data: T, callback?: (result?: R) => void) => void) -export interface EventMap { - [event: string]: AsyncListener -} - -async function runInSeries( - context: any, - tasks: Array<(data: unknown, callback?: (error?: Error) => void) => void>, - data: unknown, -): Promise { - let error: Error | undefined - for await (const task of tasks) { - try { - if (task.length < 2) { - //sync - task.call(context, data) - } else { - await new Promise((resolve, reject) => { - task.call(context, data, (error) => { - if (error) { - reject(error) - } else { - resolve() - } - }) - }) - } - } catch (e: unknown) { - error = e as Error - } - } - if (error) { - throw error - } -} - -export class AsyncEventEmitter extends EventEmitter { - emit(event: E & string, ...args: Parameters) { - let [data, callback] = args - const self = this - - let listeners = (self as any)._events[event] ?? [] - - // Optional data argument - if (callback === undefined && typeof data === 'function') { - callback = data - data = undefined - } - - // Special treatment of internal newListener and removeListener events - if (event === 'newListener' || event === 'removeListener') { - data = { - event: data, - fn: callback, - } - - callback = undefined - } - - // A single listener is just a function not an array... - listeners = Array.isArray(listeners) ? listeners : [listeners] - runInSeries(self, listeners.slice(), data).then(callback).catch(callback) - - return self.listenerCount(event) > 0 - } - - once(event: E & string, listener: T[E]): this { - const self = this - let g: (...args: any[]) => void - - if (typeof listener !== 'function') { - throw new TypeError('listener must be a function') - } - - // Hack to support set arity - if (listener.length >= 2) { - g = function (e: E, next: any) { - self.removeListener(event, g as T[E]) - void listener(e, next) - } - } else { - g = function (e: E) { - self.removeListener(event, g as T[E]) - void listener(e, g) - } - } - - self.on(event, g as T[E]) - - return self - } - - first(event: E & string, listener: T[E]): this { - let listeners = (this as any)._events[event] ?? [] - - // Contract - if (typeof listener !== 'function') { - throw new TypeError('listener must be a function') - } - - // Listeners are not always an array - if (!Array.isArray(listeners)) { - ;(this as any)._events[event] = listeners = [listeners] - } - - listeners.unshift(listener) - - return this - } - - before(event: E & string, target: T[E], listener: T[E]): this { - return this.beforeOrAfter(event, target, listener) - } - - after(event: E & string, target: T[E], listener: T[E]): this { - return this.beforeOrAfter(event, target, listener, 'after') - } - - private beforeOrAfter( - event: E & string, - target: T[E], - listener: T[E], - beforeOrAfter?: string, - ) { - let listeners = (this as any)._events[event] ?? [] - let i - let index - const add = beforeOrAfter === 'after' ? 1 : 0 - - // Contract - if (typeof listener !== 'function') { - throw new TypeError('listener must be a function') - } - if (typeof target !== 'function') { - throw new TypeError('target must be a function') - } - - // Listeners are not always an array - if (!Array.isArray(listeners)) { - ;(this as any)._events[event] = listeners = [listeners] - } - - index = listeners.length - - for (i = listeners.length; i--; ) { - if (listeners[i] === target) { - index = i + add - break - } - } - - listeners.splice(index, 0, listener) - - return this - } - - on(event: E & string, listener: T[E]): this { - return super.on(event, listener) - } - - addListener(event: E & string, listener: T[E]): this { - return super.addListener(event, listener) - } - - prependListener(event: E & string, listener: T[E]): this { - return super.prependListener(event, listener) - } - - prependOnceListener(event: E & string, listener: T[E]): this { - return super.prependOnceListener(event, listener) - } - - removeAllListeners(event?: keyof T & string): this { - return super.removeAllListeners(event) - } - - removeListener(event: E & string, listener: T[E]): this { - return super.removeListener(event, listener) - } - - eventNames(): Array { - return super.eventNames() as keyof T & string[] - } - - listeners(event: E & string): Array { - return super.listeners(event) as T[E][] - } - - listenerCount(event: keyof T & string): number { - return super.listenerCount(event) - } - - getMaxListeners(): number { - return super.getMaxListeners() - } - - setMaxListeners(maxListeners: number): this { - return super.setMaxListeners(maxListeners) - } -} diff --git a/packages/util/src/index.ts b/packages/util/src/index.ts index d1f6dd95e7..5031689036 100644 --- a/packages/util/src/index.ts +++ b/packages/util/src/index.ts @@ -46,7 +46,6 @@ export * from './types.js' /** * Export ethjs-util methods */ -export * from './asyncEventEmitter.js' export * from './blobs.js' export * from './genesis.js' export { diff --git a/packages/util/test/asyncEventEmitter.spec.ts b/packages/util/test/asyncEventEmitter.spec.ts deleted file mode 100644 index fa14b12639..0000000000 --- a/packages/util/test/asyncEventEmitter.spec.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { EventEmitter } from 'events' -import { assert, describe, it } from 'vitest' - -import { AsyncEventEmitter } from '../src/index.js' - -import type { EventMap } from '../src/index.js' - -describe('async event emit/on test', async () => { - it('should receive event', () => { - const emitter = new AsyncEventEmitter() - emitter.on('event', async (data, next) => { - const startTime = Date.now() - assert.equal(data, 'eventData', 'Received data from an event') - setTimeout(() => { - assert.ok(Date.now() > startTime, 'some time passed before event resolved') - next?.() - }, 1000) - }) - emitter.emit('event', 'eventData') - }) -}) - -describe('async event emit/once test', async () => { - it('should receive event', () => { - const emitter = new AsyncEventEmitter() - emitter.once('event', async (data, next) => { - setTimeout(next!, 1000) - }) - assert.equal(emitter.listenerCount('event'), 1, 'emitter has one event listener') - emitter.emit('event', 'eventData', () => { - assert.equal(emitter.listenerCount('event'), 0, 'listener removed after one event emitted') - }) - }) -}) - -describe('AsyncEventEmitter', () => { - it('should add listener using addListener()', () => { - const emitter = new AsyncEventEmitter() - const listener = () => {} - const event = 'event1' - - emitter.addListener(event, listener) - - assert.deepStrictEqual(emitter.listeners(event), [listener]) - }) - - it('should prepend listener using prependListener()', () => { - const emitter = new AsyncEventEmitter() - const listener1 = () => {} - const listener2 = () => {} - const event = 'event1' - - emitter.prependListener(event, listener1) - emitter.prependListener(event, listener2) - - assert.deepStrictEqual(emitter.listeners(event), [listener2, listener1]) - }) - - it('should prepend once listener using prependOnceListener()', () => { - const emitter = new AsyncEventEmitter() - const listener1 = () => {} - const listener2 = () => {} - const event = 'event1' - - emitter.prependOnceListener(event, listener1) - emitter.prependOnceListener(event, listener2) - - assert.deepStrictEqual(emitter.listeners(event), [listener2, listener1]) - }) - - it('should return event names when using eventNames()', () => { - const emitter = new AsyncEventEmitter() - const event1 = 'event1' - const event2 = 'event2' - - emitter.addListener(event1, () => {}) - emitter.addListener(event2, () => {}) - - assert.deepStrictEqual(emitter.eventNames(), [event1, event2]) - }) - - it('should return listeners for an event when using listeners()', () => { - const emitter = new AsyncEventEmitter() - const listener1 = () => {} - const listener2 = () => {} - const event = 'event1' - - emitter.addListener(event, listener1) - emitter.addListener(event, listener2) - - assert.deepStrictEqual(emitter.listeners(event), [listener1, listener2]) - }) - - it('should return the maximum number of listeners when using getMaxListeners()', () => { - const emitter = new AsyncEventEmitter() - - const maxListeners = emitter.getMaxListeners() - - assert.strictEqual(maxListeners, EventEmitter.defaultMaxListeners) - }) - - it('should set the maximum number of listeners when using setMaxListeners()', () => { - const emitter = new AsyncEventEmitter() - - const newMaxListeners = 10 - emitter.setMaxListeners(newMaxListeners) - - const maxListeners = emitter.getMaxListeners() - - assert.strictEqual(maxListeners, newMaxListeners) - }) -}) diff --git a/packages/util/test/bench/events.bench.ts b/packages/util/test/bench/events.bench.ts new file mode 100644 index 0000000000..26f1062bc1 --- /dev/null +++ b/packages/util/test/bench/events.bench.ts @@ -0,0 +1,26 @@ +import { EventEmitter as ee3 } from 'eventemitter3' +import EventEmitter from 'events' +import { bench, describe } from 'vitest' + +describe('event benchmarks', () => { + const nodeJSevents = new EventEmitter() + const event3 = new ee3() + bench('NodeJS EventEmitter', async () => { + await new Promise((resolve) => { + nodeJSevents.on('msg', () => { + nodeJSevents.removeAllListeners('msg') + resolve(undefined) + }) + nodeJSevents.emit('msg') + }) + }) + bench('EventEmitter3', async () => { + await new Promise((resolve) => { + event3.on('msg', () => { + event3.removeAllListeners('msg') + resolve(undefined) + }) + event3.emit('msg') + }) + }) +}) diff --git a/packages/vm/README.md b/packages/vm/README.md index 29d4cc7f16..4045f213fc 100644 --- a/packages/vm/README.md +++ b/packages/vm/README.md @@ -38,11 +38,11 @@ npm install @ethereumjs/vm import { Common, Hardfork, Mainnet } from '@ethereumjs/common' import { createLegacyTx } from '@ethereumjs/tx' import { createZeroAddress } from '@ethereumjs/util' -import { VM, runTx } from '@ethereumjs/vm' +import { createVM, runTx } from '@ethereumjs/vm' const main = async () => { const common = new Common({ chain: Mainnet, hardfork: Hardfork.Shanghai }) - const vm = await VM.create({ common }) + const vm = await createVM({ common }) const tx = createLegacyTx({ gasLimit: BigInt(21000), @@ -77,11 +77,11 @@ import { createBlock } from '@ethereumjs/block' import { Common, Mainnet } from '@ethereumjs/common' import { createLegacyTx } from '@ethereumjs/tx' import { Account, bytesToHex, createAddressFromPrivateKey, hexToBytes } from '@ethereumjs/util' -import { VM, buildBlock } from '@ethereumjs/vm' +import { buildBlock, createVM } from '@ethereumjs/vm' const main = async () => { const common = new Common({ chain: Mainnet }) - const vm = await VM.create({ common }) + const vm = await createVM({ common }) const parentBlock = createBlock( { header: { number: 1n } }, @@ -216,14 +216,13 @@ import { createBlockFromRPC } from '@ethereumjs/block' import { Common, Goerli } from '@ethereumjs/common' import { bytesToHex } from '@ethereumjs/util' -import { runBlock } from '../src/index.js' -import { VM } from '../src/vm.js' +import { createVM, runBlock } from '../src/index.js' import goerliBlock2 from './testData/goerliBlock2.json' const main = async () => { const common = new Common({ chain: Goerli, hardfork: 'london' }) - const vm = await VM.create({ common, setHardfork: true }) + const vm = await createVM({ common, setHardfork: true }) const block = createBlockFromRPC(goerliBlock2, undefined, { common }) const result = await runBlock(vm, { block, generate: true, skipHeaderValidation: true }) // we skip header validation since we are running a block without the full Ethereum history available @@ -245,11 +244,11 @@ An explicit HF in the `VM` - which is then passed on to the inner `EVM` - can be import { Common, Hardfork, Mainnet } from '@ethereumjs/common' import { createLegacyTx } from '@ethereumjs/tx' import { createZeroAddress } from '@ethereumjs/util' -import { VM, runTx } from '@ethereumjs/vm' +import { createVM, runTx } from '@ethereumjs/vm' const main = async () => { const common = new Common({ chain: Mainnet, hardfork: Hardfork.Shanghai }) - const vm = await VM.create({ common }) + const vm = await createVM({ common }) ``` ### Custom genesis state support @@ -268,12 +267,12 @@ For initializing a custom genesis state you can use the `genesisState` construct import { Chain } from '@ethereumjs/common' import { getGenesis } from '@ethereumjs/genesis' import { createAddressFromString } from '@ethereumjs/util' -import { VM } from '@ethereumjs/vm' +import { createVM } from '@ethereumjs/vm' const main = async () => { const genesisState = getGenesis(Chain.Mainnet) - const vm = await VM.create() + const vm = await createVM() await vm.stateManager.generateCanonicalGenesis!(genesisState) const account = await vm.stateManager.getAccount( createAddressFromString('0x000d836201318ec6899a67540690382780743280'), @@ -309,11 +308,11 @@ with the respective EIPs, e.g.: // ./examples/vmWithEIPs.ts import { Common, Mainnet } from '@ethereumjs/common' -import { VM } from '@ethereumjs/vm' +import { createVM } from '@ethereumjs/vm' const main = async () => { const common = new Common({ chain: Mainnet, eips: [7702] }) - const vm = await VM.create({ common }) + const vm = await createVM({ common }) console.log(`EIP 7702 is active in the VM - ${vm.common.isActivatedEIP(7702)}`) } void main() @@ -354,11 +353,11 @@ To run VM/EVM related EIP-4844 functionality you have to activate the EIP in the import { Common, Hardfork, Mainnet } from '@ethereumjs/common' -import { VM } from '../src/vm.js' +import { createVM } from '../src/index.js' const main = async () => { const common = new Common({ chain: Mainnet, hardfork: Hardfork.Shanghai, eips: [4844] }) - const vm = await VM.create({ common }) + const vm = await createVM({ common }) console.log(`4844 is active in the VM - ${vm.common.isActivatedEIP(4844)}`) } @@ -371,7 +370,7 @@ EIP-4844 comes with a new opcode `BLOBHASH` and adds a new point evaluation prec ### Tracing Events -Our `TypeScript` VM is implemented as an [AsyncEventEmitter](https://github.com/ahultgren/async-eventemitter) and events are submitted along major execution steps which you can listen to. +Our `TypeScript` VM emits events that support async listeners. You can subscribe to the following events: @@ -380,6 +379,23 @@ You can subscribe to the following events: - `beforeTx`: Emits a `Transaction` right before running it. - `afterTx`: Emits a `AfterTxEvent` right after running a transaction. +Note, if subscribing to events with an async listener, specify the second parameter of your listener as a `resolve` function that must be called once your listener code has finished. + +```ts +// ./examples/eventListener.ts#L10-L19 + +// Setup an event listener on the `afterTx` event +vm.events.on('afterTx', (event, resolve) => { + console.log('asynchronous listener to afterTx', bytesToHex(event.transaction.hash())) + // we need to call resolve() to avoid the event listener hanging + resolve?.() +}) + +vm.events.on('afterTx', (event) => { + console.log('synchronous listener to afterTx', bytesToHex(event.transaction.hash())) +}) +``` + Please note that there are additional EVM-specific events in the [@ethereumjs/evm](https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/evm) package. #### Asynchronous event handlers diff --git a/packages/vm/examples/eventListener.ts b/packages/vm/examples/eventListener.ts new file mode 100644 index 0000000000..d5fed2eaa3 --- /dev/null +++ b/packages/vm/examples/eventListener.ts @@ -0,0 +1,34 @@ +import { Common, Hardfork, Mainnet } from '@ethereumjs/common' +import { createLegacyTx } from '@ethereumjs/tx' +import { bytesToHex, createZeroAddress } from '@ethereumjs/util' +import { createVM, runTx } from '@ethereumjs/vm' + +const main = async () => { + const common = new Common({ chain: Mainnet, hardfork: Hardfork.Shanghai }) + const vm = await createVM({ common }) + + // Setup an event listener on the `afterTx` event + vm.events.on('afterTx', (event, resolve) => { + console.log('asynchronous listener to afterTx', bytesToHex(event.transaction.hash())) + // we need to call resolve() to avoid the event listener hanging + resolve?.() + }) + + vm.events.on('afterTx', (event) => { + console.log('synchronous listener to afterTx', bytesToHex(event.transaction.hash())) + }) + + const tx = createLegacyTx({ + gasLimit: BigInt(21000), + gasPrice: BigInt(1000000000), + value: BigInt(1), + to: createZeroAddress(), + v: BigInt(37), + r: BigInt('62886504200765677832366398998081608852310526822767264927793100349258111544447'), + s: BigInt('21948396863567062449199529794141973192314514851405455194940751428901681436138'), + }) + const res = await runTx(vm, { tx, skipBalance: true }) + console.log(res.totalGasSpent) // 21000n - gas cost for simple ETH transfer +} + +void main() diff --git a/packages/vm/package.json b/packages/vm/package.json index 32934c073e..195fcfb97c 100644 --- a/packages/vm/package.json +++ b/packages/vm/package.json @@ -67,13 +67,14 @@ "@ethereumjs/block": "^5.3.0", "@ethereumjs/common": "^4.4.0", "@ethereumjs/evm": "^3.1.0", + "@ethereumjs/mpt": "^6.2.2", "@ethereumjs/rlp": "^5.0.2", "@ethereumjs/statemanager": "^2.4.0", - "@ethereumjs/mpt": "^6.2.2", "@ethereumjs/tx": "^5.4.0", "@ethereumjs/util": "^9.1.0", "debug": "^4.3.3", - "ethereum-cryptography": "^3.0.0" + "ethereum-cryptography": "^3.0.0", + "eventemitter3": "^5.0.1" }, "devDependencies": { "@ethereumjs/blockchain": "^7.3.0", diff --git a/packages/vm/src/types.ts b/packages/vm/src/types.ts index 0e3d7e49cd..1aec711032 100644 --- a/packages/vm/src/types.ts +++ b/packages/vm/src/types.ts @@ -80,7 +80,7 @@ export type EVMProfilerOpts = { // extra options here (such as use X hardfork for gas) } -export type VMEvents = { +export type VMEvent = { beforeBlock: (data: Block, resolve?: (result?: any) => void) => void afterBlock: (data: AfterBlockEvent, resolve?: (result?: any) => void) => void beforeTx: (data: TypedTransaction, resolve?: (result?: any) => void) => void diff --git a/packages/vm/src/vm.ts b/packages/vm/src/vm.ts index 5d010271a4..114931d285 100644 --- a/packages/vm/src/vm.ts +++ b/packages/vm/src/vm.ts @@ -1,10 +1,10 @@ import { createEVM } from '@ethereumjs/evm' -import { AsyncEventEmitter } from '@ethereumjs/util' +import { EventEmitter } from 'eventemitter3' import { createVM } from './constructors.js' import { paramsVM } from './params.js' -import type { VMEvents, VMOpts } from './types.js' +import type { VMEvent, VMOpts } from './types.js' import type { Common, StateManagerInterface } from '@ethereumjs/common' import type { EVMInterface, EVMMockBlockchainInterface } from '@ethereumjs/evm' import type { BigIntLike } from '@ethereumjs/util' @@ -12,8 +12,6 @@ import type { BigIntLike } from '@ethereumjs/util' /** * Execution engine which can be used to run a blockchain, individual * blocks, individual transactions, or snippets of EVM bytecode. - * - * This class is an AsyncEventEmitter, please consult the README to learn how to use it. */ export class VM { /** @@ -28,7 +26,7 @@ export class VM { readonly common: Common - readonly events: AsyncEventEmitter + readonly events: EventEmitter /** * The EVM used for bytecode execution */ @@ -44,9 +42,7 @@ export class VM { * set to public due to implementation internals * @hidden */ - public _emit(topic: keyof VMEvents, data: any): Promise { - return new Promise((resolve) => this.events.emit(topic, data, resolve)) - } + public readonly _emit: (topic: string, data: any) => Promise /** * VM is run in DEBUG mode (default: false) @@ -73,8 +69,20 @@ export class VM { this.blockchain = opts.blockchain! this.evm = opts.evm! - this.events = new AsyncEventEmitter() - + this.events = new EventEmitter() + + this._emit = async (topic: string, data: any): Promise => { + const listeners = this.events.listeners(topic as keyof VMEvent) + for (const listener of listeners) { + if (listener.length === 2) { + await new Promise((resolve) => { + listener(data, resolve) + }) + } else { + listener(data) + } + } + } this._opts = opts this._setHardfork = opts.setHardfork ?? false diff --git a/packages/vm/test/api/EIPs/eip-1153.spec.ts b/packages/vm/test/api/EIPs/eip-1153.spec.ts index 1cdfa5a095..559c2b0fa3 100644 --- a/packages/vm/test/api/EIPs/eip-1153.spec.ts +++ b/packages/vm/test/api/EIPs/eip-1153.spec.ts @@ -25,7 +25,7 @@ describe('EIP 1153: transient storage', () => { let currentGas = initialGas const vm = await createVM({ common }) - vm.evm.events!.on('step', function (step: any) { + vm.evm.events!.on('step', function (step, resolve) { const gasUsed = currentGas - step.gasLeft currentGas = step.gasLeft @@ -51,6 +51,7 @@ describe('EIP 1153: transient storage', () => { ) } i++ + resolve?.() }) for (const { code, address } of test.contracts) { diff --git a/packages/vm/test/api/EIPs/eip-2929.spec.ts b/packages/vm/test/api/EIPs/eip-2929.spec.ts index 7059351319..78510169c5 100644 --- a/packages/vm/test/api/EIPs/eip-2929.spec.ts +++ b/packages/vm/test/api/EIPs/eip-2929.spec.ts @@ -18,7 +18,7 @@ describe('EIP 2929: gas cost tests', () => { let i = 0 let currentGas = initialGas const vm = await createVM({ common }) - vm.evm.events!.on('step', function (step: any) { + vm.evm.events!.on('step', function (step, resolve) { const gasUsed = currentGas - step.gasLeft currentGas = step.gasLeft @@ -45,6 +45,7 @@ describe('EIP 2929: gas cost tests', () => { } } i++ + resolve?.() }) await vm.stateManager.putCode(address, hexToBytes(test.code)) diff --git a/packages/vm/test/api/EIPs/eip-2930-accesslists.spec.ts b/packages/vm/test/api/EIPs/eip-2930-accesslists.spec.ts index 9a383d673f..32ce84124b 100644 --- a/packages/vm/test/api/EIPs/eip-2930-accesslists.spec.ts +++ b/packages/vm/test/api/EIPs/eip-2930-accesslists.spec.ts @@ -67,8 +67,9 @@ describe('EIP-2930 Optional Access Lists tests', () => { let trace: any = [] - vm.evm.events!.on('step', (o: any) => { + vm.evm.events!.on('step', (o, resolve) => { trace.push([o.opcode.name, o.gasLeft]) + resolve?.() }) await runTx(vm, { tx: txnWithAccessList }) diff --git a/packages/vm/test/api/EIPs/eip-3198-BaseFee.spec.ts b/packages/vm/test/api/EIPs/eip-3198-BaseFee.spec.ts index 4002b59db0..d637096d63 100644 --- a/packages/vm/test/api/EIPs/eip-3198-BaseFee.spec.ts +++ b/packages/vm/test/api/EIPs/eip-3198-BaseFee.spec.ts @@ -6,7 +6,6 @@ import { assert, describe, it } from 'vitest' import { createVM, runTx } from '../../../src/index.js' -import type { InterpreterStep } from '@ethereumjs/evm' import type { TypedTransaction } from '@ethereumjs/tx' const common = new Common({ @@ -78,10 +77,11 @@ describe('EIP3198 tests', () => { // Track stack let stack: any = [] - vm.evm.events!.on('step', (iStep: InterpreterStep) => { + vm.evm.events!.on('step', (iStep, resolve) => { if (iStep.opcode.name === 'STOP') { stack = iStep.stack } + resolve?.() }) const results = await runTx(vm, { diff --git a/packages/vm/test/api/EIPs/eip-3529.spec.ts b/packages/vm/test/api/EIPs/eip-3529.spec.ts index 42922d640a..788d34dc14 100644 --- a/packages/vm/test/api/EIPs/eip-3529.spec.ts +++ b/packages/vm/test/api/EIPs/eip-3529.spec.ts @@ -5,7 +5,6 @@ import { assert, describe, it } from 'vitest' import { createVM, runTx } from '../../../src/index.js' -import type { InterpreterStep } from '@ethereumjs/evm' import type { PrefixedHexString } from '@ethereumjs/util' const address = new Address(hexToBytes(`0x${'11'.repeat(20)}`)) @@ -118,11 +117,12 @@ describe('EIP-3529 tests', () => { let gasRefund: bigint let gasLeft: bigint - vm.evm.events!.on('step', (step: InterpreterStep) => { + vm.evm.events!.on('step', (step, resolve) => { if (step.opcode.name === 'STOP') { gasRefund = step.gasRefund gasLeft = step.gasLeft } + resolve?.() }) const gasLimit = BigInt(100000) @@ -184,13 +184,14 @@ describe('EIP-3529 tests', () => { let startGas: bigint let finalGas: bigint - vm.evm.events!.on('step', (step: InterpreterStep) => { + vm.evm.events!.on('step', (step, resolve) => { if (startGas === undefined) { startGas = step.gasLeft } if (step.opcode.name === 'STOP') { finalGas = step.gasLeft } + resolve?.() }) const address = new Address(hexToBytes(`0x${'20'.repeat(20)}`)) diff --git a/packages/vm/test/api/EIPs/eip-3541.spec.ts b/packages/vm/test/api/EIPs/eip-3541.spec.ts index 2717ccb45a..34dc597301 100644 --- a/packages/vm/test/api/EIPs/eip-3541.spec.ts +++ b/packages/vm/test/api/EIPs/eip-3541.spec.ts @@ -5,7 +5,6 @@ import { assert, describe, it } from 'vitest' import { createVM, runTx } from '../../../src/index.js' -import type { InterpreterStep } from '@ethereumjs/evm' import type { Address } from '@ethereumjs/util' const pkey = hexToBytes(`0x${'20'.repeat(32)}`) @@ -71,10 +70,11 @@ describe('EIP 3541 tests', () => { const vm = await createVM({ common }) let address: Address - vm.evm.events!.on('step', (step: InterpreterStep) => { + vm.evm.events!.on('step', (step, resolve) => { if (step.depth === 1) { address = step.address } + resolve?.() }) await runTx(vm, { tx, skipHardForkValidation: true }) @@ -106,10 +106,11 @@ describe('EIP 3541 tests', () => { const vm = await createVM({ common }) let address: Address - vm.evm.events!.on('step', (step: InterpreterStep) => { + vm.evm.events!.on('step', (step, resolve) => { if (step.depth === 1) { address = step.address } + resolve?.() }) await runTx(vm, { tx, skipHardForkValidation: true }) diff --git a/packages/vm/test/api/EIPs/eip-3855.spec.ts b/packages/vm/test/api/EIPs/eip-3855.spec.ts index 480a236df5..1b78903624 100644 --- a/packages/vm/test/api/EIPs/eip-3855.spec.ts +++ b/packages/vm/test/api/EIPs/eip-3855.spec.ts @@ -5,8 +5,6 @@ import { assert, describe, it } from 'vitest' import { createVM } from '../../../src/index.js' -import type { InterpreterStep } from '@ethereumjs/evm' - describe('EIP 3855 tests', () => { const common = new Common({ chain: Mainnet, hardfork: Hardfork.Chainstart, eips: [3855] }) const commonNoEIP3855 = new Common({ @@ -18,8 +16,9 @@ describe('EIP 3855 tests', () => { it('should correctly use push0 opcode', async () => { const vm = await createVM({ common }) let stack: bigint[] - vm.evm.events!.on('step', (e: InterpreterStep) => { + vm.evm.events!.on('step', (e, resolve) => { stack = e.stack + resolve?.() }) const result = await vm.evm.runCode!({ @@ -35,8 +34,9 @@ describe('EIP 3855 tests', () => { it('should correctly use push0 to create a stack with stack limit length', async () => { const vm = await createVM({ common }) let stack: bigint[] = [] - vm.evm.events!.on('step', (e: InterpreterStep) => { + vm.evm.events!.on('step', (e, resolve) => { stack = e.stack + resolve?.() }) const depth = Number(common.param('stackLimit')) diff --git a/packages/vm/test/api/EIPs/eip-4399-supplant-difficulty-opcode-with-prevrando.spec.ts b/packages/vm/test/api/EIPs/eip-4399-supplant-difficulty-opcode-with-prevrando.spec.ts index 781dd53c88..a46365673a 100644 --- a/packages/vm/test/api/EIPs/eip-4399-supplant-difficulty-opcode-with-prevrando.spec.ts +++ b/packages/vm/test/api/EIPs/eip-4399-supplant-difficulty-opcode-with-prevrando.spec.ts @@ -6,8 +6,6 @@ import { assert, describe, it } from 'vitest' import { createVM } from '../../../src/index.js' -import type { InterpreterStep } from '@ethereumjs/evm' - describe('EIP-4399 -> 0x44 (DIFFICULTY) should return PREVRANDAO', () => { it('should return the right values', async () => { const common = new Common({ chain: Mainnet, hardfork: Hardfork.London }) @@ -25,10 +23,11 @@ describe('EIP-4399 -> 0x44 (DIFFICULTY) should return PREVRANDAO', () => { // Track stack let stack: any = [] - vm.evm.events!.on('step', (iStep: InterpreterStep) => { + vm.evm.events!.on('step', (iStep, resolve) => { if (iStep.opcode.name === 'STOP') { stack = iStep.stack } + resolve?.() }) const runCodeArgs = { diff --git a/packages/vm/test/api/EIPs/eip-4895-withdrawals.spec.ts b/packages/vm/test/api/EIPs/eip-4895-withdrawals.spec.ts index 6686a10350..5f1ff5c1a7 100644 --- a/packages/vm/test/api/EIPs/eip-4895-withdrawals.spec.ts +++ b/packages/vm/test/api/EIPs/eip-4895-withdrawals.spec.ts @@ -108,8 +108,9 @@ describe('EIP4895 tests', () => { ) let result: Uint8Array - vm.events.on('afterTx', (e) => { + vm.events.on('afterTx', (e, resolve) => { result = e.execResult.returnValue + resolve?.() }) await runBlock(vm, { block, generate: true }) diff --git a/packages/vm/test/api/EIPs/eip-7002.spec.ts b/packages/vm/test/api/EIPs/eip-7002.spec.ts index f92e8d661a..01e5287231 100644 --- a/packages/vm/test/api/EIPs/eip-7002.spec.ts +++ b/packages/vm/test/api/EIPs/eip-7002.spec.ts @@ -107,8 +107,9 @@ describe('EIP-7002 tests', () => { ) let generatedBlock: Block - vm.events.on('afterBlock', (e) => { + vm.events.on('afterBlock', (e, resolve) => { generatedBlock = e.block + resolve?.() }) await runBlock(vm, { diff --git a/packages/vm/test/api/events.spec.ts b/packages/vm/test/api/events.spec.ts index 48c9d4fd77..ab94c5d5f3 100644 --- a/packages/vm/test/api/events.spec.ts +++ b/packages/vm/test/api/events.spec.ts @@ -12,8 +12,9 @@ describe('VM events', () => { const vm = await createVM() let emitted - vm.events.on('beforeBlock', (val: any) => { + vm.events.on('beforeBlock', (val, resolve) => { emitted = val + resolve?.() }) const block = new Block() @@ -31,8 +32,9 @@ describe('VM events', () => { const vm = await createVM() let emitted - vm.events.on('afterBlock', (val: any) => { + vm.events.on('afterBlock', (val, resolve) => { emitted = val + resolve?.() }) const block = new Block() @@ -51,8 +53,9 @@ describe('VM events', () => { const vm = await createVM() let emitted - vm.events.on('beforeTx', (val: any) => { + vm.events.on('beforeTx', (val, resolve) => { emitted = val + resolve?.() }) const tx = createFeeMarket1559Tx({ @@ -71,8 +74,9 @@ describe('VM events', () => { const address = createAddressFromPrivateKey(privKey) await vm.stateManager.putAccount(address, new Account(BigInt(0), BigInt(0x11111111))) let emitted: any - vm.events.on('afterTx', (val: any) => { + vm.events.on('afterTx', (val: any, resolve) => { emitted = val + resolve?.() }) const tx = createFeeMarket1559Tx({ @@ -92,8 +96,9 @@ describe('VM events', () => { const address = createAddressFromPrivateKey(privKey) await vm.stateManager.putAccount(address, new Account(BigInt(0), BigInt(0x11111111))) let emitted: any - vm.evm.events!.on('beforeMessage', (val: any) => { + vm.evm.events!.on('beforeMessage', (val, resolve) => { emitted = val + resolve?.() }) const tx = createFeeMarket1559Tx({ @@ -114,8 +119,9 @@ describe('VM events', () => { const address = createAddressFromPrivateKey(privKey) await vm.stateManager.putAccount(address, new Account(BigInt(0), BigInt(0x11111111))) let emitted: any - vm.evm.events!.on('afterMessage', (val: any) => { + vm.evm.events!.on('afterMessage', (val, resolve) => { emitted = val + resolve?.() }) const tx = createFeeMarket1559Tx({ @@ -134,8 +140,9 @@ describe('VM events', () => { const vm = await createVM() let lastEmitted: any - vm.evm.events!.on('step', (val: any) => { + vm.evm.events!.on('step', (val, resolve) => { lastEmitted = val + resolve?.() }) // This is a deployment transaction that pushes 0x41 (i.e. ascii A) followed by 31 0s to @@ -156,8 +163,9 @@ describe('VM events', () => { const vm = await createVM() let emitted: any - vm.evm.events!.on('newContract', (val: any) => { + vm.evm.events!.on('newContract', (val, resolve) => { emitted = val + resolve?.() }) // This is a deployment transaction that pushes 0x41 (i.e. ascii A) followed by 31 0s to diff --git a/packages/vm/test/api/runBlock.spec.ts b/packages/vm/test/api/runBlock.spec.ts index a6ce711cb6..b51897993e 100644 --- a/packages/vm/test/api/runBlock.spec.ts +++ b/packages/vm/test/api/runBlock.spec.ts @@ -404,8 +404,9 @@ async function runBlockAndGetAfterBlockEvent( runBlockOpts: RunBlockOpts, ): Promise { let results: AfterBlockEvent - function handler(event: AfterBlockEvent) { + function handler(event: AfterBlockEvent, resolve: any) { results = event + resolve?.() } try { diff --git a/packages/vm/test/api/runTx.spec.ts b/packages/vm/test/api/runTx.spec.ts index b2d92071f3..fb2e8de1af 100644 --- a/packages/vm/test/api/runTx.spec.ts +++ b/packages/vm/test/api/runTx.spec.ts @@ -50,7 +50,7 @@ const TRANSACTION_TYPES = [ ] const common = new Common({ chain: Mainnet, hardfork: Hardfork.London }) -common.events.setMaxListeners(100) + describe('runTx() -> successful API parameter usage', async () => { async function simpleRun(vm: VM, msg: string) { for (const txType of TRANSACTION_TYPES) { diff --git a/packages/vm/test/t8n/t8ntool.ts b/packages/vm/test/t8n/t8ntool.ts index e322d9a241..4344dba2a6 100644 --- a/packages/vm/test/t8n/t8ntool.ts +++ b/packages/vm/test/t8n/t8ntool.ts @@ -95,7 +95,10 @@ export class TransitionTool { let index = 0 - this.vm.events.on('afterTx', (event) => this.afterTx(event, index, builder)) + this.vm.events.on('afterTx', (event, resolve) => { + this.afterTx(event, index, builder) + resolve?.() + }) for (const txData of this.txsData) { try { @@ -142,15 +145,17 @@ export class TransitionTool { this.stateTracker = new StateTracker(this.vm, this.alloc) if (args.log === true) { - this.vm.events.on('beforeTx', () => { + this.vm.events.on('beforeTx', (_, resolve) => { // eslint-disable-next-line no-console console.log('Processing new transaction...') + resolve?.() }) - this.vm.events.on('afterTx', () => { + this.vm.events.on('afterTx', (_, resolve) => { // eslint-disable-next-line no-console console.log('Done processing transaction (system operations might follow next)') + resolve?.() }) - this.vm.evm.events?.on('step', (e) => { + this.vm.evm.events?.on('step', (e, resolve) => { // eslint-disable-next-line no-console console.log({ gasLeft: e.gasLeft.toString(), @@ -159,6 +164,7 @@ export class TransitionTool { depth: e.depth, address: e.address.toString(), }) + resolve?.() }) } } diff --git a/packages/vm/test/tester/runners/GeneralStateTestsRunner.ts b/packages/vm/test/tester/runners/GeneralStateTestsRunner.ts index 10e9e3bbca..33a146faeb 100644 --- a/packages/vm/test/tester/runners/GeneralStateTestsRunner.ts +++ b/packages/vm/test/tester/runners/GeneralStateTestsRunner.ts @@ -116,7 +116,7 @@ async function runTestCase(options: any, testData: any, t: tape.Test) { const account = await vm.stateManager.getAccount(coinbaseAddress) await vm.evm.journal.putAccount(coinbaseAddress, account ?? new Account()) - const stepHandler = (e: InterpreterStep) => { + const stepHandler = (e: InterpreterStep, resolve: any) => { let hexStack = [] hexStack = e.stack.map((item: bigint) => { return '0x' + item.toString(16) @@ -133,13 +133,15 @@ async function runTestCase(options: any, testData: any, t: tape.Test) { } t.comment(JSON.stringify(opTrace)) + resolve?.() } - const afterTxHandler = async () => { + const afterTxHandler = async (_: any, resolve: any) => { const stateRoot = { stateRoot: bytesToHex(await vm.stateManager.getStateRoot()), } t.comment(JSON.stringify(stateRoot)) + resolve?.() } if (tx) {