From 48348d34c8aa7dbfeb26a53f4037558b883be2d6 Mon Sep 17 00:00:00 2001 From: jacobmakarsky Date: Mon, 20 Jan 2025 19:08:10 -0500 Subject: [PATCH 1/4] add support for WebSocketProvider by using JsonRpcApiProvider abstract type, remove use of polling provider --- .../V2/railgun-smart-wallet.ts | 23 ++++------- .../V3/poseidon-merkle-accumulator.ts | 28 +++++-------- .../V3/poseidon-merkle-verifier.ts | 5 +-- .../V3/token-vault-contract.ts | 5 +-- src/railgun-engine.ts | 39 +++++++------------ 5 files changed, 34 insertions(+), 66 deletions(-) diff --git a/src/contracts/railgun-smart-wallet/V2/railgun-smart-wallet.ts b/src/contracts/railgun-smart-wallet/V2/railgun-smart-wallet.ts index 2305124d..561158df 100644 --- a/src/contracts/railgun-smart-wallet/V2/railgun-smart-wallet.ts +++ b/src/contracts/railgun-smart-wallet/V2/railgun-smart-wallet.ts @@ -2,8 +2,8 @@ import { Contract, ContractEventPayload, ContractTransaction, - FallbackProvider, Interface, + JsonRpcApiProvider, Result, } from 'ethers'; import EventEmitter from 'events'; @@ -62,8 +62,6 @@ const SCAN_TIMEOUT_ERROR_MESSAGE = 'getLogs request timed out after 5 seconds.'; export class RailgunSmartWalletContract extends EventEmitter { readonly contract: RailgunSmartWallet; - readonly contractForListeners: RailgunSmartWallet; - readonly address: string; readonly chain: Chain; @@ -73,12 +71,14 @@ export class RailgunSmartWalletContract extends EventEmitter { /** * Connect to Railgun instance on network * @param railgunSmartWalletContractAddress - address of Railgun instance (Proxy contract) - * @param provider - Network provider + * @param defaultProvider - Network provider + * @param pollingProvider - DEPRECATED + * @param chain */ constructor( railgunSmartWalletContractAddress: string, - defaultProvider: PollingJsonRpcProvider | FallbackProvider, - pollingProvider: PollingJsonRpcProvider, + defaultProvider: JsonRpcApiProvider, + pollingProvider: JsonRpcApiProvider, chain: Chain, ) { super(); @@ -89,15 +89,6 @@ export class RailgunSmartWalletContract extends EventEmitter { defaultProvider, ) as unknown as RailgunSmartWallet; - // Because of a 'stallTimeout' bug in Ethers v6, all providers in a FallbackProvider will get called simultaneously. - // So, we'll use a single json rpc (the first in the FallbackProvider) to poll for the event listeners. - assertIsPollingProvider(pollingProvider); - this.contractForListeners = new Contract( - railgunSmartWalletContractAddress, - ABIRailgunSmartWallet, - pollingProvider, - ) as unknown as RailgunSmartWallet; - this.chain = chain; } @@ -290,7 +281,7 @@ export class RailgunSmartWalletContract extends EventEmitter { const transactTopic = this.contract.getEvent('Transact').getFragment().topicHash; const unshieldTopic = this.contract.getEvent('Unshield').getFragment().topicHash; - await this.contractForListeners.on( + await this.contract.on( // @ts-expect-error - Use * to request all events '*', // All Events (event: ContractEventPayload) => { diff --git a/src/contracts/railgun-smart-wallet/V3/poseidon-merkle-accumulator.ts b/src/contracts/railgun-smart-wallet/V3/poseidon-merkle-accumulator.ts index 760f6726..900c3cb8 100644 --- a/src/contracts/railgun-smart-wallet/V3/poseidon-merkle-accumulator.ts +++ b/src/contracts/railgun-smart-wallet/V3/poseidon-merkle-accumulator.ts @@ -1,12 +1,10 @@ -import { Contract, ContractEventPayload, FallbackProvider, Result } from 'ethers'; +import { Contract, ContractEventPayload, type JsonRpcApiProvider, Result } from 'ethers'; import EventEmitter from 'events'; import { Chain } from '../../../models/engine-types'; -import { PollingJsonRpcProvider } from '../../../provider/polling-json-rpc-provider'; import { PoseidonMerkleAccumulator } from '../../../abi/typechain/PoseidonMerkleAccumulator'; import { ABIPoseidonMerkleAccumulator } from '../../../abi/abi'; import { ByteLength, ByteUtils } from '../../../utils/bytes'; import EngineDebug from '../../../debugger/debugger'; -import { assertIsPollingProvider } from '../../../provider/polling-util'; import { EngineEvent, EventsCommitmentListener, @@ -29,8 +27,6 @@ const SCAN_TIMEOUT_ERROR_MESSAGE = 'getLogs request timed out after 5 seconds.'; export class PoseidonMerkleAccumulatorContract extends EventEmitter { readonly contract: PoseidonMerkleAccumulator; - readonly contractForListeners: PoseidonMerkleAccumulator; - readonly address: string; readonly chain: Chain; @@ -39,10 +35,16 @@ export class PoseidonMerkleAccumulatorContract extends EventEmitter { private readonly eventTopic: string; + /** + * @param address + * @param provider + * @param pollingProvider - DEPRECATED + * @param chain + */ constructor( address: string, - provider: PollingJsonRpcProvider | FallbackProvider, - pollingProvider: PollingJsonRpcProvider, + provider: JsonRpcApiProvider, + pollingProvider: JsonRpcApiProvider, chain: Chain, ) { super(); @@ -54,15 +56,6 @@ export class PoseidonMerkleAccumulatorContract extends EventEmitter { ) as unknown as PoseidonMerkleAccumulator; this.eventTopic = this.contract.getEvent('AccumulatorStateUpdate').getFragment().topicHash; this.chain = chain; - - // Because of a 'stallTimeout' bug in Ethers v6, all providers in a FallbackProvider will get called simultaneously. - // So, we'll use a single json rpc (the first in the FallbackProvider) to poll for the event listeners. - assertIsPollingProvider(pollingProvider); - this.contractForListeners = new Contract( - address, - ABIPoseidonMerkleAccumulator, - pollingProvider, - ) as unknown as PoseidonMerkleAccumulator; } /** @@ -104,7 +97,7 @@ export class PoseidonMerkleAccumulatorContract extends EventEmitter { eventsRailgunTransactionsV3Listener: EventsRailgunTransactionListenerV3, triggerWalletBalanceDecryptions: (txidVersion: TXIDVersion) => Promise, ): Promise { - await this.contractForListeners.on(this.eventTopic as any, (event: ContractEventPayload) => { + await this.contract.on(this.eventTopic as any, (event: ContractEventPayload) => { try { if (event.log.topics.length !== 1) { throw new Error('Requires one topic for railgun events'); @@ -264,6 +257,5 @@ export class PoseidonMerkleAccumulatorContract extends EventEmitter { */ async unload() { await this.contract.removeAllListeners(); - await this.contractForListeners?.removeAllListeners(); } } diff --git a/src/contracts/railgun-smart-wallet/V3/poseidon-merkle-verifier.ts b/src/contracts/railgun-smart-wallet/V3/poseidon-merkle-verifier.ts index ca2f5fc9..b90863a9 100644 --- a/src/contracts/railgun-smart-wallet/V3/poseidon-merkle-verifier.ts +++ b/src/contracts/railgun-smart-wallet/V3/poseidon-merkle-verifier.ts @@ -1,5 +1,4 @@ -import { Contract, ContractTransaction, FallbackProvider } from 'ethers'; -import { PollingJsonRpcProvider } from '../../../provider/polling-json-rpc-provider'; +import { Contract, ContractTransaction, type JsonRpcApiProvider } from 'ethers'; import { PoseidonMerkleVerifier } from '../../../abi/typechain/PoseidonMerkleVerifier'; import { ABIPoseidonMerkleVerifier } from '../../../abi/abi'; import { ShieldCiphertextStruct } from '../../../abi/typechain/RailgunSmartWallet'; @@ -9,7 +8,7 @@ export class PoseidonMerkleVerifierContract { readonly address: string; - constructor(address: string, provider: PollingJsonRpcProvider | FallbackProvider) { + constructor(address: string, provider: JsonRpcApiProvider) { this.address = address; this.contract = new Contract( address, diff --git a/src/contracts/railgun-smart-wallet/V3/token-vault-contract.ts b/src/contracts/railgun-smart-wallet/V3/token-vault-contract.ts index ce5470b6..5d1e02b9 100644 --- a/src/contracts/railgun-smart-wallet/V3/token-vault-contract.ts +++ b/src/contracts/railgun-smart-wallet/V3/token-vault-contract.ts @@ -1,6 +1,5 @@ -import { Contract, FallbackProvider } from 'ethers'; +import { Contract, type JsonRpcApiProvider } from 'ethers'; import { TokenDataStructOutput, TokenVault } from '../../../abi/typechain/TokenVault'; -import { PollingJsonRpcProvider } from '../../../provider/polling-json-rpc-provider'; import { ABITokenVault } from '../../../abi/abi'; import { ByteLength, ByteUtils } from '../../../utils/bytes'; import EngineDebug from '../../../debugger/debugger'; @@ -10,7 +9,7 @@ export class TokenVaultContract { readonly address: string; - constructor(address: string, provider: PollingJsonRpcProvider | FallbackProvider) { + constructor(address: string, provider: JsonRpcApiProvider) { this.address = address; this.contract = new Contract(address, ABITokenVault, provider) as unknown as TokenVault; } diff --git a/src/railgun-engine.ts b/src/railgun-engine.ts index 93aa8222..0008fb56 100644 --- a/src/railgun-engine.ts +++ b/src/railgun-engine.ts @@ -1,6 +1,6 @@ import type { AbstractLevelDOWN } from 'abstract-leveldown'; import EventEmitter from 'events'; -import { FallbackProvider } from 'ethers'; +import type { JsonRpcApiProvider } from 'ethers'; import { RailgunSmartWalletContract } from './contracts/railgun-smart-wallet/V2/railgun-smart-wallet'; import { RelayAdaptV2Contract } from './contracts/relay-adapt/V2/relay-adapt-v2'; import { Database, DatabaseNamespace } from './database/database'; @@ -46,8 +46,6 @@ import { CURRENT_TXID_V2_MERKLETREE_HISTORY_VERSION, CURRENT_UTXO_MERKLETREE_HISTORY_VERSION, } from './utils/constants'; -import { PollingJsonRpcProvider } from './provider/polling-json-rpc-provider'; -import { assertIsPollingProvider } from './provider/polling-util'; import { isDefined } from './utils/is-defined'; import { UTXOMerkletree } from './merkletree/utxo-merkletree'; import { TXIDMerkletree } from './merkletree/txid-merkletree'; @@ -1445,11 +1443,18 @@ class RailgunEngine extends EventEmitter { /** * Load network + * @param chain * @param railgunSmartWalletContractAddress - address of railgun instance (proxy contract) * @param relayAdaptV2ContractAddress - address of railgun instance (proxy contract) - * @param provider - ethers provider for network - * @param deploymentBlock - block number to start scanning from - */ + * @param poseidonMerkleAccumulatorV3Address + * @param poseidonMerkleVerifierV3Address + * @param tokenVaultV3Address + * @param defaultProvider - ethers provider for network + * @param pollingProvider - DEPRECATED + * @param deploymentBlocks - block number to start scanning from + * @param poiLaunchBlock + * @param supportsV3 + */ async loadNetwork( chain: Chain, railgunSmartWalletContractAddress: string, @@ -1457,8 +1462,8 @@ class RailgunEngine extends EventEmitter { poseidonMerkleAccumulatorV3Address: string, poseidonMerkleVerifierV3Address: string, tokenVaultV3Address: string, - defaultProvider: PollingJsonRpcProvider | FallbackProvider, - pollingProvider: PollingJsonRpcProvider, + defaultProvider: JsonRpcApiProvider, + pollingProvider: JsonRpcApiProvider, deploymentBlocks: Record, poiLaunchBlock: Optional, supportsV3: boolean, @@ -1480,24 +1485,6 @@ class RailgunEngine extends EventEmitter { throw err; } - assertIsPollingProvider(pollingProvider); - try { - await promiseTimeout( - pollingProvider.getBlockNumber(), - 60_000, - 'Timed out waiting for polling RPC provider to connect.', - ); - } catch (cause) { - const err = new Error( - 'Failed to get block number from polling provider when loading network', - { - cause, - }, - ); - EngineDebug.error(cause as Error); - throw err; - } - if (supportsV3) { addChainSupportsV3(chain); } From f85ab122d3c100317893910ba4902e1d7e5bce1b Mon Sep 17 00:00:00 2001 From: jacobmakarsky Date: Mon, 20 Jan 2025 19:13:12 -0500 Subject: [PATCH 2/4] fix test and listener unload --- src/contracts/railgun-smart-wallet/V2/railgun-smart-wallet.ts | 1 - src/test/helper.test.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/contracts/railgun-smart-wallet/V2/railgun-smart-wallet.ts b/src/contracts/railgun-smart-wallet/V2/railgun-smart-wallet.ts index 561158df..2aacb66b 100644 --- a/src/contracts/railgun-smart-wallet/V2/railgun-smart-wallet.ts +++ b/src/contracts/railgun-smart-wallet/V2/railgun-smart-wallet.ts @@ -624,6 +624,5 @@ export class RailgunSmartWalletContract extends EventEmitter { */ async unload() { await this.contract.removeAllListeners(); - await this.contractForListeners?.removeAllListeners(); } } diff --git a/src/test/helper.test.ts b/src/test/helper.test.ts index dccf7a4a..c2f47408 100644 --- a/src/test/helper.test.ts +++ b/src/test/helper.test.ts @@ -138,7 +138,7 @@ export const awaitRailgunSmartWalletEvent = async ( new Promise((resolve) => { // eslint-disable-next-line @typescript-eslint/no-floating-promises // @ts-expect-error - RailgunVersionedSmartContracts.getAccumulator(txidVersion, chain).contractForListeners.once( + RailgunVersionedSmartContracts.getAccumulator(txidVersion, chain).contract.once( event, () => resolve(), ); From db17ca533e9f2e505fc85f4c320d9a7da580ae22 Mon Sep 17 00:00:00 2001 From: jacobmakarsky Date: Mon, 20 Jan 2025 20:08:34 -0500 Subject: [PATCH 3/4] use base Provider type --- .../V2/__tests__/railgun-smart-wallet.test.ts | 15 --------------- .../V2/railgun-smart-wallet.ts | 5 +++-- .../V3/poseidon-merkle-accumulator.ts | 6 +++--- .../V3/poseidon-merkle-verifier.ts | 4 ++-- .../V3/token-vault-contract.ts | 4 ++-- src/railgun-engine.ts | 6 +++--- 6 files changed, 13 insertions(+), 27 deletions(-) diff --git a/src/contracts/railgun-smart-wallet/V2/__tests__/railgun-smart-wallet.test.ts b/src/contracts/railgun-smart-wallet/V2/__tests__/railgun-smart-wallet.test.ts index 0ef77d6b..3a463f8d 100644 --- a/src/contracts/railgun-smart-wallet/V2/__tests__/railgun-smart-wallet.test.ts +++ b/src/contracts/railgun-smart-wallet/V2/__tests__/railgun-smart-wallet.test.ts @@ -188,21 +188,6 @@ describe('railgun-smart-wallet', function runTests() { }; }); - it('Should fail to instantiate without a polling provider', () => { - const nonPollingProvider = new JsonRpcProvider(config.rpc); - expect(() => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any - return new RailgunSmartWalletContract( - 'abc', - nonPollingProvider as any, - nonPollingProvider as any, - chain, - ); - }).to.throw( - 'The JsonRpcProvider must have polling enabled. Use PollingJsonRpcProvider to instantiate.', - ); - }); - it('[HH] Should retrieve merkle root from contract', async function run() { if (!isDefined(process.env.RUN_HARDHAT_TESTS)) { this.skip(); diff --git a/src/contracts/railgun-smart-wallet/V2/railgun-smart-wallet.ts b/src/contracts/railgun-smart-wallet/V2/railgun-smart-wallet.ts index 2aacb66b..4ed36678 100644 --- a/src/contracts/railgun-smart-wallet/V2/railgun-smart-wallet.ts +++ b/src/contracts/railgun-smart-wallet/V2/railgun-smart-wallet.ts @@ -4,6 +4,7 @@ import { ContractTransaction, Interface, JsonRpcApiProvider, + Provider, Result, } from 'ethers'; import EventEmitter from 'events'; @@ -77,8 +78,8 @@ export class RailgunSmartWalletContract extends EventEmitter { */ constructor( railgunSmartWalletContractAddress: string, - defaultProvider: JsonRpcApiProvider, - pollingProvider: JsonRpcApiProvider, + defaultProvider: Provider, + pollingProvider: Provider, chain: Chain, ) { super(); diff --git a/src/contracts/railgun-smart-wallet/V3/poseidon-merkle-accumulator.ts b/src/contracts/railgun-smart-wallet/V3/poseidon-merkle-accumulator.ts index 900c3cb8..ecad8aac 100644 --- a/src/contracts/railgun-smart-wallet/V3/poseidon-merkle-accumulator.ts +++ b/src/contracts/railgun-smart-wallet/V3/poseidon-merkle-accumulator.ts @@ -1,4 +1,4 @@ -import { Contract, ContractEventPayload, type JsonRpcApiProvider, Result } from 'ethers'; +import { Contract, ContractEventPayload, Provider, Result } from 'ethers'; import EventEmitter from 'events'; import { Chain } from '../../../models/engine-types'; import { PoseidonMerkleAccumulator } from '../../../abi/typechain/PoseidonMerkleAccumulator'; @@ -43,8 +43,8 @@ export class PoseidonMerkleAccumulatorContract extends EventEmitter { */ constructor( address: string, - provider: JsonRpcApiProvider, - pollingProvider: JsonRpcApiProvider, + provider: Provider, + pollingProvider: Provider, chain: Chain, ) { super(); diff --git a/src/contracts/railgun-smart-wallet/V3/poseidon-merkle-verifier.ts b/src/contracts/railgun-smart-wallet/V3/poseidon-merkle-verifier.ts index b90863a9..e4de8d0e 100644 --- a/src/contracts/railgun-smart-wallet/V3/poseidon-merkle-verifier.ts +++ b/src/contracts/railgun-smart-wallet/V3/poseidon-merkle-verifier.ts @@ -1,4 +1,4 @@ -import { Contract, ContractTransaction, type JsonRpcApiProvider } from 'ethers'; +import { Contract, ContractTransaction, Provider } from 'ethers'; import { PoseidonMerkleVerifier } from '../../../abi/typechain/PoseidonMerkleVerifier'; import { ABIPoseidonMerkleVerifier } from '../../../abi/abi'; import { ShieldCiphertextStruct } from '../../../abi/typechain/RailgunSmartWallet'; @@ -8,7 +8,7 @@ export class PoseidonMerkleVerifierContract { readonly address: string; - constructor(address: string, provider: JsonRpcApiProvider) { + constructor(address: string, provider: Provider) { this.address = address; this.contract = new Contract( address, diff --git a/src/contracts/railgun-smart-wallet/V3/token-vault-contract.ts b/src/contracts/railgun-smart-wallet/V3/token-vault-contract.ts index 5d1e02b9..94fb24ad 100644 --- a/src/contracts/railgun-smart-wallet/V3/token-vault-contract.ts +++ b/src/contracts/railgun-smart-wallet/V3/token-vault-contract.ts @@ -1,4 +1,4 @@ -import { Contract, type JsonRpcApiProvider } from 'ethers'; +import { Contract, Provider } from 'ethers'; import { TokenDataStructOutput, TokenVault } from '../../../abi/typechain/TokenVault'; import { ABITokenVault } from '../../../abi/abi'; import { ByteLength, ByteUtils } from '../../../utils/bytes'; @@ -9,7 +9,7 @@ export class TokenVaultContract { readonly address: string; - constructor(address: string, provider: JsonRpcApiProvider) { + constructor(address: string, provider: Provider) { this.address = address; this.contract = new Contract(address, ABITokenVault, provider) as unknown as TokenVault; } diff --git a/src/railgun-engine.ts b/src/railgun-engine.ts index 0008fb56..6fa7fcbd 100644 --- a/src/railgun-engine.ts +++ b/src/railgun-engine.ts @@ -1,6 +1,6 @@ import type { AbstractLevelDOWN } from 'abstract-leveldown'; import EventEmitter from 'events'; -import type { JsonRpcApiProvider } from 'ethers'; +import type { JsonRpcApiProvider, Provider } from 'ethers'; import { RailgunSmartWalletContract } from './contracts/railgun-smart-wallet/V2/railgun-smart-wallet'; import { RelayAdaptV2Contract } from './contracts/relay-adapt/V2/relay-adapt-v2'; import { Database, DatabaseNamespace } from './database/database'; @@ -1462,8 +1462,8 @@ class RailgunEngine extends EventEmitter { poseidonMerkleAccumulatorV3Address: string, poseidonMerkleVerifierV3Address: string, tokenVaultV3Address: string, - defaultProvider: JsonRpcApiProvider, - pollingProvider: JsonRpcApiProvider, + defaultProvider: Provider, + pollingProvider: Provider, deploymentBlocks: Record, poiLaunchBlock: Optional, supportsV3: boolean, From 3e3f607a6c594618abfa2dd7528c40332e6406df Mon Sep 17 00:00:00 2001 From: jacobmakarsky Date: Tue, 21 Jan 2025 16:20:59 -0500 Subject: [PATCH 4/4] clearly deprecate pollingProvider and allow as optional --- .../railgun-smart-wallet/V2/railgun-smart-wallet.ts | 6 +++++- .../railgun-smart-wallet/V3/poseidon-merkle-accumulator.ts | 6 +++++- src/railgun-engine.ts | 6 +++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/contracts/railgun-smart-wallet/V2/railgun-smart-wallet.ts b/src/contracts/railgun-smart-wallet/V2/railgun-smart-wallet.ts index 4ed36678..9e589265 100644 --- a/src/contracts/railgun-smart-wallet/V2/railgun-smart-wallet.ts +++ b/src/contracts/railgun-smart-wallet/V2/railgun-smart-wallet.ts @@ -79,7 +79,11 @@ export class RailgunSmartWalletContract extends EventEmitter { constructor( railgunSmartWalletContractAddress: string, defaultProvider: Provider, - pollingProvider: Provider, + /** + * @deprecated pollingProvider will be removed in the next major version. + */ + pollingProvider?: Provider, + // @ts-ignore - Ignore optional param last requirement during deprecation chain: Chain, ) { super(); diff --git a/src/contracts/railgun-smart-wallet/V3/poseidon-merkle-accumulator.ts b/src/contracts/railgun-smart-wallet/V3/poseidon-merkle-accumulator.ts index ecad8aac..defbbf86 100644 --- a/src/contracts/railgun-smart-wallet/V3/poseidon-merkle-accumulator.ts +++ b/src/contracts/railgun-smart-wallet/V3/poseidon-merkle-accumulator.ts @@ -44,7 +44,11 @@ export class PoseidonMerkleAccumulatorContract extends EventEmitter { constructor( address: string, provider: Provider, - pollingProvider: Provider, + /** + * @deprecated pollingProvider will be removed in the next major version. + */ + pollingProvider?: Provider, + // @ts-ignore - Ignore optional param last requirement during deprecation chain: Chain, ) { super(); diff --git a/src/railgun-engine.ts b/src/railgun-engine.ts index 6fa7fcbd..66cea0f6 100644 --- a/src/railgun-engine.ts +++ b/src/railgun-engine.ts @@ -1463,7 +1463,11 @@ class RailgunEngine extends EventEmitter { poseidonMerkleVerifierV3Address: string, tokenVaultV3Address: string, defaultProvider: Provider, - pollingProvider: Provider, + /** + * @deprecated pollingProvider will be removed in the next major version. + */ + pollingProvider?: Provider, + // @ts-ignore - Ignore optional param last requirement during deprecation deploymentBlocks: Record, poiLaunchBlock: Optional, supportsV3: boolean,