diff --git a/integration-tests/test/alt-l2/shared/env.ts b/integration-tests/test/alt-l2/shared/env.ts index 342b8e735a..9971011571 100644 --- a/integration-tests/test/alt-l2/shared/env.ts +++ b/integration-tests/test/alt-l2/shared/env.ts @@ -15,6 +15,7 @@ import { import { l1Provider, l2Provider, + l2WsProvider, replicaProvider, verifierProvider, l1Wallet, @@ -70,6 +71,7 @@ export class OptimismEnv { messengerFast: CrossChainMessenger l1Provider: providers.JsonRpcProvider l2Provider: providers.JsonRpcProvider + l2WsProvider: providers.WebSocketProvider verifierProvider: providers.JsonRpcProvider replicaProvider: providers.JsonRpcProvider @@ -92,6 +94,7 @@ export class OptimismEnv { this.l2BobalinkWallet = args.l2BobalinkWallet this.l1Provider = args.l1Provider this.l2Provider = args.l2Provider + this.l2WsProvider = args.l2WsProvider this.verifierProvider = args.verifierProvider this.replicaProvider = args.replicaProvider this.bundlerUrl = args.bundlerUrl @@ -145,6 +148,7 @@ export class OptimismEnv { l2BobalinkWallet, l1Provider, l2Provider, + l2WsProvider, verifierProvider, replicaProvider, l1Bridge, diff --git a/integration-tests/test/alt-l2/shared/utils.ts b/integration-tests/test/alt-l2/shared/utils.ts index fee5a13c50..df54eb0c8b 100644 --- a/integration-tests/test/alt-l2/shared/utils.ts +++ b/integration-tests/test/alt-l2/shared/utils.ts @@ -63,6 +63,7 @@ const env = cleanEnv(process.env, { default: 'onchain', }), L2_URL: str({ default: 'http://localhost:8545' }), + L2_WS_URL: str( { default: 'ws://localhost:8546' }), L2_POLLING_INTERVAL: num({ default: 10 }), L2_WALLET_MIN_BALANCE_ETH: num({ default: 2, @@ -151,6 +152,9 @@ export const l2Provider = asL2Provider( ) l2Provider.pollingInterval = env.L2_POLLING_INTERVAL +// asL2Provider breaks Websocket provider +export const l2WsProvider = new providers.WebSocketProvider(env.L2_WS_URL) + export const replicaProvider = asL2Provider( new providers.JsonRpcProvider(env.REPLICA_URL) ) diff --git a/integration-tests/test/alt-l2/teleportation.spec.ts b/integration-tests/test/alt-l2/teleportation.spec.ts index a8ac273916..f8e4372c2d 100644 --- a/integration-tests/test/alt-l2/teleportation.spec.ts +++ b/integration-tests/test/alt-l2/teleportation.spec.ts @@ -17,7 +17,7 @@ import TeleportationJson from '@boba/contracts/artifacts/contracts/Teleportation import L1ERC20Json from '@boba/contracts/artifacts/contracts/test-helpers/L1ERC20.sol/L1ERC20.json' /* Imports: Interface */ -import { ChainInfo } from '@boba/teleportation/src/utils/types' +import { ChainInfo, DepositTeleportations} from '@boba/teleportation/src/utils/types' /* Imports: Core */ import { TeleportationService } from '@boba/teleportation/src/service' @@ -26,12 +26,14 @@ import { historyDataRepository, } from '@boba/teleportation/src/data-source' import { OptimismEnv } from './shared/env' -import { getContractFactory, predeploys } from '@eth-optimism/contracts' +import {JsonRpcProvider, WebSocketProvider} from "@ethersproject/providers"; describe('teleportation', () => { let env: OptimismEnv let signer: Signer let signerAddr: string + let wsProvider: WebSocketProvider + let httpProvider: JsonRpcProvider let wallet1: Wallet let address1: string @@ -50,9 +52,11 @@ describe('teleportation', () => { await AppDataSource.initialize() await AppDataSource.synchronize(true) // drops database and recreates - signer = env.l2Wallet + wsProvider = env.l2WsProvider + httpProvider = env.l2Provider + signer = env.l2Wallet.connect(wsProvider ?? httpProvider) signerAddr = await signer.getAddress() - wallet1 = env.l2Wallet_2 + wallet1 = env.l2Wallet_2.connect(wsProvider ?? httpProvider) address1 = wallet1.address await signer.sendTransaction({ @@ -73,7 +77,7 @@ describe('teleportation', () => { let L2BNBOnBobaEth: Contract before(async () => { - chainId = (await ethers.provider.getNetwork()).chainId + chainId = (await httpProvider.getNetwork()).chainId chainIdBnb = chainId + 1 Factory__Teleportation = new ethers.ContractFactory( @@ -121,8 +125,9 @@ describe('teleportation', () => { selectedBobaChains = [ { chainId, - url: 'http://localhost:8545', - provider: ethers.provider, + url: '', + provider: httpProvider, + wsProvider, testnet: true, name: 'localhost', teleportationAddress: Teleportation.address, @@ -141,7 +146,8 @@ describe('teleportation', () => { const chainIdToUse = useBnb ? chainIdBnb : chainId return new TeleportationService({ - l2RpcProvider: ethers.provider, + l2RpcProvider: httpProvider, + l2WSRpcProvider: wsProvider, chainId: chainIdToUse, teleportationAddress: useBnb ? TeleportationBNB.address @@ -174,7 +180,7 @@ describe('teleportation', () => { const teleportationService = await startTeleportationService() await teleportationService.init() - const blockNumber = await ethers.provider.getBlockNumber() + const blockNumber = await httpProvider.getBlockNumber() const events = await teleportationService._getEvents( Teleportation, @@ -192,7 +198,7 @@ describe('teleportation', () => { chainId ) - const latestBlockNumber = await ethers.provider.getBlockNumber() + const latestBlockNumber = await httpProvider.getBlockNumber() const latestEvents = await teleportationService._getEvents( Teleportation, Teleportation.filters.AssetReceived(), @@ -212,7 +218,7 @@ describe('teleportation', () => { const teleportationService = await startTeleportationService() await teleportationService.init() - const blockNumber = await ethers.provider.getBlockNumber() + const blockNumber = await httpProvider.getBlockNumber() const events = await teleportationService._getEvents( Teleportation, Teleportation.filters.AssetReceived(), @@ -270,7 +276,7 @@ describe('teleportation', () => { const teleportationService = await startTeleportationService() await teleportationService.init() - const blockNumber = await ethers.provider.getBlockNumber() + const blockNumber = await httpProvider.getBlockNumber() const events = await teleportationService._getEvents( Teleportation, Teleportation.filters.AssetReceived(), @@ -316,7 +322,7 @@ describe('teleportation', () => { const teleportationService = await startTeleportationService() await teleportationService.init() - const startBlockNumber = await ethers.provider.getBlockNumber() + const startBlockNumber = await httpProvider.getBlockNumber() // deposit token for (let i = 0; i < 15; i++) { @@ -328,7 +334,7 @@ describe('teleportation', () => { ) } - const endBlockNumber = await ethers.provider.getBlockNumber() + const endBlockNumber = await httpProvider.getBlockNumber() const latestEvents = await teleportationService._getEvents( Teleportation, Teleportation.filters.AssetReceived(), @@ -343,7 +349,7 @@ describe('teleportation', () => { const teleportationService = await startTeleportationService() await teleportationService.init() - const blockNumber = await ethers.provider.getBlockNumber() + const blockNumber = await httpProvider.getBlockNumber() const events = await teleportationService._getEvents( Teleportation, Teleportation.filters.AssetReceived(), @@ -378,13 +384,13 @@ describe('teleportation', () => { const preBOBABalance = await L2BOBA.balanceOf(address1) const preSignerBOBABalance = await L2BOBA.balanceOf(signerAddr) - const preBlockNumber = await ethers.provider.getBlockNumber() + const preBlockNumber = await httpProvider.getBlockNumber() await teleportationService._disburseTx(disbursement, chainId, blockNumber) const postBOBABalance = await L2BOBA.balanceOf(address1) const postSignerBOBABalance = await L2BOBA.balanceOf(signerAddr) - const postBlockNumber = await ethers.provider.getBlockNumber() + const postBlockNumber = await httpProvider.getBlockNumber() expect(preBOBABalance.sub(postBOBABalance)).to.be.eq( utils.parseEther('150') @@ -397,7 +403,8 @@ describe('teleportation', () => { }) describe('global tests', () => { - it('should watch Teleportation contract', async () => { + + it('should watch Teleportation contract via http', async () => { const teleportationService = await startTeleportationService() await teleportationService.init() @@ -410,10 +417,11 @@ describe('teleportation', () => { ) // check events - const latestBlock = await ethers.provider.getBlockNumber() + const latestBlock = await httpProvider.getBlockNumber() const depositTeleportations = { Teleportation, chainId, + wsAvailable: false, totalDeposits: BigNumber.from('0'), totalDisbursements: BigNumber.from('0'), height: 0, @@ -435,10 +443,11 @@ describe('teleportation', () => { const teleportationService = await startTeleportationService() await teleportationService.init() - const latestBlock = await ethers.provider.getBlockNumber() - const depositTeleportations = { + const latestBlock = await httpProvider.getBlockNumber() + const depositTeleportations: DepositTeleportations = { Teleportation, chainId, + wsAvailable: false, totalDeposits: BigNumber.from('0'), totalDisbursements: BigNumber.from('0'), height: 0, @@ -488,10 +497,11 @@ describe('teleportation', () => { } // check events - const latestBlock = await ethers.provider.getBlockNumber() + const latestBlock = await httpProvider.getBlockNumber() const depositTeleportations = { Teleportation, chainId, + wsAvailable: false, totalDeposits: BigNumber.from('0'), totalDisbursements: BigNumber.from('0'), height: 0, @@ -507,10 +517,11 @@ describe('teleportation', () => { const teleportationService = await startTeleportationService() await teleportationService.init() - const latestBlock = await ethers.provider.getBlockNumber() + const latestBlock = await httpProvider.getBlockNumber() const depositTeleportations = { Teleportation, chainId, + wsAvailable: false, totalDeposits: BigNumber.from('0'), totalDisbursements: BigNumber.from('0'), height: 0, @@ -551,10 +562,11 @@ describe('teleportation', () => { await historyDataRepository.delete({ chainId }) - const latestBlock = await ethers.provider.getBlockNumber() + const latestBlock = await httpProvider.getBlockNumber() const depositTeleportations = { Teleportation, chainId, + wsAvailable: false, totalDeposits: BigNumber.from('0'), totalDisbursements: BigNumber.from('0'), height: 0, @@ -573,6 +585,47 @@ describe('teleportation', () => { const postBOBABalance = await L2BOBA.balanceOf(address1) expect(preBOBABalance.sub(postBOBABalance)).to.be.eq(0) }) + + it('should watch Teleportation contract via ws', async () => { + const teleportationService = await startTeleportationService() + await teleportationService.init() + + // deposit token + const amount = utils.parseEther('11') + await L2BOBA.approve(Teleportation.address, amount) + + // check events + const latestBlock = await wsProvider.getBlockNumber() + const depositTeleportations = { + Teleportation, + chainId, + wsAvailable: true, + totalDeposits: BigNumber.from('0'), + totalDisbursements: BigNumber.from('0'), + height: latestBlock, + } + + teleportationService._handleWebsocketConnection(depositTeleportations, latestBlock) + + const preBOBABalance = await L2BOBA.balanceOf(address1) + const tx = await Teleportation.connect(signer).teleportAsset( + L2BOBA.address, + amount, + chainId + ) + await tx.wait() + + const delay = ms => new Promise(res => setTimeout(res, ms)); + console.log("Waiting 2 seconds for websocket to catch up..") + await delay(2000) + + // websocket should have disbursed now + const postBOBABalance = await L2BOBA.balanceOf(address1) + expect(preBOBABalance).not.be.eq(postBOBABalance) + expect(preBOBABalance.sub(postBOBABalance)).to.be.eq( + amount + ) + }) }) describe('asset routing', () => { @@ -643,8 +696,9 @@ describe('teleportation', () => { selectedBobaChains = [ { chainId: chainIdBnb, - url: 'http://localhost:8545', - provider: ethers.provider, + url: '', + provider: httpProvider, + wsProvider, testnet: true, name: 'localhost:bnb', teleportationAddress: TeleportationBNB.address, @@ -658,8 +712,9 @@ describe('teleportation', () => { selectedBobaChainsBnb = [ { chainId, - url: 'http://localhost:8545', - provider: ethers.provider, + url: '', + provider: httpProvider, + wsProvider, testnet: true, name: 'localhost', teleportationAddress: Teleportation.address, @@ -678,7 +733,7 @@ describe('teleportation', () => { await teleportationServiceBnb.init() // deposit token - const preBlockNumber = await ethers.provider.getBlockNumber() + const preBlockNumber = await httpProvider.getBlockNumber() await L2BobaOnBobaBnb.connect(signer).approve( TeleportationBNB.address, utils.parseEther('10') @@ -689,7 +744,7 @@ describe('teleportation', () => { chainId // toChainId ) - const blockNumber = await ethers.provider.getBlockNumber() + const blockNumber = await httpProvider.getBlockNumber() const events = await teleportationServiceBnb._getEvents( TeleportationBNB, TeleportationBNB.filters.AssetReceived(), @@ -761,7 +816,7 @@ describe('teleportation', () => { await teleportationService.init() // deposit token - const preBlockNumber = await ethers.provider.getBlockNumber() + const preBlockNumber = await httpProvider.getBlockNumber() await TeleportationBNB.connect(signer).teleportAsset( ethers.constants.AddressZero, // send native BNB utils.parseEther('10'), @@ -769,7 +824,7 @@ describe('teleportation', () => { { value: utils.parseEther('10') } ) - const blockNumber = await ethers.provider.getBlockNumber() + const blockNumber = await httpProvider.getBlockNumber() const events = await teleportationService._getEvents( TeleportationBNB, TeleportationBNB.filters.AssetReceived(), @@ -839,7 +894,7 @@ describe('teleportation', () => { await teleportationService.init() // deposit token - const preBlockNumber = await ethers.provider.getBlockNumber() + const preBlockNumber = await httpProvider.getBlockNumber() await L2BNBOnBobaEth.approve( Teleportation.address, @@ -851,7 +906,7 @@ describe('teleportation', () => { chainIdBnb // toChainId ) - const blockNumber = await ethers.provider.getBlockNumber() + const blockNumber = await httpProvider.getBlockNumber() const events = await teleportationService._getEvents( Teleportation, Teleportation.filters.AssetReceived(), diff --git a/integration-tests/test/eth-l2/shared/env.ts b/integration-tests/test/eth-l2/shared/env.ts index a58e44898d..476c1585d6 100644 --- a/integration-tests/test/eth-l2/shared/env.ts +++ b/integration-tests/test/eth-l2/shared/env.ts @@ -33,7 +33,7 @@ import { getBOBADeployerAddresses, getAABOBADeployerAddresses, envConfig, - BUNDLER_URL, + BUNDLER_URL, l2WsProvider, } from './utils' export interface CrossDomainMessagePair { @@ -72,6 +72,7 @@ export class OptimismEnv { messengerFast: CrossChainMessenger l1Provider: providers.JsonRpcProvider l2Provider: providers.JsonRpcProvider + l2WsProvider: providers.WebSocketProvider verifierProvider: providers.JsonRpcProvider replicaProvider: providers.JsonRpcProvider @@ -94,6 +95,7 @@ export class OptimismEnv { this.l2BobalinkWallet = args.l2BobalinkWallet this.l1Provider = args.l1Provider this.l2Provider = args.l2Provider + this.l2WsProvider = args.l2WsProvider this.verifierProvider = args.verifierProvider this.replicaProvider = args.replicaProvider this.bundlerUrl = args.bundlerUrl @@ -155,6 +157,7 @@ export class OptimismEnv { l2BobalinkWallet, l1Provider, l2Provider, + l2WsProvider, verifierProvider, replicaProvider, l1Bridge, diff --git a/integration-tests/test/eth-l2/shared/utils.ts b/integration-tests/test/eth-l2/shared/utils.ts index dedb6f525e..ce56a61d9f 100644 --- a/integration-tests/test/eth-l2/shared/utils.ts +++ b/integration-tests/test/eth-l2/shared/utils.ts @@ -54,6 +54,7 @@ const env = cleanEnv(process.env, { default: 'onchain', }), L2_URL: str({ default: 'http://localhost:8545' }), + L2_WS_URL: str({ default: 'ws://localhost:8546'}), L2_POLLING_INTERVAL: num({ default: 10 }), L2_WALLET_MIN_BALANCE_ETH: num({ default: 2, @@ -142,6 +143,9 @@ export const l2Provider = asL2Provider( ) l2Provider.pollingInterval = env.L2_POLLING_INTERVAL +// asL2Provider breaks Websocket provider +export const l2WsProvider = new providers.WebSocketProvider(env.L2_WS_URL) + export const replicaProvider = asL2Provider( new providers.JsonRpcProvider(env.REPLICA_URL) ) diff --git a/integration-tests/test/eth-l2/teleportation.spec.ts b/integration-tests/test/eth-l2/teleportation.spec.ts index 0042c147a0..f8e4372c2d 100644 --- a/integration-tests/test/eth-l2/teleportation.spec.ts +++ b/integration-tests/test/eth-l2/teleportation.spec.ts @@ -17,7 +17,7 @@ import TeleportationJson from '@boba/contracts/artifacts/contracts/Teleportation import L1ERC20Json from '@boba/contracts/artifacts/contracts/test-helpers/L1ERC20.sol/L1ERC20.json' /* Imports: Interface */ -import { ChainInfo } from '@boba/teleportation/src/utils/types' +import { ChainInfo, DepositTeleportations} from '@boba/teleportation/src/utils/types' /* Imports: Core */ import { TeleportationService } from '@boba/teleportation/src/service' @@ -26,11 +26,14 @@ import { historyDataRepository, } from '@boba/teleportation/src/data-source' import { OptimismEnv } from './shared/env' +import {JsonRpcProvider, WebSocketProvider} from "@ethersproject/providers"; describe('teleportation', () => { let env: OptimismEnv let signer: Signer let signerAddr: string + let wsProvider: WebSocketProvider + let httpProvider: JsonRpcProvider let wallet1: Wallet let address1: string @@ -49,9 +52,11 @@ describe('teleportation', () => { await AppDataSource.initialize() await AppDataSource.synchronize(true) // drops database and recreates - signer = env.l2Wallet + wsProvider = env.l2WsProvider + httpProvider = env.l2Provider + signer = env.l2Wallet.connect(wsProvider ?? httpProvider) signerAddr = await signer.getAddress() - wallet1 = env.l2Wallet_2 + wallet1 = env.l2Wallet_2.connect(wsProvider ?? httpProvider) address1 = wallet1.address await signer.sendTransaction({ @@ -72,7 +77,7 @@ describe('teleportation', () => { let L2BNBOnBobaEth: Contract before(async () => { - chainId = (await ethers.provider.getNetwork()).chainId + chainId = (await httpProvider.getNetwork()).chainId chainIdBnb = chainId + 1 Factory__Teleportation = new ethers.ContractFactory( @@ -120,8 +125,9 @@ describe('teleportation', () => { selectedBobaChains = [ { chainId, - url: 'http://localhost:8545', - provider: ethers.provider, + url: '', + provider: httpProvider, + wsProvider, testnet: true, name: 'localhost', teleportationAddress: Teleportation.address, @@ -140,7 +146,8 @@ describe('teleportation', () => { const chainIdToUse = useBnb ? chainIdBnb : chainId return new TeleportationService({ - l2RpcProvider: ethers.provider, + l2RpcProvider: httpProvider, + l2WSRpcProvider: wsProvider, chainId: chainIdToUse, teleportationAddress: useBnb ? TeleportationBNB.address @@ -173,7 +180,7 @@ describe('teleportation', () => { const teleportationService = await startTeleportationService() await teleportationService.init() - const blockNumber = await ethers.provider.getBlockNumber() + const blockNumber = await httpProvider.getBlockNumber() const events = await teleportationService._getEvents( Teleportation, @@ -191,7 +198,7 @@ describe('teleportation', () => { chainId ) - const latestBlockNumber = await ethers.provider.getBlockNumber() + const latestBlockNumber = await httpProvider.getBlockNumber() const latestEvents = await teleportationService._getEvents( Teleportation, Teleportation.filters.AssetReceived(), @@ -211,7 +218,7 @@ describe('teleportation', () => { const teleportationService = await startTeleportationService() await teleportationService.init() - const blockNumber = await ethers.provider.getBlockNumber() + const blockNumber = await httpProvider.getBlockNumber() const events = await teleportationService._getEvents( Teleportation, Teleportation.filters.AssetReceived(), @@ -269,7 +276,7 @@ describe('teleportation', () => { const teleportationService = await startTeleportationService() await teleportationService.init() - const blockNumber = await ethers.provider.getBlockNumber() + const blockNumber = await httpProvider.getBlockNumber() const events = await teleportationService._getEvents( Teleportation, Teleportation.filters.AssetReceived(), @@ -315,7 +322,7 @@ describe('teleportation', () => { const teleportationService = await startTeleportationService() await teleportationService.init() - const startBlockNumber = await ethers.provider.getBlockNumber() + const startBlockNumber = await httpProvider.getBlockNumber() // deposit token for (let i = 0; i < 15; i++) { @@ -327,7 +334,7 @@ describe('teleportation', () => { ) } - const endBlockNumber = await ethers.provider.getBlockNumber() + const endBlockNumber = await httpProvider.getBlockNumber() const latestEvents = await teleportationService._getEvents( Teleportation, Teleportation.filters.AssetReceived(), @@ -342,7 +349,7 @@ describe('teleportation', () => { const teleportationService = await startTeleportationService() await teleportationService.init() - const blockNumber = await ethers.provider.getBlockNumber() + const blockNumber = await httpProvider.getBlockNumber() const events = await teleportationService._getEvents( Teleportation, Teleportation.filters.AssetReceived(), @@ -377,13 +384,13 @@ describe('teleportation', () => { const preBOBABalance = await L2BOBA.balanceOf(address1) const preSignerBOBABalance = await L2BOBA.balanceOf(signerAddr) - const preBlockNumber = await ethers.provider.getBlockNumber() + const preBlockNumber = await httpProvider.getBlockNumber() await teleportationService._disburseTx(disbursement, chainId, blockNumber) const postBOBABalance = await L2BOBA.balanceOf(address1) const postSignerBOBABalance = await L2BOBA.balanceOf(signerAddr) - const postBlockNumber = await ethers.provider.getBlockNumber() + const postBlockNumber = await httpProvider.getBlockNumber() expect(preBOBABalance.sub(postBOBABalance)).to.be.eq( utils.parseEther('150') @@ -396,7 +403,8 @@ describe('teleportation', () => { }) describe('global tests', () => { - it('should watch Teleportation contract', async () => { + + it('should watch Teleportation contract via http', async () => { const teleportationService = await startTeleportationService() await teleportationService.init() @@ -409,10 +417,11 @@ describe('teleportation', () => { ) // check events - const latestBlock = await ethers.provider.getBlockNumber() + const latestBlock = await httpProvider.getBlockNumber() const depositTeleportations = { Teleportation, chainId, + wsAvailable: false, totalDeposits: BigNumber.from('0'), totalDisbursements: BigNumber.from('0'), height: 0, @@ -434,10 +443,11 @@ describe('teleportation', () => { const teleportationService = await startTeleportationService() await teleportationService.init() - const latestBlock = await ethers.provider.getBlockNumber() - const depositTeleportations = { + const latestBlock = await httpProvider.getBlockNumber() + const depositTeleportations: DepositTeleportations = { Teleportation, chainId, + wsAvailable: false, totalDeposits: BigNumber.from('0'), totalDisbursements: BigNumber.from('0'), height: 0, @@ -487,10 +497,11 @@ describe('teleportation', () => { } // check events - const latestBlock = await ethers.provider.getBlockNumber() + const latestBlock = await httpProvider.getBlockNumber() const depositTeleportations = { Teleportation, chainId, + wsAvailable: false, totalDeposits: BigNumber.from('0'), totalDisbursements: BigNumber.from('0'), height: 0, @@ -506,10 +517,11 @@ describe('teleportation', () => { const teleportationService = await startTeleportationService() await teleportationService.init() - const latestBlock = await ethers.provider.getBlockNumber() + const latestBlock = await httpProvider.getBlockNumber() const depositTeleportations = { Teleportation, chainId, + wsAvailable: false, totalDeposits: BigNumber.from('0'), totalDisbursements: BigNumber.from('0'), height: 0, @@ -550,10 +562,11 @@ describe('teleportation', () => { await historyDataRepository.delete({ chainId }) - const latestBlock = await ethers.provider.getBlockNumber() + const latestBlock = await httpProvider.getBlockNumber() const depositTeleportations = { Teleportation, chainId, + wsAvailable: false, totalDeposits: BigNumber.from('0'), totalDisbursements: BigNumber.from('0'), height: 0, @@ -572,6 +585,47 @@ describe('teleportation', () => { const postBOBABalance = await L2BOBA.balanceOf(address1) expect(preBOBABalance.sub(postBOBABalance)).to.be.eq(0) }) + + it('should watch Teleportation contract via ws', async () => { + const teleportationService = await startTeleportationService() + await teleportationService.init() + + // deposit token + const amount = utils.parseEther('11') + await L2BOBA.approve(Teleportation.address, amount) + + // check events + const latestBlock = await wsProvider.getBlockNumber() + const depositTeleportations = { + Teleportation, + chainId, + wsAvailable: true, + totalDeposits: BigNumber.from('0'), + totalDisbursements: BigNumber.from('0'), + height: latestBlock, + } + + teleportationService._handleWebsocketConnection(depositTeleportations, latestBlock) + + const preBOBABalance = await L2BOBA.balanceOf(address1) + const tx = await Teleportation.connect(signer).teleportAsset( + L2BOBA.address, + amount, + chainId + ) + await tx.wait() + + const delay = ms => new Promise(res => setTimeout(res, ms)); + console.log("Waiting 2 seconds for websocket to catch up..") + await delay(2000) + + // websocket should have disbursed now + const postBOBABalance = await L2BOBA.balanceOf(address1) + expect(preBOBABalance).not.be.eq(postBOBABalance) + expect(preBOBABalance.sub(postBOBABalance)).to.be.eq( + amount + ) + }) }) describe('asset routing', () => { @@ -642,8 +696,9 @@ describe('teleportation', () => { selectedBobaChains = [ { chainId: chainIdBnb, - url: 'http://localhost:8545', - provider: ethers.provider, + url: '', + provider: httpProvider, + wsProvider, testnet: true, name: 'localhost:bnb', teleportationAddress: TeleportationBNB.address, @@ -657,8 +712,9 @@ describe('teleportation', () => { selectedBobaChainsBnb = [ { chainId, - url: 'http://localhost:8545', - provider: ethers.provider, + url: '', + provider: httpProvider, + wsProvider, testnet: true, name: 'localhost', teleportationAddress: Teleportation.address, @@ -677,7 +733,7 @@ describe('teleportation', () => { await teleportationServiceBnb.init() // deposit token - const preBlockNumber = await ethers.provider.getBlockNumber() + const preBlockNumber = await httpProvider.getBlockNumber() await L2BobaOnBobaBnb.connect(signer).approve( TeleportationBNB.address, utils.parseEther('10') @@ -688,7 +744,7 @@ describe('teleportation', () => { chainId // toChainId ) - const blockNumber = await ethers.provider.getBlockNumber() + const blockNumber = await httpProvider.getBlockNumber() const events = await teleportationServiceBnb._getEvents( TeleportationBNB, TeleportationBNB.filters.AssetReceived(), @@ -760,7 +816,7 @@ describe('teleportation', () => { await teleportationService.init() // deposit token - const preBlockNumber = await ethers.provider.getBlockNumber() + const preBlockNumber = await httpProvider.getBlockNumber() await TeleportationBNB.connect(signer).teleportAsset( ethers.constants.AddressZero, // send native BNB utils.parseEther('10'), @@ -768,7 +824,7 @@ describe('teleportation', () => { { value: utils.parseEther('10') } ) - const blockNumber = await ethers.provider.getBlockNumber() + const blockNumber = await httpProvider.getBlockNumber() const events = await teleportationService._getEvents( TeleportationBNB, TeleportationBNB.filters.AssetReceived(), @@ -838,7 +894,7 @@ describe('teleportation', () => { await teleportationService.init() // deposit token - const preBlockNumber = await ethers.provider.getBlockNumber() + const preBlockNumber = await httpProvider.getBlockNumber() await L2BNBOnBobaEth.approve( Teleportation.address, @@ -850,7 +906,7 @@ describe('teleportation', () => { chainIdBnb // toChainId ) - const blockNumber = await ethers.provider.getBlockNumber() + const blockNumber = await httpProvider.getBlockNumber() const events = await teleportationService._getEvents( Teleportation, Teleportation.filters.AssetReceived(), diff --git a/ops/docker-compose-avalanche.yml b/ops/docker-compose-avalanche.yml index c46b31ec9b..c51e2f22be 100644 --- a/ops/docker-compose-avalanche.yml +++ b/ops/docker-compose-avalanche.yml @@ -359,6 +359,7 @@ services: REPLICA_URL: http://replica:8545 L1_URL: http://l1_chain:9650/ext/bc/C/rpc L2_URL: http://l2geth:8545 + L2_WS_URL: ws://l2geth:8546 URL: http://dtl:8081/addresses.json BASE_URL: http://dtl:8081/addresses.json BOBA_URL: http://dtl:8081/boba-addr.json diff --git a/ops/docker-compose-bnb.yml b/ops/docker-compose-bnb.yml index 885c39b362..939ef47e7f 100644 --- a/ops/docker-compose-bnb.yml +++ b/ops/docker-compose-bnb.yml @@ -329,6 +329,7 @@ services: REPLICA_URL: http://replica:8545 L1_URL: http://l1_chain:8545 L2_URL: http://l2geth:8545 + L2_WS_URL: ws://l2geth:8546 URL: http://dtl:8081/addresses.json BASE_URL: http://dtl:8081/addresses.json BOBA_URL: http://dtl:8081/boba-addr.json diff --git a/ops/docker-compose-fantom.yml b/ops/docker-compose-fantom.yml index 28d8bd2dc9..f3927019dc 100644 --- a/ops/docker-compose-fantom.yml +++ b/ops/docker-compose-fantom.yml @@ -300,6 +300,7 @@ services: REPLICA_URL: http://replica:8545 L1_URL: http://l1_chain:18545 L2_URL: http://l2geth:8545 + L2_WS_URL: ws://l2geth:8546 URL: http://dtl:8081/addresses.json BASE_URL: http://dtl:8081/addresses.json BOBA_URL: http://dtl:8081/boba-addr.json diff --git a/ops/docker-compose-moonbeam.yml b/ops/docker-compose-moonbeam.yml index 1ff527807f..ea5afaf328 100644 --- a/ops/docker-compose-moonbeam.yml +++ b/ops/docker-compose-moonbeam.yml @@ -311,7 +311,8 @@ services: VERIFIER_URL: http://verifier:8545 REPLICA_URL: http://replica:8545 L1_URL: http://l1_chain:9933 - L2_URL: http://l2geth:8545 + L2_URL: ws://l2geth:8545 + L2_WS_URL: http://l2geth:8546 URL: http://dtl:8081/addresses.json BASE_URL: http://dtl:8081/addresses.json BOBA_URL: http://dtl:8081/boba-addr.json diff --git a/ops/docker-compose.yml b/ops/docker-compose.yml index 3138c279c3..6d31b79490 100644 --- a/ops/docker-compose.yml +++ b/ops/docker-compose.yml @@ -324,6 +324,7 @@ services: REPLICA_URL: http://replica:8545 L1_URL: http://l1_chain:8545 L2_URL: http://l2geth:8545 + L2_WS_URL: ws://l2geth:8546 URL: http://dtl:8081/addresses.json BASE_URL: http://dtl:8081/addresses.json BOBA_URL: http://dtl:8081/boba-addr.json diff --git a/packages/boba/teleportation/.env.example b/packages/boba/teleportation/.env.example index 59074e7272..adf9079be9 100644 --- a/packages/boba/teleportation/.env.example +++ b/packages/boba/teleportation/.env.example @@ -1,4 +1,5 @@ L2_NODE_WEB3_URL= +L2_WS_NODE_WEB3_URL= TELEPORTATION_POSTGRES_PASSWORD=abcdef TELEPORTATION_POSTGRES_DB_HOST=teleportation_db TELEPORTATION_POSTGRES_DB=postgres diff --git a/packages/boba/teleportation/src/exec/run.ts b/packages/boba/teleportation/src/exec/run.ts index 4fde48be34..6940dfc9f7 100644 --- a/packages/boba/teleportation/src/exec/run.ts +++ b/packages/boba/teleportation/src/exec/run.ts @@ -7,7 +7,7 @@ import Config from 'bcfg' import { TeleportationService } from '../service' /* Imports: Config */ -import { BobaChains } from '../utils/chains' +import {BobaChains, IBobaChain} from '../utils/chains' /* Imports: Interface */ import { ChainInfo, SupportedAssets } from '../utils/types' @@ -38,6 +38,7 @@ const main = async () => { const env = process.env const L2_NODE_WEB3_URL = config.str('l2-node-web3-url', env.L2_NODE_WEB3_URL) + const L2_WS_NODE_WEB3_URL: string | undefined = config.str('l2-ws-node-web3-url', env.L2_WS_NODE_WEB3_URL) // This private key is used to send funds to the contract and initiate the tx, // so it should have enough BOBA balance const TELEPORTATION_AWS_KMS_ACCESS_KEY = config.str( @@ -85,6 +86,10 @@ const main = async () => { } const l2Provider = new providers.StaticJsonRpcProvider(L2_NODE_WEB3_URL) + const l2WSRpcProvider = L2_WS_NODE_WEB3_URL ? new providers.WebSocketProvider(L2_WS_NODE_WEB3_URL) : null; + if (L2_WS_NODE_WEB3_URL) { + console.log('Websocket node url provided, using websocket instead of http provider', L2_WS_NODE_WEB3_URL) + } // get all boba chains and exclude the current chain const chainId = (await l2Provider.getNetwork()).chainId @@ -92,10 +97,14 @@ const main = async () => { let originSupportedAssets: SupportedAssets const selectedBobaChains: ChainInfo[] = Object.keys(BobaChains).reduce( (acc, cur) => { - const chain = BobaChains[cur] + const chain: ChainInfo = BobaChains[cur] if (isTestnet === chain.testnet) { if (Number(cur) !== chainId) { + chain.provider = new providers.StaticJsonRpcProvider(chain.url) + if (chain.wsUrl) { + chain.wsProvider = new providers.WebSocketProvider(chain.wsUrl) + } acc.push({ chainId: cur, ...chain }) } else { originSupportedAssets = chain.supportedAssets @@ -109,6 +118,7 @@ const main = async () => { const service = new TeleportationService({ l2RpcProvider: l2Provider, + l2WSRpcProvider: l2WSRpcProvider, chainId, teleportationAddress: TELEPORTATION_ADDRESS, selectedBobaChains, diff --git a/packages/boba/teleportation/src/service.ts b/packages/boba/teleportation/src/service.ts index 4eee8856ed..5707e6db6c 100644 --- a/packages/boba/teleportation/src/service.ts +++ b/packages/boba/teleportation/src/service.ts @@ -4,17 +4,17 @@ import { constants as ethersConstants, Contract, EventFilter, - providers, + providers, Signer, Wallet, } from 'ethers' -import { orderBy } from 'lodash' +import {orderBy} from 'lodash' import 'reflect-metadata' /* Imports: Internal */ -import { sleep } from '@eth-optimism/core-utils' -import { BaseService } from '@eth-optimism/common-ts' -import { getContractFactory } from '@eth-optimism/contracts' -import { getBobaContractAt } from '@boba/contracts' +import {sleep} from '@eth-optimism/core-utils' +import {BaseService} from '@eth-optimism/common-ts' +import {getContractFactory} from '@eth-optimism/contracts' +import {getBobaContractAt} from '@boba/contracts' /* Imports: Interface */ import { @@ -24,12 +24,14 @@ import { Disbursement, SupportedAssets, } from './utils/types' -import { HistoryData } from './entities/HistoryData.entity' -import { historyDataRepository } from './data-source' -import { IKMSSignerConfig, KMSSigner } from './utils/kms-signing' +import {HistoryData} from './entities/HistoryData.entity' +import {historyDataRepository} from './data-source' +import {IKMSSignerConfig, KMSSigner} from './utils/kms-signing' +import {IBobaChain} from "./utils/chains"; interface TeleportationOptions { l2RpcProvider: providers.StaticJsonRpcProvider + l2WSRpcProvider?: providers.WebSocketProvider // chainId of the L2 network chainId: number @@ -52,6 +54,9 @@ interface TeleportationOptions { const optionSettings = {} export class TeleportationService extends BaseService { + private readonly EXPECTED_PONG_BACK = 15000 + private readonly KEEP_ALIVE_CHECK_INTERVAL = 7500 + constructor(options: TeleportationOptions) { super('Teleportation', options, optionSettings) } @@ -63,7 +68,11 @@ export class TeleportationService extends BaseService { // the contract of the chain that users deposit token depositTeleportations: DepositTeleportations[] // AWS KMS Signer for disburser key, .. - KMSSigner: KMSSigner + KMSSigner: KMSSigner, + // ws connection ping timeout + wsPingTimeout: { number: NodeJS.Timeout }, + // ws connection keep alive interval + wsKeepAliveInterval: { number: NodeJS.Timer }, } = {} as any protected async _init(): Promise { @@ -78,7 +87,7 @@ export class TeleportationService extends BaseService { this.state.Teleportation = await getBobaContractAt( 'Teleportation', this.options.teleportationAddress, - this.options.l2RpcProvider + this.options.l2WSRpcProvider ?? this.options.l2RpcProvider ) this.logger.info('Connected to Teleportation', { @@ -105,6 +114,7 @@ export class TeleportationService extends BaseService { const bobaTokenContract = Object.keys(chain.supportedAssets).find( (k) => chain.supportedAssets[k] === 'BOBA' ) + const isSupported = await this.state.Teleportation.supportedTokens( bobaTokenContract, chainId @@ -122,7 +132,7 @@ export class TeleportationService extends BaseService { const depositTeleportation = await getBobaContractAt( 'Teleportation', chain.teleportationAddress, - chain.provider + chain.wsProvider ?? chain.provider ) const totalDisbursements = await this.state.Teleportation.totalDisbursements(chainId) @@ -132,6 +142,7 @@ export class TeleportationService extends BaseService { this.state.depositTeleportations.push({ Teleportation: depositTeleportation, + wsAvailable: !!chain.wsProvider, chainId, totalDisbursements, totalDeposits, @@ -143,8 +154,38 @@ export class TeleportationService extends BaseService { } protected async _start(): Promise { + + const wsDepositTeleportation = this.state.depositTeleportations.filter(t => t.wsAvailable) + const noWsDepositTeleportation = this.state.depositTeleportations.filter(t => !t.wsAvailable) + + // Websocket providers + for (const depositTeleportation of wsDepositTeleportation) { + + const latestBlock = + await depositTeleportation.Teleportation.provider.getBlockNumber() + + try { + // load events for blocks before currentBlock + const events: AssetReceivedEvent[] = await this._watchTeleportation( + depositTeleportation, + latestBlock + ) + await this._disburseTeleportation( + depositTeleportation, + events, + latestBlock + ) + } catch (err) { + this.logger.error('Error while running teleportation (ws)', { + err, + }) + } + + this._handleWebsocketConnection(depositTeleportation, latestBlock) + } + while (this.running) { - for (const depositTeleportation of this.state.depositTeleportations) { + for (const depositTeleportation of noWsDepositTeleportation) { // search AssetReceived events const latestBlock = await depositTeleportation.Teleportation.provider.getBlockNumber() @@ -168,6 +209,50 @@ export class TeleportationService extends BaseService { } } + _handleWebsocketConnection(depositTeleportation: DepositTeleportations, latestBlock: number) { + const provider: providers.WebSocketProvider = depositTeleportation.Teleportation.provider as providers.WebSocketProvider + const chainId = depositTeleportation.chainId + + // also returns all parameters of the event, which we don't need here (last one is the event itself) + depositTeleportation.Teleportation.on(this.state.Teleportation.filters.AssetReceived(), async (_, _1, _2, _3, _4, _5, event) => { + console.log("Received new event via websocket..") + await this._disburseTeleportation( + depositTeleportation, + [event], + latestBlock + ) + }) + + provider._websocket.on('open', () => { + this.state.wsKeepAliveInterval[chainId] = setInterval(() => { + console.log('Checking if the connection is alive, sending a ping for chainId', chainId) + + provider._websocket.ping() + + // Use `WebSocket#terminate()`, which immediately destroys the connection, + // instead of `WebSocket#close()`, which waits for the close timer. + // Delay should be equal to the interval at which your server + // sends out pings plus a conservative assumption of the latency. + this.state.wsPingTimeout[chainId] = setTimeout(() => { + provider._websocket.terminate() + }, this.EXPECTED_PONG_BACK) + }, this.KEEP_ALIVE_CHECK_INTERVAL) + }) + + provider._websocket.on('close', () => { + console.error('The websocket connection was closed for chainId', chainId) + clearInterval(this.state.wsKeepAliveInterval[chainId]) + clearTimeout(this.state.wsPingTimeout[chainId]) + this._handleWebsocketConnection(depositTeleportation, latestBlock) + }) + + provider._websocket.on('pong', () => { + console.log('Received pong, so connection is alive, clearing the timeout for chainId ', chainId) + clearInterval(this.state.wsPingTimeout[chainId]) + }) + console.log("Websocket started..") + } + async _watchTeleportation( depositTeleportation: DepositTeleportations, latestBlock: number @@ -182,6 +267,7 @@ export class TeleportationService extends BaseService { // store the new deposit info await this._putDepositInfo(chainId, lastBlock) } + return this._getEvents( depositTeleportation.Teleportation, this.state.Teleportation.filters.AssetReceived(), @@ -213,7 +299,6 @@ export class TeleportationService extends BaseService { const amount = event.args.amount const sourceChainTokenAddr = event.args.token const emitter = event.args.emitter - const destChainId = event.args.toChainId // we disburse tokens only if depositId is greater or equal to the last disbursement if (depositId.gte(lastDisbursement)) { @@ -312,7 +397,7 @@ export class TeleportationService extends BaseService { const disburseTxUnsigned = await this.state.Teleportation.populateTransaction.disburseAsset( slicedDisbursement, - { value: nativeValue } + {value: nativeValue} ) const disburseTx = await this.state.KMSSigner.sendTxViaKMS( this.state.Teleportation.provider, @@ -345,11 +430,13 @@ export class TeleportationService extends BaseService { ): Promise { let events = [] let startBlock = fromBlock + while (startBlock < toBlock) { const endBlock = Math.min( startBlock + this.options.blockRangePerPolling, toBlock ) + const partialEvents = await contract.queryFilter( event, startBlock, @@ -405,10 +492,10 @@ export class TeleportationService extends BaseService { historyData.chainId = chainId historyData.blockNo = latestBlock if ( - await historyDataRepository.findOneBy({ chainId: historyData.chainId }) + await historyDataRepository.findOneBy({chainId: historyData.chainId}) ) { await historyDataRepository.update( - { chainId: historyData.chainId }, + {chainId: historyData.chainId}, historyData ) } else { diff --git a/packages/boba/teleportation/src/utils/chains.ts b/packages/boba/teleportation/src/utils/chains.ts index ca979bcf9f..03c0a2b0a1 100644 --- a/packages/boba/teleportation/src/utils/chains.ts +++ b/packages/boba/teleportation/src/utils/chains.ts @@ -1,16 +1,20 @@ -export interface IBobaChains { - [chainId: number]: { - url: string - testnet: boolean - name: string - teleportationAddress: string - height: number - supportedAssets: { - [address: string]: string // symbol (MUST BE UNIQUE) - } +export interface IBobaChain { + url: string + /** @dev If defined teleportation service will use websocket instead of regular httpProvider */ + wsUrl?: string + testnet: boolean + name: string + teleportationAddress: string + height: number + supportedAssets: { + [address: string]: string // symbol (MUST BE UNIQUE) } } +export interface IBobaChains { + [chainId: number]: IBobaChain +} + /** * @dev Chain configs * @property supportedAssets: BOBA as fee token only supported for EOAs, since Teleporter consists of a contract & the disburser wallet (assuming ETH fee) everything with 0x0 should be fine. @@ -21,6 +25,7 @@ export const BobaChains: IBobaChains = { //#region boba_networks 288: { url: 'https://replica.boba.network', + // wsUrl: 'wss://boba-ethereum.gateway.tenderly.co', testnet: false, name: 'Boba Ethereum Mainnet', teleportationAddress: '0xd68809330075C792C171C450B983F4D18128e9BF', @@ -34,6 +39,7 @@ export const BobaChains: IBobaChains = { }, 43288: { url: 'https://replica.avax.boba.network', + // wsUrl: 'wss://wss.avax.boba.network', testnet: false, name: 'Boba Avalanche Mainnet', teleportationAddress: '0xd68809330075C792C171C450B983F4D18128e9BF', @@ -45,6 +51,7 @@ export const BobaChains: IBobaChains = { }, 56288: { url: 'https://replica.bnb.boba.network', + // wsUrl: 'wss://boba-bnb.gateway.tenderly.co', testnet: false, name: 'Boba BNB Mainnet', teleportationAddress: '0xd68809330075C792C171C450B983F4D18128e9BF', @@ -56,6 +63,7 @@ export const BobaChains: IBobaChains = { }, 2888: { url: 'https://replica.goerli.boba.network', + // wsUrl: 'wss://wss.goerli.boba.network', testnet: true, name: 'Boba Ethereum Goerli', teleportationAddress: '0x64bD91c67af8cd17e04BeBDaac675f0EF6527edd', @@ -67,6 +75,7 @@ export const BobaChains: IBobaChains = { }, 4328: { url: 'https://replica.testnet.avax.boba.network', + // wsUrl: 'wss://wss.testnet.avax.boba.network', testnet: true, name: 'Boba Avalanche Testnet', teleportationAddress: '0xC226F132A686A08018431C913d87693396246024', @@ -78,6 +87,7 @@ export const BobaChains: IBobaChains = { }, 9728: { url: 'https://replica.testnet.bnb.boba.network', + // wsUrl: 'wss://wss.testnet.bnb.boba.network', testnet: true, name: 'Boba BNB Testnet', teleportationAddress: '0xC226F132A686A08018431C913d87693396246024', diff --git a/packages/boba/teleportation/src/utils/types.ts b/packages/boba/teleportation/src/utils/types.ts index 0b276ce0d3..cc29b90b4d 100644 --- a/packages/boba/teleportation/src/utils/types.ts +++ b/packages/boba/teleportation/src/utils/types.ts @@ -1,4 +1,5 @@ import { BigNumber, Contract, providers } from 'ethers' +import {IBobaChain} from "./chains"; export interface SupportedAssets { [address: string]: string // symbol (MUST BE UNIQUE) @@ -15,19 +16,15 @@ export interface AssetReceivedEvent { } } -export interface ChainInfo { +export interface ChainInfo extends IBobaChain { chainId: number - url: string provider: providers.StaticJsonRpcProvider - testnet: boolean - name: string - teleportationAddress: string - height: number - supportedAssets: SupportedAssets + wsProvider?: providers.WebSocketProvider } export interface DepositTeleportations { Teleportation: Contract + wsAvailable: boolean chainId: number totalDeposits: BigNumber totalDisbursements: BigNumber