From f269c6ed0f6c3f9efc12697de5b9d765c5083e77 Mon Sep 17 00:00:00 2001 From: Jake Kidd Date: Fri, 26 Mar 2021 00:57:04 -0700 Subject: [PATCH 01/11] refactor : implement fallback provider wrapper and replace vector implementation --- modules/contracts/src.ts/constants.ts | 12 +++-- .../contracts/src.ts/services/ethReader.ts | 8 +-- .../contracts/src.ts/services/ethService.ts | 7 +-- modules/iframe-app/src/ConnextManager.tsx | 4 +- modules/protocol/src/testing/constants.ts | 4 +- modules/router/src/config.ts | 2 +- modules/router/src/index.ts | 6 +-- modules/router/src/metrics.ts | 3 +- modules/router/src/test/autoRebalance.spec.ts | 9 ++-- modules/router/src/test/utils/mocks.ts | 4 +- modules/server-node/src/index.ts | 4 +- modules/types/src/network.ts | 50 +++++++++++++++++-- modules/utils/src/eth.ts | 16 +++++- 13 files changed, 95 insertions(+), 34 deletions(-) diff --git a/modules/contracts/src.ts/constants.ts b/modules/contracts/src.ts/constants.ts index e48d991cd..c17a23b0b 100644 --- a/modules/contracts/src.ts/constants.ts +++ b/modules/contracts/src.ts/constants.ts @@ -1,13 +1,15 @@ import { HDNode } from "@ethersproject/hdnode"; import { Wallet } from "@ethersproject/wallet"; import { JsonRpcProvider } from "@ethersproject/providers"; -import { network, ethers }from "hardhat"; +import { ChainProvider } from "@connext/vector-types"; +import { network, ethers } from "hardhat"; import pino from "pino"; // Get defaults from env const chainProviders = JSON.parse(process.env.CHAIN_PROVIDERS ?? "{}"); + const chainId = Object.keys(chainProviders)[0]; -const url = Object.values(chainProviders)[0]; +const urls = Object.values(chainProviders)[0]; const mnemonic = process.env.SUGAR_DADDY ?? "candy maple cake sugar pudding cream honey rich smooth crumble sweet treat"; export const defaultLogLevel = process.env.LOG_LEVEL || "info"; @@ -15,9 +17,9 @@ export const logger = pino({ level: defaultLogLevel }); export const networkName = network.name; -export const provider = url - ? new JsonRpcProvider(url as string, parseInt(chainId)) - : ethers.provider as JsonRpcProvider; +export const provider = urls + ? new ChainProvider(parseInt(chainId), (urls as string).split(",")) + : new ChainProvider(parseInt(chainId), [ethers.provider as JsonRpcProvider]); const hdNode = HDNode.fromMnemonic(mnemonic).derivePath("m/44'/60'/0'/0"); diff --git a/modules/contracts/src.ts/services/ethReader.ts b/modules/contracts/src.ts/services/ethReader.ts index cf5f1531f..125a87955 100644 --- a/modules/contracts/src.ts/services/ethReader.ts +++ b/modules/contracts/src.ts/services/ethReader.ts @@ -26,6 +26,7 @@ import { CoreChannelState, CoreTransferState, TransferDispute, + ChainProvider, } from "@connext/vector-types"; import axios from "axios"; import { encodeBalance, encodeTransferResolver, encodeTransferState } from "@connext/vector-utils"; @@ -33,7 +34,7 @@ import { BigNumber } from "@ethersproject/bignumber"; import { parseUnits } from "@ethersproject/units"; import { AddressZero, HashZero } from "@ethersproject/constants"; import { Contract } from "@ethersproject/contracts"; -import { JsonRpcProvider, TransactionRequest } from "@ethersproject/providers"; +import { TransactionRequest } from "@ethersproject/providers"; import pino from "pino"; import { ChannelFactory, ChannelMastercopy, TransferDefinition, TransferRegistry, VectorChannel } from "../artifacts"; @@ -59,14 +60,15 @@ export class EthereumChainReader implements IVectorChainReader { }; private contracts: Map = new Map(); constructor( - public readonly chainProviders: { [chainId: string]: JsonRpcProvider }, + // The chainProviders specified here are hydrated. + public readonly chainProviders: { [chainId: string]: ChainProvider }, public readonly log: pino.BaseLogger, ) {} getChainProviders(): Result { const ret: ChainProviders = {}; Object.entries(this.chainProviders).forEach(([name, value]) => { - ret[parseInt(name)] = value.connection.url; + ret[parseInt(name)] = value.providerUrls; }); return Result.ok(ret); } diff --git a/modules/contracts/src.ts/services/ethService.ts b/modules/contracts/src.ts/services/ethService.ts index 32e1a5f3e..d5aad039a 100644 --- a/modules/contracts/src.ts/services/ethService.ts +++ b/modules/contracts/src.ts/services/ethService.ts @@ -17,6 +17,7 @@ import { StringifiedTransactionResponse, getConfirmationsForChain, StoredTransaction, + ChainProvider, } from "@connext/vector-types"; import { delay, @@ -29,7 +30,7 @@ import { import { Signer } from "@ethersproject/abstract-signer"; import { BigNumber } from "@ethersproject/bignumber"; import { Contract } from "@ethersproject/contracts"; -import { JsonRpcProvider, TransactionReceipt, TransactionResponse } from "@ethersproject/providers"; +import { TransactionReceipt, TransactionResponse } from "@ethersproject/providers"; import { Wallet } from "@ethersproject/wallet"; import { BaseLogger } from "pino"; import PriorityQueue from "p-queue"; @@ -54,7 +55,7 @@ export const BIG_GAS_PRICE = parseUnits("1500", "gwei"); // TODO: Deprecate. Note that this is used in autoRebalance.ts. export const waitForTransaction = async ( - provider: JsonRpcProvider, + provider: ChainProvider, transactionHash: string, confirmations?: number, timeout?: number, @@ -84,7 +85,7 @@ export class EthereumChainService extends EthereumChainReader implements IVector }; constructor( private readonly store: IChainServiceStore, - chainProviders: { [chainId: string]: JsonRpcProvider }, + chainProviders: { [chainId: string]: ChainProvider }, signer: string | Signer, log: BaseLogger, private readonly defaultRetries = 3, diff --git a/modules/iframe-app/src/ConnextManager.tsx b/modules/iframe-app/src/ConnextManager.tsx index 2a38d11c7..1c0c08f05 100644 --- a/modules/iframe-app/src/ConnextManager.tsx +++ b/modules/iframe-app/src/ConnextManager.tsx @@ -6,7 +6,7 @@ import { EngineParams, jsonifyError, } from "@connext/vector-types"; -import { ChannelSigner, constructRpcRequest, safeJsonParse } from "@connext/vector-utils"; +import { ChannelSigner, constructRpcRequest, parseProviders, safeJsonParse } from "@connext/vector-utils"; import { entropyToMnemonic } from "@ethersproject/hdnode"; import { keccak256 } from "@ethersproject/keccak256"; import { toUtf8Bytes } from "@ethersproject/strings"; @@ -89,7 +89,7 @@ export default class ConnextManager { this.browserNode = await BrowserNode.connect({ signer, chainAddresses: chainAddresses ?? config.chainAddresses, - chainProviders, + chainProviders: parseProviders(chainProviders), logger: pino(), messagingUrl: _messagingUrl, authUrl: _authUrl, diff --git a/modules/protocol/src/testing/constants.ts b/modules/protocol/src/testing/constants.ts index 26bddb49c..622332e3a 100644 --- a/modules/protocol/src/testing/constants.ts +++ b/modules/protocol/src/testing/constants.ts @@ -1,11 +1,11 @@ -import { JsonRpcProvider } from "@ethersproject/providers"; +import { ChainProvider } from "@connext/vector-types"; import { Wallet } from "@ethersproject/wallet"; import { env } from "./env"; export const chainId = parseInt(Object.keys(env.chainProviders)[0]); export const tokenAddress = env.chainAddresses[chainId]?.testTokenAddress ?? ""; -export const provider = new JsonRpcProvider(env.chainProviders[chainId], chainId); +export const provider = new ChainProvider(chainId, env.chainProviders[chainId]); export const sugarDaddy = Wallet.fromMnemonic(env.sugarDaddyMnemonic).connect(provider); export const rando = Wallet.createRandom().connect(provider); diff --git a/modules/router/src/config.ts b/modules/router/src/config.ts index 4470de1dd..e1efa14dd 100644 --- a/modules/router/src/config.ts +++ b/modules/router/src/config.ts @@ -20,7 +20,7 @@ export type RebalanceProfile = Static; const VectorRouterConfigSchema = Type.Object({ adminToken: Type.String(), allowedSwaps: Type.Array(AllowedSwapSchema), - chainProviders: Type.Dict(TUrl), + chainProviders: Type.Dict(Type.String()), dbUrl: Type.Optional(TUrl), nodeUrl: TUrl, routerUrl: TUrl, diff --git a/modules/router/src/index.ts b/modules/router/src/index.ts index 1b8de863d..c0fbf915a 100644 --- a/modules/router/src/index.ts +++ b/modules/router/src/index.ts @@ -4,7 +4,7 @@ import fastify from "fastify"; import pino from "pino"; import { Evt } from "evt"; import { VectorChainReader } from "@connext/vector-contracts"; -import { EventCallbackConfig, hydrateProviders, RestServerNodeService, ChannelSigner } from "@connext/vector-utils"; +import { EventCallbackConfig, hydrateProviders, parseProviders, RestServerNodeService, ChannelSigner } from "@connext/vector-utils"; import { IsAlivePayload, ConditionalTransferCreatedPayload, @@ -149,8 +149,8 @@ const server = fastify({ collectDefaultMetrics({ prefix: "router_" }); let router: IRouter; -const store = new PrismaStore(); -const hydratedProviders = hydrateProviders(config.chainProviders); +const store = new PrismaStore() +const hydratedProviders = hydrateProviders(parseProviders(config.chainProviders)); const chainService = new VectorChainReader(hydratedProviders, logger.child({ module: "RouterChainReader" })); const messagingService = new NatsRouterMessagingService({ signer, diff --git a/modules/router/src/metrics.ts b/modules/router/src/metrics.ts index ac91ba473..22759a6e2 100644 --- a/modules/router/src/metrics.ts +++ b/modules/router/src/metrics.ts @@ -13,6 +13,7 @@ import { getMainnetEquivalent, getExchangeRateInEth, calculateExchangeWad, + parseProviders, } from "@connext/vector-utils"; import { BigNumber, BigNumberish } from "@ethersproject/bignumber"; import { AddressZero } from "@ethersproject/constants"; @@ -35,7 +36,7 @@ const config = getConfig(); ///// Helpers/Utils export const wallet = Wallet.fromMnemonic(config.mnemonic); export const signerAddress = wallet.address; -export const hydrated: HydratedProviders = hydrateProviders(config.chainProviders); +export const hydrated: HydratedProviders = hydrateProviders(parseProviders(config.chainProviders)); export const rebalancedTokens: { [chainId: string]: { [assetId: string]: { diff --git a/modules/router/src/test/autoRebalance.spec.ts b/modules/router/src/test/autoRebalance.spec.ts index 6cfc17d6a..f7cf8c62d 100644 --- a/modules/router/src/test/autoRebalance.spec.ts +++ b/modules/router/src/test/autoRebalance.spec.ts @@ -1,9 +1,8 @@ import { VectorChainReader } from "@connext/vector-contracts"; import { expect, getRandomBytes32, getTestLoggers, mkAddress, mkBytes32 } from "@connext/vector-utils"; import Sinon from "sinon"; -import { AllowedSwap, Result } from "@connext/vector-types"; +import { AllowedSwap, ChainProvider, Result } from "@connext/vector-types"; import { Wallet } from "@ethersproject/wallet"; -import { JsonRpcProvider } from "@ethersproject/providers"; import { BigNumber } from "@ethersproject/bignumber"; import { parseEther } from "@ethersproject/units"; import axios from "axios"; @@ -112,7 +111,7 @@ describe(testName, () => { describe("rebalanceIfNeeded", () => { let wallet: Sinon.SinonStubbedInstance; let chainService: Sinon.SinonStubbedInstance; - let hydratedProviders: { [chainId: number]: Sinon.SinonStubbedInstance }; + let hydratedProviders: { [chainId: number]: Sinon.SinonStubbedInstance }; let mockAxios: Sinon.SinonStubbedInstance; let mockConfirmation: Sinon.SinonStubbedInstance; let store: Sinon.SinonStubbedInstance; @@ -132,8 +131,8 @@ describe(testName, () => { chainService = Sinon.createStubInstance(VectorChainReader); hydratedProviders = { - 1337: Sinon.createStubInstance(JsonRpcProvider), - 1338: Sinon.createStubInstance(JsonRpcProvider), + 1337: Sinon.createStubInstance(ChainProvider), + 1338: Sinon.createStubInstance(ChainProvider), }; const parseBalanceStub = Sinon.stub(metrics, "getDecimals").resolves(18); hydratedProviders[1337].getGasPrice.resolves(BigNumber.from(138)); diff --git a/modules/router/src/test/utils/mocks.ts b/modules/router/src/test/utils/mocks.ts index d6963ac25..1d6c0e090 100644 --- a/modules/router/src/test/utils/mocks.ts +++ b/modules/router/src/test/utils/mocks.ts @@ -1,7 +1,7 @@ -import { JsonRpcProvider } from "@ethersproject/providers"; +import { ChainProvider } from "@connext/vector-types"; import { createStubInstance } from "sinon"; -export const mockProvider = createStubInstance(JsonRpcProvider, { +export const mockProvider = createStubInstance(ChainProvider, { waitForTransaction: Promise.resolve({ logs: [] } as any), getNetwork: Promise.resolve({ chainId: 1337, name: "" }), }); diff --git a/modules/server-node/src/index.ts b/modules/server-node/src/index.ts index 7460d032c..939f2b79f 100644 --- a/modules/server-node/src/index.ts +++ b/modules/server-node/src/index.ts @@ -19,7 +19,7 @@ import { VectorErrorJson, StoredTransaction, } from "@connext/vector-types"; -import { constructRpcRequest, getPublicIdentifierFromPublicKey, hydrateProviders } from "@connext/vector-utils"; +import { constructRpcRequest, getPublicIdentifierFromPublicKey, hydrateProviders, parseProviders } from "@connext/vector-utils"; import { WithdrawCommitment } from "@connext/vector-contracts"; import { Static, Type } from "@sinclair/typebox"; import { Wallet } from "@ethersproject/wallet"; @@ -51,7 +51,7 @@ server.register(fastifyCors, { export const store = new PrismaStore(); -export const _providers = hydrateProviders(config.chainProviders); +export const _providers = hydrateProviders(parseProviders(config.chainProviders)); server.addHook("onReady", async () => { const persistedNodes = await store.getNodeIndexes(); diff --git a/modules/types/src/network.ts b/modules/types/src/network.ts index 64284a265..6f2b47d11 100644 --- a/modules/types/src/network.ts +++ b/modules/types/src/network.ts @@ -1,9 +1,53 @@ -import { JsonRpcProvider } from "@ethersproject/providers"; +import { JsonRpcProvider, FallbackProvider } from "@ethersproject/providers"; export type ChainProviders = { - [chainId: number]: string; + [chainId: number]: string[] }; export type HydratedProviders = { - [chainId: number]: JsonRpcProvider; + [chainId: number]: ChainProvider; }; + +/* Represents an aggregate of providers for a particular chain. Leverages functionality from +* @ethersproject/providers/FallbackProvider in order to fallback to other providers in the +* event of failed requests. +*/ +export class ChainProvider extends FallbackProvider { + readonly chainId: number; + readonly providerUrls: string[]; + + constructor(chainId: number, providers: string[] | JsonRpcProvider[], stallTimeout?: number) { + // We'll collect all the provider URLs as we hydrate each provider. + var providerUrls: string[] = []; + super( + // Map the provider URLs into JsonRpcProviders + providers.map((provider: string | JsonRpcProvider, priority: number) => { + const hydratedProvider = (typeof(provider) === "string") ? new JsonRpcProvider(provider, chainId) : provider; + providerUrls.push(hydratedProvider.connection.url); + return { + provider: hydratedProvider, + // Invert priority as higher values are used first. + priority: -priority, + // Timeout before also triggering the next provider; this does not stop + // this provider and if its result comes back before a quorum is reached + // it will be incorporated into the vote + // - lower values will cause more network traffic but may result in a + // faster retult. + // TODO: Should we have our own default timeout defined, as well as a config option for this? + // Default timeout is written as either 2sec or .75sec (in @ethers-project/fallback-provider.ts): + // config.stallTimeout = isCommunityResource(configOrProvider) ? 2000: 750; + stallTimeout: stallTimeout + } + }), + // Quorum stays at 1, since we only ever want to send reqs to 1 node at a time. + 1 + ); + this.chainId = chainId; + this.providerUrls = providerUrls; + } + + send(method: string, params: { [name: string]: any }): Promise { + return this.perform(method, params); + } + +} \ No newline at end of file diff --git a/modules/utils/src/eth.ts b/modules/utils/src/eth.ts index d3544c4d6..5a22bba22 100644 --- a/modules/utils/src/eth.ts +++ b/modules/utils/src/eth.ts @@ -2,6 +2,7 @@ import { ChainProviders, HydratedProviders } from "@connext/vector-types"; import { Provider } from "@ethersproject/abstract-provider"; import { BigNumber } from "@ethersproject/bignumber"; import { JsonRpcProvider, StaticJsonRpcProvider } from "@ethersproject/providers"; +import { ChainProvider } from "@connext/vector-types"; const classicProviders = ["https://www.ethercluster.com/etc"]; const classicChainIds = [61]; @@ -20,10 +21,21 @@ export const getGasPrice = async (provider: Provider, providedChainId?: number): return chainId === 100 && price.lt(minGasPrice) ? minGasPrice : price; }; +/// Parse CSV formatted provider dict into ChainProviders, which uses a list of Urls per chainId. +export const parseProviders = (prevChainProviders: { [chainId: string]: string }): ChainProviders => { + var chainProviders: ChainProviders = {} + Object.entries(prevChainProviders).forEach( + ([chainId, urlString]) => { + chainProviders[chainId] = urlString.split(","); + } + ); + return chainProviders +} + export const hydrateProviders = (chainProviders: ChainProviders): HydratedProviders => { - const hydratedProviders: { [url: string]: JsonRpcProvider } = {}; + const hydratedProviders: { [url: string]: ChainProvider } = {}; Object.entries(chainProviders).map(([chainId, url]) => { - hydratedProviders[chainId] = new StaticJsonRpcProvider(url as string, parseInt(chainId)); + hydratedProviders[chainId] = new ChainProvider(parseInt(chainId), url); }); return hydratedProviders; }; From 13613ab97f3d58a9957d2547e9c341c9fd04bb4e Mon Sep 17 00:00:00 2001 From: Jake Kidd Date: Mon, 29 Mar 2021 15:32:44 -0700 Subject: [PATCH 02/11] ChainProvider remove extension, make wrapper instead --- modules/types/src/network.ts | 67 +++++++++++++++++++++--------------- 1 file changed, 40 insertions(+), 27 deletions(-) diff --git a/modules/types/src/network.ts b/modules/types/src/network.ts index 6f2b47d11..44e7cba3c 100644 --- a/modules/types/src/network.ts +++ b/modules/types/src/network.ts @@ -8,46 +8,59 @@ export type HydratedProviders = { [chainId: number]: ChainProvider; }; +interface Blah extends JsonRpcProvider { + +} + /* Represents an aggregate of providers for a particular chain. Leverages functionality from * @ethersproject/providers/FallbackProvider in order to fallback to other providers in the * event of failed requests. */ -export class ChainProvider extends FallbackProvider { +export class ChainProvider { readonly chainId: number; readonly providerUrls: string[]; + readonly _provider: JsonRpcProvider | FallbackProvider; constructor(chainId: number, providers: string[] | JsonRpcProvider[], stallTimeout?: number) { // We'll collect all the provider URLs as we hydrate each provider. var providerUrls: string[] = []; - super( - // Map the provider URLs into JsonRpcProviders - providers.map((provider: string | JsonRpcProvider, priority: number) => { - const hydratedProvider = (typeof(provider) === "string") ? new JsonRpcProvider(provider, chainId) : provider; - providerUrls.push(hydratedProvider.connection.url); - return { - provider: hydratedProvider, - // Invert priority as higher values are used first. - priority: -priority, - // Timeout before also triggering the next provider; this does not stop - // this provider and if its result comes back before a quorum is reached - // it will be incorporated into the vote - // - lower values will cause more network traffic but may result in a - // faster retult. - // TODO: Should we have our own default timeout defined, as well as a config option for this? - // Default timeout is written as either 2sec or .75sec (in @ethers-project/fallback-provider.ts): - // config.stallTimeout = isCommunityResource(configOrProvider) ? 2000: 750; - stallTimeout: stallTimeout - } - }), - // Quorum stays at 1, since we only ever want to send reqs to 1 node at a time. - 1 - ); + if (providers.length > 1) { + this._provider = new FallbackProvider( + // Map the provider URLs into JsonRpcProviders + providers.map((provider: string | JsonRpcProvider, priority: number) => { + const hydratedProvider = (typeof(provider) === "string") ? new JsonRpcProvider(provider, chainId) : provider; + providerUrls.push(hydratedProvider.connection.url); + return { + provider: hydratedProvider, + priority: priority, + // Timeout before also triggering the next provider; this does not stop + // this provider and if its result comes back before a quorum is reached + // it will be incorporated into the vote + // - lower values will cause more network traffic but may result in a + // faster retult. + // TODO: Should we have our own default timeout defined, as well as a config option for this? + // Default timeout is written as either 2sec or .75sec (in @ethers-project/fallback-provider.ts): + // config.stallTimeout = isCommunityResource(configOrProvider) ? 2000: 750; + stallTimeout, + weight: 1 + } + }), + // Quorum stays at 1, since we only ever want to send reqs to 1 node at a time. + 1 + ); + } else if (providers.length === 1) { + const provider = providers[0]; + this._provider = (typeof(provider) === "string") ? new JsonRpcProvider(provider, chainId) : provider; + } else { + throw new Error("At least one provider must be defined.") + } + this.chainId = chainId; this.providerUrls = providerUrls; } - send(method: string, params: { [name: string]: any }): Promise { - return this.perform(method, params); - } + // send(method: string, params: { [name: string]: any }): Promise { + // return this.perform(method, params); + // } } \ No newline at end of file From 4d34f2e1c422e9993717855aff81eb6f204985ea Mon Sep 17 00:00:00 2001 From: Jake Kidd Date: Mon, 29 Mar 2021 16:19:22 -0700 Subject: [PATCH 03/11] manually wrap chosen provider --- modules/types/src/network.ts | 47 +++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/modules/types/src/network.ts b/modules/types/src/network.ts index 44e7cba3c..e99d68af0 100644 --- a/modules/types/src/network.ts +++ b/modules/types/src/network.ts @@ -1,4 +1,4 @@ -import { JsonRpcProvider, FallbackProvider } from "@ethersproject/providers"; +import { JsonRpcProvider, FallbackProvider, BaseProvider } from "@ethersproject/providers"; export type ChainProviders = { [chainId: number]: string[] @@ -8,15 +8,11 @@ export type HydratedProviders = { [chainId: number]: ChainProvider; }; -interface Blah extends JsonRpcProvider { - -} - /* Represents an aggregate of providers for a particular chain. Leverages functionality from * @ethersproject/providers/FallbackProvider in order to fallback to other providers in the * event of failed requests. */ -export class ChainProvider { +export class ChainProvider extends BaseProvider { readonly chainId: number; readonly providerUrls: string[]; readonly _provider: JsonRpcProvider | FallbackProvider; @@ -24,8 +20,9 @@ export class ChainProvider { constructor(chainId: number, providers: string[] | JsonRpcProvider[], stallTimeout?: number) { // We'll collect all the provider URLs as we hydrate each provider. var providerUrls: string[] = []; + var provider: JsonRpcProvider | FallbackProvider; if (providers.length > 1) { - this._provider = new FallbackProvider( + provider = new FallbackProvider( // Map the provider URLs into JsonRpcProviders providers.map((provider: string | JsonRpcProvider, priority: number) => { const hydratedProvider = (typeof(provider) === "string") ? new JsonRpcProvider(provider, chainId) : provider; @@ -49,18 +46,44 @@ export class ChainProvider { 1 ); } else if (providers.length === 1) { - const provider = providers[0]; - this._provider = (typeof(provider) === "string") ? new JsonRpcProvider(provider, chainId) : provider; + const singleProvider = providers[0]; + provider = (typeof(singleProvider) === "string") ? new JsonRpcProvider(singleProvider, chainId) : singleProvider; } else { throw new Error("At least one provider must be defined.") } + super(provider.getNetwork()) + this._provider = provider; this.chainId = chainId; this.providerUrls = providerUrls; + + for (var member in BaseProvider) { + if (typeof BaseProvider[member] === "function") { + // if (T.hasOwnProperty(member)) { + this[member] = this._provider[member]; + // } + } + } } - // send(method: string, params: { [name: string]: any }): Promise { - // return this.perform(method, params); - // } + send(method: string, params: any[]): Promise { + if (this._provider instanceof JsonRpcProvider) { + return (this._provider as JsonRpcProvider).send(method, params); + } else { + // return (this._provider as FallbackProvider).perform(method, params); + const providers = (this._provider as FallbackProvider).providerConfigs.map(p => p.provider); + return new Promise((resolve, reject) => { + var errors: any[] = []; + for (let i = 0; i < providers.length; i++) { + try { + resolve((providers[i] as JsonRpcProvider).send(method, params)); + } catch (e) { + errors.push(e); + } + } + reject(errors); + }); + } + } } \ No newline at end of file From d897f08e52386e99b6fce8a912dab8547f9d6a16 Mon Sep 17 00:00:00 2001 From: Jake Kidd Date: Mon, 29 Mar 2021 16:49:24 -0700 Subject: [PATCH 04/11] try Object.assign --- modules/contracts/src.sol/CMCCore.sol | 6 +++--- modules/types/src/network.ts | 15 +++++++++------ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/modules/contracts/src.sol/CMCCore.sol b/modules/contracts/src.sol/CMCCore.sol index c56b52a71..91d3be7fc 100644 --- a/modules/contracts/src.sol/CMCCore.sol +++ b/modules/contracts/src.sol/CMCCore.sol @@ -52,8 +52,8 @@ contract CMCCore is ReentrancyGuard, ICMCCore { bob = _bob; } - /// @notice A getter function for the bob of the multisig - /// @return Bob's signer address + /// @notice A getter function for the alice of the multisig + /// @return Alice's signer address function getAlice() external view @@ -66,7 +66,7 @@ contract CMCCore is ReentrancyGuard, ICMCCore { } /// @notice A getter function for the bob of the multisig - /// @return Alice's signer address + /// @return Bob's signer address function getBob() external view diff --git a/modules/types/src/network.ts b/modules/types/src/network.ts index e99d68af0..41afbebc2 100644 --- a/modules/types/src/network.ts +++ b/modules/types/src/network.ts @@ -57,11 +57,15 @@ export class ChainProvider extends BaseProvider { this.chainId = chainId; this.providerUrls = providerUrls; - for (var member in BaseProvider) { - if (typeof BaseProvider[member] === "function") { - // if (T.hasOwnProperty(member)) { - this[member] = this._provider[member]; - // } + // Object.assign(this, this._provider); -> TypeError: Cannot assign to read only property '_isProvider' of object '#' + for (var key in Object.keys(this._provider)) { + console.log("DEBUG MESSAGE:") + console.log(key, this._provider[key]); + if (typeof this._provider[key] == "function") { + Object.assign(this, { + key: this._provider[key] + }); + console.log(this[key]); } } } @@ -70,7 +74,6 @@ export class ChainProvider extends BaseProvider { if (this._provider instanceof JsonRpcProvider) { return (this._provider as JsonRpcProvider).send(method, params); } else { - // return (this._provider as FallbackProvider).perform(method, params); const providers = (this._provider as FallbackProvider).providerConfigs.map(p => p.provider); return new Promise((resolve, reject) => { var errors: any[] = []; From f2501038269f923e38a72cd771d2f2da63068292 Mon Sep 17 00:00:00 2001 From: Jake Kidd Date: Mon, 29 Mar 2021 20:40:42 -0700 Subject: [PATCH 05/11] try implements BaseProvider --- modules/types/src/network.ts | 360 ++++++++++++++++++++++++++++------- 1 file changed, 287 insertions(+), 73 deletions(-) diff --git a/modules/types/src/network.ts b/modules/types/src/network.ts index 41afbebc2..6d3dcf20e 100644 --- a/modules/types/src/network.ts +++ b/modules/types/src/network.ts @@ -1,4 +1,9 @@ -import { JsonRpcProvider, FallbackProvider, BaseProvider } from "@ethersproject/providers"; +import { FilterByBlockHash, BlockWithTransactions, TransactionRequest } from "@ethersproject/abstract-provider"; +import { BigNumber, BigNumberish } from "@ethersproject/bignumber"; +import { JsonRpcProvider, FallbackProvider, BaseProvider, Block, BlockTag, EventType, Filter, Formatter, Listener, Log, Network, Provider, Resolver, TransactionReceipt, TransactionResponse } from "@ethersproject/providers"; +import { Event } from "@ethersproject/providers/lib/base-provider"; +import { Transaction } from "@ethersproject/transactions"; +import { Deferrable } from "@ethersproject/properties"; export type ChainProviders = { [chainId: number]: string[] @@ -12,81 +17,290 @@ export type HydratedProviders = { * @ethersproject/providers/FallbackProvider in order to fallback to other providers in the * event of failed requests. */ -export class ChainProvider extends BaseProvider { - readonly chainId: number; - readonly providerUrls: string[]; - readonly _provider: JsonRpcProvider | FallbackProvider; - - constructor(chainId: number, providers: string[] | JsonRpcProvider[], stallTimeout?: number) { - // We'll collect all the provider URLs as we hydrate each provider. - var providerUrls: string[] = []; - var provider: JsonRpcProvider | FallbackProvider; - if (providers.length > 1) { - provider = new FallbackProvider( - // Map the provider URLs into JsonRpcProviders - providers.map((provider: string | JsonRpcProvider, priority: number) => { - const hydratedProvider = (typeof(provider) === "string") ? new JsonRpcProvider(provider, chainId) : provider; - providerUrls.push(hydratedProvider.connection.url); - return { - provider: hydratedProvider, - priority: priority, - // Timeout before also triggering the next provider; this does not stop - // this provider and if its result comes back before a quorum is reached - // it will be incorporated into the vote - // - lower values will cause more network traffic but may result in a - // faster retult. - // TODO: Should we have our own default timeout defined, as well as a config option for this? - // Default timeout is written as either 2sec or .75sec (in @ethers-project/fallback-provider.ts): - // config.stallTimeout = isCommunityResource(configOrProvider) ? 2000: 750; - stallTimeout, - weight: 1 - } - }), - // Quorum stays at 1, since we only ever want to send reqs to 1 node at a time. - 1 - ); - } else if (providers.length === 1) { - const singleProvider = providers[0]; - provider = (typeof(singleProvider) === "string") ? new JsonRpcProvider(singleProvider, chainId) : singleProvider; - } else { - throw new Error("At least one provider must be defined.") - } - super(provider.getNetwork()) - - this._provider = provider; - this.chainId = chainId; - this.providerUrls = providerUrls; - - // Object.assign(this, this._provider); -> TypeError: Cannot assign to read only property '_isProvider' of object '#' - for (var key in Object.keys(this._provider)) { - console.log("DEBUG MESSAGE:") - console.log(key, this._provider[key]); - if (typeof this._provider[key] == "function") { - Object.assign(this, { - key: this._provider[key] - }); - console.log(this[key]); - } - } +export class ChainProvider implements BaseProvider { + readonly chainId: number; + readonly providerUrls: string[]; + readonly _provider: JsonRpcProvider | FallbackProvider; + + _isProvider: boolean = true; + _networkPromise: Promise; + _network: Network; + _events: Event[]; + formatter: Formatter; + _emitted: { [eventName: string]: number | "pending"; }; + _pollingInterval: number; + _poller: NodeJS.Timer = setInterval(() => {}, this.pollingInterval); + _bootstrapPoll: NodeJS.Timer = setInterval(() => {}, this.pollingInterval); + _lastBlockNumber: number = -1; + _fastBlockNumber: number = -1; + _fastBlockNumberPromise: Promise = new Promise((res, rej) => {res(-1)}); + _fastQueryDate: number = -1; + _maxInternalBlockNumber: number = -1 + _internalBlockNumber: Promise<{ blockNumber: number; reqTime: number; respTime: number; }> = new Promise((res, rej) => {}); + anyNetwork: boolean = false; + + constructor(chainId: number, providers: string[] | JsonRpcProvider[], stallTimeout?: number) { + // We'll collect all the provider URLs as we hydrate each provider. + var providerUrls: string[] = []; + var provider: JsonRpcProvider | FallbackProvider; + if (providers.length > 1) { + provider = new FallbackProvider( + // Map the provider URLs into JsonRpcProviders + providers.map((provider: string | JsonRpcProvider, priority: number) => { + const hydratedProvider = (typeof(provider) === "string") ? new JsonRpcProvider(provider, chainId) : provider; + providerUrls.push(hydratedProvider.connection.url); + return { + provider: hydratedProvider, + priority: priority, + // Timeout before also triggering the next provider; this does not stop + // this provider and if its result comes back before a quorum is reached + // it will be incorporated into the vote + // - lower values will cause more network traffic but may result in a + // faster retult. + // TODO: Should we have our own default timeout defined, as well as a config option for this? + // Default timeout is written as either 2sec or .75sec (in @ethers-project/fallback-provider.ts): + // config.stallTimeout = isCommunityResource(configOrProvider) ? 2000: 750; + stallTimeout, + weight: 1 + } + }), + // Quorum stays at 1, since we only ever want to send reqs to 1 node at a time. + 1 + ); + } else if (providers.length === 1) { + const singleProvider = providers[0]; + provider = (typeof(singleProvider) === "string") ? new JsonRpcProvider(singleProvider, chainId) : singleProvider; + } else { + throw new Error("At least one provider must be defined.") } - send(method: string, params: any[]): Promise { - if (this._provider instanceof JsonRpcProvider) { - return (this._provider as JsonRpcProvider).send(method, params); - } else { - const providers = (this._provider as FallbackProvider).providerConfigs.map(p => p.provider); - return new Promise((resolve, reject) => { - var errors: any[] = []; - for (let i = 0; i < providers.length; i++) { - try { - resolve((providers[i] as JsonRpcProvider).send(method, params)); - } catch (e) { - errors.push(e); - } + // @TODO: These were copied directly from BaseProvider constructor, since they are required to + // do an implements in this case. Is there a better way to wrap BaseProvider? + this._events = []; + this._emitted = { block: -2 }; + this.formatter = BaseProvider.getFormatter(); + this._maxInternalBlockNumber = -1024; + this._lastBlockNumber = -2; + this._pollingInterval = 4000; + this._fastQueryDate = 0; + this._networkPromise = provider.getNetwork(); + this._network = provider.network; + + this._provider = provider; + this.chainId = chainId; + this.providerUrls = providerUrls; + + // for (var member in BaseProvider) { + // if (typeof BaseProvider[member] === "function") { + // // if (T.hasOwnProperty(member)) { + // this[member] = this._provider[member]; + // // } + // } + // } + + // Object.assign(this, this._provider); -> TypeError: Cannot assign to read only property '_isProvider' of object '#' + // for (var key in Object.keys(this._provider)) { + // console.log("DEBUG MESSAGE:") + // console.log(key, this._provider[key]); + // if (typeof this._provider[key] == "function") { + // Object.assign(this, { + // key: this._provider[key] + // }); + // console.log(this[key]); + // } + // } + } + + send(method: string, params: any[]): Promise { + if (this._provider instanceof JsonRpcProvider) { + return (this._provider as JsonRpcProvider).send(method, params); + } else { + const providers = (this._provider as FallbackProvider).providerConfigs.map(p => p.provider); + return new Promise((resolve, reject) => { + var errors: any[] = []; + for (let i = 0; i < providers.length; i++) { + try { + resolve((providers[i] as JsonRpcProvider).send(method, params)); + } catch (e) { + errors.push(e); } - reject(errors); - }); - } + } + reject(errors); + }); } + } + + + async call(transaction: Deferrable, blockTag?: BlockTag | Promise): Promise { + return this._provider.call(transaction, blockTag); + } + + async estimateGas(transaction: Deferrable): Promise { + return this._provider.estimateGas(transaction); + } + + _wrapTransaction(tx: Transaction, hash?: string): TransactionResponse { + return this._provider._wrapTransaction(tx, hash); + } + + async _getTransactionRequest(transaction: Deferrable): Promise { + return this._provider._getTransactionRequest(transaction); + } + + _ready(): Promise { + return this._provider._ready(); + } + get ready(): Promise { + return this._provider.ready; + } + _getInternalBlockNumber(maxAge: number): Promise { + return this._provider._getInternalBlockNumber(maxAge); + } + poll(): Promise { + return this._provider.poll(); + } + resetEventsBlock(blockNumber: number): void { + return this._provider.resetEventsBlock(blockNumber); + } + get network(): Network { + return this._provider.network; + } + detectNetwork(): Promise { + return this._provider.detectNetwork(); + } + getNetwork(): Promise { + return this._provider.getNetwork(); + } + get blockNumber(): number { + return this._provider.blockNumber; + } + get polling(): boolean { + return this._provider.polling; + } + set polling(value: boolean) { + this._provider.polling = value; + } + get pollingInterval(): number { + return this._provider.pollingInterval; + } + set pollingInterval(value: number) { + this._provider.pollingInterval = value; + } + _getFastBlockNumber(): Promise { + return this._provider._getFastBlockNumber(); + } + _setFastBlockNumber(blockNumber: number): void { + return this._provider._setFastBlockNumber(blockNumber); + } + waitForTransaction(transactionHash: string, confirmations?: number, timeout?: number): Promise { + return this._provider.waitForTransaction(transactionHash, confirmations, timeout); + } + getBlockNumber(): Promise { + return this._provider.getBlockNumber(); + } + getGasPrice(): Promise { + return this._provider.getGasPrice(); + } + getBalance(addressOrName: string | Promise, blockTag?: BlockTag | Promise): Promise { + return this._provider.getBalance(addressOrName, blockTag); + } + getTransactionCount(addressOrName: string | Promise, blockTag?: BlockTag | Promise): Promise { + return this._provider.getTransactionCount(addressOrName, blockTag); + } + getCode(addressOrName: string | Promise, blockTag?: BlockTag | Promise): Promise { + return this._provider.getCode(addressOrName, blockTag); + } + getStorageAt(addressOrName: string | Promise, position: BigNumberish | Promise, blockTag?: BlockTag | Promise): Promise { + return this._provider.getStorageAt(addressOrName, position); + } + sendTransaction(signedTransaction: string | Promise): Promise { + return this._provider.sendTransaction(signedTransaction); + } + _getFilter(filter: Filter | FilterByBlockHash | Promise): Promise { + return this._provider._getFilter(filter); + } + _getAddress(addressOrName: string | Promise): Promise { + return this._provider._getAddress(addressOrName) + } + _getBlock(blockHashOrBlockTag: BlockTag | Promise, includeTransactions?: boolean): Promise { + return this._provider._getBlock(blockHashOrBlockTag, includeTransactions) + } + getBlock(blockHashOrBlockTag: BlockTag | Promise): Promise { + return this._provider.getBlock(blockHashOrBlockTag); + } + getBlockWithTransactions(blockHashOrBlockTag: BlockTag | Promise): Promise { + return this._provider.getBlockWithTransactions(blockHashOrBlockTag); + } + getTransaction(transactionHash: string | Promise): Promise { + return this._provider.getTransaction(transactionHash); + } + getTransactionReceipt(transactionHash: string | Promise): Promise { + return this._provider.getTransactionReceipt(transactionHash); + } + getLogs(filter: Filter | FilterByBlockHash | Promise): Promise { + return this._provider.getLogs(filter); + } + getEtherPrice(): Promise { + return this._provider.getEtherPrice(); + } + _getBlockTag(blockTag: BlockTag | Promise): Promise { + return this._provider._getBlockTag(blockTag); + } + getResolver(name: string): Promise { + return this._provider.getResolver(name); + } + _getResolver(name: string): Promise { + return this._provider._getResolver(name); + } + resolveName(name: string | Promise): Promise { + return this._provider.resolveName(name); + } + lookupAddress(address: string | Promise): Promise { + return this._provider.lookupAddress(address); + } + perform(method: string, params: any): Promise { + return this._provider.perform(method, params); + } + _startEvent(event: Event): void { + return this._provider._startEvent(event); + } + _stopEvent(event: Event): void { + return this._provider._stopEvent(event); + } + _addEventListener(eventName: EventType, listener: Listener, once: boolean): this { + this._provider._addEventListener(eventName, listener, once); + return this; + } + on(eventName: EventType, listener: Listener): this { + this._provider.on(eventName, listener); + return this; + } + once(eventName: EventType, listener: Listener): this { + this._provider.once(eventName, listener); + return this; + } + emit(eventName: EventType, ...args: any[]): boolean { + return this._provider.emit(eventName, ...args); + } + listenerCount(eventName?: EventType): number { + return this._provider.listenerCount(eventName); + } + listeners(eventName?: EventType): Listener[] { + return this._provider.listeners(eventName); + } + off(eventName: EventType, listener?: Listener): this { + this._provider.off(eventName, listener); + return this; + } + removeAllListeners(eventName?: EventType): this { + this._provider.removeAllListeners(eventName); + return this; + } + addListener(eventName: EventType, listener: Listener): Provider { + return this._provider.addListener(eventName, listener); + } + removeListener(eventName: EventType, listener: Listener): Provider { + return this._provider.removeListener(eventName, listener); + } } \ No newline at end of file From 551b555e28dc4120b7239aa580aa3d512047ad43 Mon Sep 17 00:00:00 2001 From: Jake Kidd Date: Mon, 17 May 2021 15:37:20 -0700 Subject: [PATCH 06/11] revert : unnecessary change --- modules/contracts/src.sol/CMCCore.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/contracts/src.sol/CMCCore.sol b/modules/contracts/src.sol/CMCCore.sol index 91d3be7fc..c56b52a71 100644 --- a/modules/contracts/src.sol/CMCCore.sol +++ b/modules/contracts/src.sol/CMCCore.sol @@ -52,8 +52,8 @@ contract CMCCore is ReentrancyGuard, ICMCCore { bob = _bob; } - /// @notice A getter function for the alice of the multisig - /// @return Alice's signer address + /// @notice A getter function for the bob of the multisig + /// @return Bob's signer address function getAlice() external view @@ -66,7 +66,7 @@ contract CMCCore is ReentrancyGuard, ICMCCore { } /// @notice A getter function for the bob of the multisig - /// @return Bob's signer address + /// @return Alice's signer address function getBob() external view From 93b9ebc4bf641147dabf12489dd5754c4d1405f0 Mon Sep 17 00:00:00 2001 From: Jake Kidd Date: Mon, 17 May 2021 17:32:57 -0700 Subject: [PATCH 07/11] update : usage of JsonRpcProvider -> ChainProvider --- .../src.ts/services/ethReader.spec.ts | 10 ++++---- .../src.ts/services/ethService.spec.ts | 9 ++++---- .../contracts/src.ts/services/ethService.ts | 2 +- modules/router/src/services/config.ts | 4 ++-- modules/router/src/test/autoRebalance.spec.ts | 4 +--- modules/types/src/network.ts | 23 ++----------------- 6 files changed, 16 insertions(+), 36 deletions(-) diff --git a/modules/contracts/src.ts/services/ethReader.spec.ts b/modules/contracts/src.ts/services/ethReader.spec.ts index 0abf25a39..0014ca8ed 100644 --- a/modules/contracts/src.ts/services/ethReader.spec.ts +++ b/modules/contracts/src.ts/services/ethReader.spec.ts @@ -1,6 +1,6 @@ -import { ChainError, FullChannelState, Result } from "@connext/vector-types"; +import { ChainError, ChainProvider, FullChannelState, Result } from "@connext/vector-types"; import { createTestChannelState, expect, getTestLoggers, mkHash } from "@connext/vector-utils"; -import { JsonRpcProvider, TransactionReceipt } from "@ethersproject/providers"; +import { TransactionReceipt } from "@ethersproject/providers"; import { AddressZero, One, Zero } from "@ethersproject/constants"; import { parseUnits } from "@ethersproject/units"; import { restore, reset, createStubInstance, SinonStubbedInstance } from "sinon"; @@ -9,8 +9,8 @@ import { EthereumChainReader, MIN_GAS_PRICE, BUMP_GAS_PRICE } from "./ethReader" let ethReader: EthereumChainReader; let channelState: FullChannelState; -let provider1337: SinonStubbedInstance; -let provider1338: SinonStubbedInstance; +let provider1337: SinonStubbedInstance; +let provider1338: SinonStubbedInstance; const assertResult = (result: Result, isError: boolean, unwrappedVal?: any) => { if (isError) { @@ -46,7 +46,7 @@ describe("ethReader", () => { beforeEach(() => { // eth service deps - const _provider = createStubInstance(JsonRpcProvider); + const _provider = createStubInstance(ChainProvider); _provider.getTransaction.resolves(_txResponse); provider1337 = _provider; provider1338 = _provider; diff --git a/modules/contracts/src.ts/services/ethService.spec.ts b/modules/contracts/src.ts/services/ethService.spec.ts index 7bcc3ca44..5c4ecf0ca 100644 --- a/modules/contracts/src.ts/services/ethService.spec.ts +++ b/modules/contracts/src.ts/services/ethService.spec.ts @@ -1,5 +1,6 @@ import { ChainError, + ChainProvider, FullChannelState, IChainServiceStore, IChannelSigner, @@ -19,7 +20,7 @@ import { mkHash, } from "@connext/vector-utils"; import { AddressZero, One, Zero } from "@ethersproject/constants"; -import { JsonRpcProvider, TransactionReceipt, TransactionResponse } from "@ethersproject/providers"; +import { TransactionReceipt, TransactionResponse } from "@ethersproject/providers"; import { BigNumber } from "ethers"; import { parseUnits } from "ethers/lib/utils"; import { restore, reset, createStubInstance, SinonStubbedInstance, stub, SinonStub } from "sinon"; @@ -30,8 +31,8 @@ import { BIG_GAS_PRICE, EthereumChainService } from "./ethService"; let storeMock: SinonStubbedInstance; let signer: SinonStubbedInstance; let ethService: EthereumChainService; -let provider1337: SinonStubbedInstance; -let provider1338: SinonStubbedInstance; +let provider1337: SinonStubbedInstance; +let provider1338: SinonStubbedInstance; let sendTxWithRetriesMock: SinonStub; let approveMock: SinonStub; @@ -96,7 +97,7 @@ describe("ethService unit test", () => { signer.connect.returns(signer as any); (signer as any)._isSigner = true; - const _provider = createStubInstance(JsonRpcProvider); + const _provider = createStubInstance(ChainProvider); _provider.getTransaction.resolves(txResponse); provider1337 = _provider; provider1338 = _provider; diff --git a/modules/contracts/src.ts/services/ethService.ts b/modules/contracts/src.ts/services/ethService.ts index d5aad039a..8bc730b69 100644 --- a/modules/contracts/src.ts/services/ethService.ts +++ b/modules/contracts/src.ts/services/ethService.ts @@ -529,7 +529,7 @@ export class EthereumChainService extends EthereumChainReader implements IVector * the tx will be resubmitted at the same nonce. */ public async waitForConfirmation(chainId: number, responses: TransactionResponse[]): Promise { - const provider: JsonRpcProvider = this.chainProviders[chainId]; + const provider = this.chainProviders[chainId]; if (!provider) { throw new ChainError(ChainError.reasons.ProviderNotFound); } diff --git a/modules/router/src/services/config.ts b/modules/router/src/services/config.ts index 79863ca70..9c1687485 100644 --- a/modules/router/src/services/config.ts +++ b/modules/router/src/services/config.ts @@ -4,8 +4,8 @@ import { jsonifyError, IVectorChainReader, DEFAULT_ROUTER_MAX_SAFE_PRICE_IMPACT, + ChainProvider, } from "@connext/vector-types"; -import { JsonRpcProvider } from "@ethersproject/providers"; import { StableSwap } from "@connext/vector-contracts"; import { getAddress } from "@ethersproject/address"; import { BigNumber } from "@ethersproject/bignumber"; @@ -132,7 +132,7 @@ export const onSwapGivenIn = async ( logger: BaseLogger, ): Promise> => { const { stableAmmChainId, stableAmmAddress } = getConfig(); - const stableAmmProvider: JsonRpcProvider = new JsonRpcProvider(getConfig().chainProviders[stableAmmChainId!]); + const stableAmmProvider: ChainProvider = new ChainProvider(stableAmmChainId!, [getConfig().chainProviders[stableAmmChainId!]]); // if there's no swap, rate is 1:1 if (fromAssetId === toAssetId && fromChainId === toChainId) { diff --git a/modules/router/src/test/autoRebalance.spec.ts b/modules/router/src/test/autoRebalance.spec.ts index f7cf8c62d..433e346a1 100644 --- a/modules/router/src/test/autoRebalance.spec.ts +++ b/modules/router/src/test/autoRebalance.spec.ts @@ -6,14 +6,12 @@ import { Wallet } from "@ethersproject/wallet"; import { BigNumber } from "@ethersproject/bignumber"; import { parseEther } from "@ethersproject/units"; import axios from "axios"; -import PriorityQueue from "p-queue"; import { rebalanceIfNeeded } from "../services/autoRebalance"; import { getConfig } from "../config"; import * as metrics from "../metrics"; import { PrismaStore, RouterRebalanceStatus } from "../services/store"; import { _createQueueForSwap } from "../services/rebalanceQueue"; -import { AutoRebalanceServiceError } from "../errors"; const config = getConfig(); @@ -24,7 +22,7 @@ const { log } = getTestLoggers(testName, config.logLevel as any); const setupForRebalance = ( mockAxios: Sinon.SinonStubbedInstance, wallet: Sinon.SinonStubbedInstance, - hydratedProviders: { [chainId: number]: Sinon.SinonStubbedInstance }, + hydratedProviders: { [chainId: number]: Sinon.SinonStubbedInstance }, chainService: Sinon.SinonStubbedInstance, ): { transaction: { diff --git a/modules/types/src/network.ts b/modules/types/src/network.ts index 6d3dcf20e..2d026ab39 100644 --- a/modules/types/src/network.ts +++ b/modules/types/src/network.ts @@ -89,26 +89,6 @@ export class ChainProvider implements BaseProvider { this._provider = provider; this.chainId = chainId; this.providerUrls = providerUrls; - - // for (var member in BaseProvider) { - // if (typeof BaseProvider[member] === "function") { - // // if (T.hasOwnProperty(member)) { - // this[member] = this._provider[member]; - // // } - // } - // } - - // Object.assign(this, this._provider); -> TypeError: Cannot assign to read only property '_isProvider' of object '#' - // for (var key in Object.keys(this._provider)) { - // console.log("DEBUG MESSAGE:") - // console.log(key, this._provider[key]); - // if (typeof this._provider[key] == "function") { - // Object.assign(this, { - // key: this._provider[key] - // }); - // console.log(this[key]); - // } - // } } send(method: string, params: any[]): Promise { @@ -181,9 +161,10 @@ export class ChainProvider implements BaseProvider { this._provider.polling = value; } get pollingInterval(): number { - return this._provider.pollingInterval; + return this._provider ? this._provider.pollingInterval : this._pollingInterval; } set pollingInterval(value: number) { + this._pollingInterval = value; this._provider.pollingInterval = value; } _getFastBlockNumber(): Promise { From e2a239ba8ad9ac3ad7b3ac075ae41244a7f9d637 Mon Sep 17 00:00:00 2001 From: Jake Kidd Date: Tue, 25 May 2021 17:21:18 -0700 Subject: [PATCH 08/11] fix protocol usage of env var -> providers --- modules/protocol/src/testing/constants.ts | 4 ++-- modules/types/src/network.ts | 20 +++++++++++++------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/modules/protocol/src/testing/constants.ts b/modules/protocol/src/testing/constants.ts index 622332e3a..f7acb509f 100644 --- a/modules/protocol/src/testing/constants.ts +++ b/modules/protocol/src/testing/constants.ts @@ -4,8 +4,8 @@ import { Wallet } from "@ethersproject/wallet"; import { env } from "./env"; export const chainId = parseInt(Object.keys(env.chainProviders)[0]); -export const tokenAddress = env.chainAddresses[chainId]?.testTokenAddress ?? ""; -export const provider = new ChainProvider(chainId, env.chainProviders[chainId]); +export const tokenAddress = env.chainAddresses[chainId]?.testTokenAddress ?? ""; +export const provider = new ChainProvider(chainId, env.chainProviders[chainId].split(",")); export const sugarDaddy = Wallet.fromMnemonic(env.sugarDaddyMnemonic).connect(provider); export const rando = Wallet.createRandom().connect(provider); diff --git a/modules/types/src/network.ts b/modules/types/src/network.ts index 2d026ab39..56222a6dd 100644 --- a/modules/types/src/network.ts +++ b/modules/types/src/network.ts @@ -110,7 +110,6 @@ export class ChainProvider implements BaseProvider { } } - async call(transaction: Deferrable, blockTag?: BlockTag | Promise): Promise { return this._provider.call(transaction, blockTag); } @@ -130,9 +129,7 @@ export class ChainProvider implements BaseProvider { _ready(): Promise { return this._provider._ready(); } - get ready(): Promise { - return this._provider.ready; - } + _getInternalBlockNumber(maxAge: number): Promise { return this._provider._getInternalBlockNumber(maxAge); } @@ -142,15 +139,20 @@ export class ChainProvider implements BaseProvider { resetEventsBlock(blockNumber: number): void { return this._provider.resetEventsBlock(blockNumber); } - get network(): Network { - return this._provider.network; - } + detectNetwork(): Promise { return this._provider.detectNetwork(); } getNetwork(): Promise { return this._provider.getNetwork(); } + + get ready(): Promise { + return this._provider.ready; + } + get network(): Network { + return this._provider.network; + } get blockNumber(): number { return this._provider.blockNumber; } @@ -167,6 +169,7 @@ export class ChainProvider implements BaseProvider { this._pollingInterval = value; this._provider.pollingInterval = value; } + _getFastBlockNumber(): Promise { return this._provider._getFastBlockNumber(); } @@ -283,5 +286,8 @@ export class ChainProvider implements BaseProvider { removeListener(eventName: EventType, listener: Listener): Provider { return this._provider.removeListener(eventName, listener); } + _waitForTransaction(transactionHash: string, confirmations: number, timeout: number, replaceable: { data: string; from: string; nonce: number; to: string; value: BigNumber; startBlock: number; }): Promise { + throw new Error("Method not implemented."); + } } \ No newline at end of file From 927015ffc203faef67c29de6e2fc71736b9b1925 Mon Sep 17 00:00:00 2001 From: Jake Kidd Date: Tue, 25 May 2021 17:38:24 -0700 Subject: [PATCH 09/11] chainprovider -> chainrpcprovider --- modules/browser-node/src/index.ts | 8 ++++---- modules/contracts/src.ts/constants.ts | 6 +++--- .../src.ts/services/ethReader.spec.ts | 14 +++++++------- modules/contracts/src.ts/services/ethReader.ts | 10 +++++----- .../src.ts/services/ethService.spec.ts | 8 ++++---- .../contracts/src.ts/services/ethService.ts | 6 +++--- modules/engine/src/index.ts | 2 +- modules/engine/src/isAlive.ts | 2 +- modules/engine/src/testing/env.ts | 4 ++-- modules/engine/src/testing/index.spec.ts | 2 +- modules/engine/src/testing/isAlive.spec.ts | 2 +- modules/protocol/src/testing/constants.ts | 4 ++-- modules/protocol/src/testing/vector.spec.ts | 2 +- modules/router/src/services/config.ts | 4 ++-- modules/router/src/test/autoRebalance.spec.ts | 10 +++++----- modules/router/src/test/utils/mocks.ts | 4 ++-- modules/types/src/chain.ts | 4 ++-- modules/types/src/network.ts | 6 +++--- modules/types/src/vectorProvider.ts | 4 ++-- modules/utils/src/eth.ts | 18 +++++++++--------- modules/utils/src/fees.spec.ts | 4 ++-- 21 files changed, 62 insertions(+), 62 deletions(-) diff --git a/modules/browser-node/src/index.ts b/modules/browser-node/src/index.ts index d9abad654..1c26ecbaa 100644 --- a/modules/browser-node/src/index.ts +++ b/modules/browser-node/src/index.ts @@ -2,7 +2,7 @@ import { deployments, VectorChainService } from "@connext/vector-contracts"; import { VectorEngine } from "@connext/vector-engine"; import { ChainAddresses, - ChainProviders, + ChainRpcProviders, ChannelRpcMethods, CreateUpdateDetails, EngineEvent, @@ -35,7 +35,7 @@ export type BrowserNodeSignerConfig = { messagingUrl?: string; logger?: BaseLogger; signer: IChannelSigner; - chainProviders: ChainProviders; + chainProviders: ChainRpcProviders; chainAddresses: ChainAddresses; }; @@ -49,7 +49,7 @@ export class BrowserNode implements INodeService { private supportedChains: number[] = []; private routerPublicIdentifier?: string; private iframeSrc?: string; - private chainProviders: ChainProviders = {}; + private chainProviders: ChainRpcProviders = {}; private chainAddresses?: ChainAddresses; private messagingUrl?: string; private natsUrl?: string; @@ -60,7 +60,7 @@ export class BrowserNode implements INodeService { routerPublicIdentifier?: string; supportedChains?: number[]; iframeSrc?: string; - chainProviders: ChainProviders; + chainProviders: ChainRpcProviders; messagingUrl?: string; natsUrl?: string; authUrl?: string; diff --git a/modules/contracts/src.ts/constants.ts b/modules/contracts/src.ts/constants.ts index c17a23b0b..ce9b78171 100644 --- a/modules/contracts/src.ts/constants.ts +++ b/modules/contracts/src.ts/constants.ts @@ -1,7 +1,7 @@ import { HDNode } from "@ethersproject/hdnode"; import { Wallet } from "@ethersproject/wallet"; import { JsonRpcProvider } from "@ethersproject/providers"; -import { ChainProvider } from "@connext/vector-types"; +import { ChainRpcProvider } from "@connext/vector-types"; import { network, ethers } from "hardhat"; import pino from "pino"; @@ -18,8 +18,8 @@ export const logger = pino({ level: defaultLogLevel }); export const networkName = network.name; export const provider = urls - ? new ChainProvider(parseInt(chainId), (urls as string).split(",")) - : new ChainProvider(parseInt(chainId), [ethers.provider as JsonRpcProvider]); + ? new ChainRpcProvider(parseInt(chainId), (urls as string).split(",")) + : new ChainRpcProvider(parseInt(chainId), [ethers.provider as JsonRpcProvider]); const hdNode = HDNode.fromMnemonic(mnemonic).derivePath("m/44'/60'/0'/0"); diff --git a/modules/contracts/src.ts/services/ethReader.spec.ts b/modules/contracts/src.ts/services/ethReader.spec.ts index 0014ca8ed..f2071bf63 100644 --- a/modules/contracts/src.ts/services/ethReader.spec.ts +++ b/modules/contracts/src.ts/services/ethReader.spec.ts @@ -1,4 +1,4 @@ -import { ChainError, ChainProvider, FullChannelState, Result } from "@connext/vector-types"; +import { ChainError, ChainRpcProvider, FullChannelState, Result } from "@connext/vector-types"; import { createTestChannelState, expect, getTestLoggers, mkHash } from "@connext/vector-utils"; import { TransactionReceipt } from "@ethersproject/providers"; import { AddressZero, One, Zero } from "@ethersproject/constants"; @@ -9,8 +9,8 @@ import { EthereumChainReader, MIN_GAS_PRICE, BUMP_GAS_PRICE } from "./ethReader" let ethReader: EthereumChainReader; let channelState: FullChannelState; -let provider1337: SinonStubbedInstance; -let provider1338: SinonStubbedInstance; +let provider1337: SinonStubbedInstance; +let provider1338: SinonStubbedInstance; const assertResult = (result: Result, isError: boolean, unwrappedVal?: any) => { if (isError) { @@ -46,7 +46,7 @@ describe("ethReader", () => { beforeEach(() => { // eth service deps - const _provider = createStubInstance(ChainProvider); + const _provider = createStubInstance(ChainRpcProvider); _provider.getTransaction.resolves(_txResponse); provider1337 = _provider; provider1338 = _provider; @@ -70,9 +70,9 @@ describe("ethReader", () => { reset(); }); - describe.skip("getChainProviders", () => { - it("happy: getChainProvider", async () => { - const result = await ethReader.getChainProviders(); + describe.skip("getChainRpcProviders", () => { + it("happy: getChainRpcProvider", async () => { + const result = await ethReader.getChainRpcProviders(); console.log(result); }); }); diff --git a/modules/contracts/src.ts/services/ethReader.ts b/modules/contracts/src.ts/services/ethReader.ts index 125a87955..16fc8e3c0 100644 --- a/modules/contracts/src.ts/services/ethReader.ts +++ b/modules/contracts/src.ts/services/ethReader.ts @@ -8,7 +8,7 @@ import { IVectorChainReader, Result, ChainError, - ChainProviders, + ChainRpcProviders, RegisteredTransfer, TransferName, ChannelDispute, @@ -26,7 +26,7 @@ import { CoreChannelState, CoreTransferState, TransferDispute, - ChainProvider, + ChainRpcProvider, } from "@connext/vector-types"; import axios from "axios"; import { encodeBalance, encodeTransferResolver, encodeTransferState } from "@connext/vector-utils"; @@ -61,12 +61,12 @@ export class EthereumChainReader implements IVectorChainReader { private contracts: Map = new Map(); constructor( // The chainProviders specified here are hydrated. - public readonly chainProviders: { [chainId: string]: ChainProvider }, + public readonly chainProviders: { [chainId: string]: ChainRpcProvider }, public readonly log: pino.BaseLogger, ) {} - getChainProviders(): Result { - const ret: ChainProviders = {}; + getChainRpcProviders(): Result { + const ret: ChainRpcProviders = {}; Object.entries(this.chainProviders).forEach(([name, value]) => { ret[parseInt(name)] = value.providerUrls; }); diff --git a/modules/contracts/src.ts/services/ethService.spec.ts b/modules/contracts/src.ts/services/ethService.spec.ts index 5c4ecf0ca..bde17a727 100644 --- a/modules/contracts/src.ts/services/ethService.spec.ts +++ b/modules/contracts/src.ts/services/ethService.spec.ts @@ -1,6 +1,6 @@ import { ChainError, - ChainProvider, + ChainRpcProvider, FullChannelState, IChainServiceStore, IChannelSigner, @@ -31,8 +31,8 @@ import { BIG_GAS_PRICE, EthereumChainService } from "./ethService"; let storeMock: SinonStubbedInstance; let signer: SinonStubbedInstance; let ethService: EthereumChainService; -let provider1337: SinonStubbedInstance; -let provider1338: SinonStubbedInstance; +let provider1337: SinonStubbedInstance; +let provider1338: SinonStubbedInstance; let sendTxWithRetriesMock: SinonStub; let approveMock: SinonStub; @@ -97,7 +97,7 @@ describe("ethService unit test", () => { signer.connect.returns(signer as any); (signer as any)._isSigner = true; - const _provider = createStubInstance(ChainProvider); + const _provider = createStubInstance(ChainRpcProvider); _provider.getTransaction.resolves(txResponse); provider1337 = _provider; provider1338 = _provider; diff --git a/modules/contracts/src.ts/services/ethService.ts b/modules/contracts/src.ts/services/ethService.ts index 8bc730b69..b123104f6 100644 --- a/modules/contracts/src.ts/services/ethService.ts +++ b/modules/contracts/src.ts/services/ethService.ts @@ -17,7 +17,7 @@ import { StringifiedTransactionResponse, getConfirmationsForChain, StoredTransaction, - ChainProvider, + ChainRpcProvider, } from "@connext/vector-types"; import { delay, @@ -55,7 +55,7 @@ export const BIG_GAS_PRICE = parseUnits("1500", "gwei"); // TODO: Deprecate. Note that this is used in autoRebalance.ts. export const waitForTransaction = async ( - provider: ChainProvider, + provider: ChainRpcProvider, transactionHash: string, confirmations?: number, timeout?: number, @@ -85,7 +85,7 @@ export class EthereumChainService extends EthereumChainReader implements IVector }; constructor( private readonly store: IChainServiceStore, - chainProviders: { [chainId: string]: ChainProvider }, + chainProviders: { [chainId: string]: ChainRpcProvider }, signer: string | Signer, log: BaseLogger, private readonly defaultRetries = 3, diff --git a/modules/engine/src/index.ts b/modules/engine/src/index.ts index a8bb99f84..102de485c 100644 --- a/modules/engine/src/index.ts +++ b/modules/engine/src/index.ts @@ -624,7 +624,7 @@ export class VectorEngine implements IVectorEngine { ); } - const chainProviders = this.chainService.getChainProviders(); + const chainProviders = this.chainService.getChainRpcProviders(); if (chainProviders.isError) { return Result.fail(chainProviders.getError()!); } diff --git a/modules/engine/src/isAlive.ts b/modules/engine/src/isAlive.ts index dde351eec..518441dfb 100644 --- a/modules/engine/src/isAlive.ts +++ b/modules/engine/src/isAlive.ts @@ -11,7 +11,7 @@ export async function sendIsAlive( ): Promise { const method = "sendIsAlive"; const channels = await store.getChannelStates(); - const providers = chainService.getChainProviders(); + const providers = chainService.getChainRpcProviders(); if (providers.isError) { logger.error({ ...providers.getError(), method }, "Error getting chain providers"); return; diff --git a/modules/engine/src/testing/env.ts b/modules/engine/src/testing/env.ts index 37bcc8560..8c32d57a3 100644 --- a/modules/engine/src/testing/env.ts +++ b/modules/engine/src/testing/env.ts @@ -1,9 +1,9 @@ -import { ChainProviders } from "@connext/vector-types"; +import { ChainRpcProviders } from "@connext/vector-types"; import { Wallet } from "@ethersproject/wallet"; import pino from "pino"; type EngineTestEnv = { - chainProviders: ChainProviders; + chainProviders: ChainRpcProviders; chainAddresses: any; sugarDaddy: Wallet; logLevel?: pino.Level; diff --git a/modules/engine/src/testing/index.spec.ts b/modules/engine/src/testing/index.spec.ts index ddc741dcb..14a9b0a1e 100644 --- a/modules/engine/src/testing/index.spec.ts +++ b/modules/engine/src/testing/index.spec.ts @@ -43,7 +43,7 @@ describe("VectorEngine", () => { }); chainService = Sinon.createStubInstance(VectorChainService); - chainService.getChainProviders.returns(Result.ok(env.chainProviders)); + chainService.getChainRpcProviders.returns(Result.ok(env.chainProviders)); }); afterEach(() => Sinon.restore()); diff --git a/modules/engine/src/testing/isAlive.spec.ts b/modules/engine/src/testing/isAlive.spec.ts index 493e3b9e9..f2f3d1ee5 100644 --- a/modules/engine/src/testing/isAlive.spec.ts +++ b/modules/engine/src/testing/isAlive.spec.ts @@ -29,7 +29,7 @@ describe("checkIn", () => { messagingService = Sinon.createStubInstance(MemoryMessagingService); chainService = Sinon.createStubInstance(VectorChainService); - chainService.getChainProviders.returns(Result.ok(env.chainProviders)); + chainService.getChainRpcProviders.returns(Result.ok(env.chainProviders)); }); it("should send no checkIn messages if there are no channels", async () => { diff --git a/modules/protocol/src/testing/constants.ts b/modules/protocol/src/testing/constants.ts index f7acb509f..4422fe059 100644 --- a/modules/protocol/src/testing/constants.ts +++ b/modules/protocol/src/testing/constants.ts @@ -1,11 +1,11 @@ -import { ChainProvider } from "@connext/vector-types"; +import { ChainRpcProvider } from "@connext/vector-types"; import { Wallet } from "@ethersproject/wallet"; import { env } from "./env"; export const chainId = parseInt(Object.keys(env.chainProviders)[0]); export const tokenAddress = env.chainAddresses[chainId]?.testTokenAddress ?? ""; -export const provider = new ChainProvider(chainId, env.chainProviders[chainId].split(",")); +export const provider = new ChainRpcProvider(chainId, env.chainProviders[chainId].split(",")); export const sugarDaddy = Wallet.fromMnemonic(env.sugarDaddyMnemonic).connect(provider); export const rando = Wallet.createRandom().connect(provider); diff --git a/modules/protocol/src/testing/vector.spec.ts b/modules/protocol/src/testing/vector.spec.ts index 214977ebc..9cfd169b9 100644 --- a/modules/protocol/src/testing/vector.spec.ts +++ b/modules/protocol/src/testing/vector.spec.ts @@ -41,7 +41,7 @@ describe("Vector", () => { chainReader = Sinon.createStubInstance(VectorChainReader); chainReader.getChannelFactoryBytecode.resolves(Result.ok(mkHash())); chainReader.getChannelMastercopyAddress.resolves(Result.ok(mkAddress())); - chainReader.getChainProviders.returns(Result.ok(env.chainProviders)); + chainReader.getChainRpcProviders.returns(Result.ok(env.chainProviders)); lockService = Sinon.createStubInstance(MemoryLockService); messagingService = Sinon.createStubInstance(MemoryMessagingService); storeService = Sinon.createStubInstance(MemoryStoreService); diff --git a/modules/router/src/services/config.ts b/modules/router/src/services/config.ts index 9c1687485..9108d900b 100644 --- a/modules/router/src/services/config.ts +++ b/modules/router/src/services/config.ts @@ -4,7 +4,7 @@ import { jsonifyError, IVectorChainReader, DEFAULT_ROUTER_MAX_SAFE_PRICE_IMPACT, - ChainProvider, + ChainRpcProvider, } from "@connext/vector-types"; import { StableSwap } from "@connext/vector-contracts"; import { getAddress } from "@ethersproject/address"; @@ -132,7 +132,7 @@ export const onSwapGivenIn = async ( logger: BaseLogger, ): Promise> => { const { stableAmmChainId, stableAmmAddress } = getConfig(); - const stableAmmProvider: ChainProvider = new ChainProvider(stableAmmChainId!, [getConfig().chainProviders[stableAmmChainId!]]); + const stableAmmProvider: ChainRpcProvider = new ChainRpcProvider(stableAmmChainId!, [getConfig().chainProviders[stableAmmChainId!]]); // if there's no swap, rate is 1:1 if (fromAssetId === toAssetId && fromChainId === toChainId) { diff --git a/modules/router/src/test/autoRebalance.spec.ts b/modules/router/src/test/autoRebalance.spec.ts index 433e346a1..6c536f030 100644 --- a/modules/router/src/test/autoRebalance.spec.ts +++ b/modules/router/src/test/autoRebalance.spec.ts @@ -1,7 +1,7 @@ import { VectorChainReader } from "@connext/vector-contracts"; import { expect, getRandomBytes32, getTestLoggers, mkAddress, mkBytes32 } from "@connext/vector-utils"; import Sinon from "sinon"; -import { AllowedSwap, ChainProvider, Result } from "@connext/vector-types"; +import { AllowedSwap, ChainRpcProvider, Result } from "@connext/vector-types"; import { Wallet } from "@ethersproject/wallet"; import { BigNumber } from "@ethersproject/bignumber"; import { parseEther } from "@ethersproject/units"; @@ -22,7 +22,7 @@ const { log } = getTestLoggers(testName, config.logLevel as any); const setupForRebalance = ( mockAxios: Sinon.SinonStubbedInstance, wallet: Sinon.SinonStubbedInstance, - hydratedProviders: { [chainId: number]: Sinon.SinonStubbedInstance }, + hydratedProviders: { [chainId: number]: Sinon.SinonStubbedInstance }, chainService: Sinon.SinonStubbedInstance, ): { transaction: { @@ -109,7 +109,7 @@ describe(testName, () => { describe("rebalanceIfNeeded", () => { let wallet: Sinon.SinonStubbedInstance; let chainService: Sinon.SinonStubbedInstance; - let hydratedProviders: { [chainId: number]: Sinon.SinonStubbedInstance }; + let hydratedProviders: { [chainId: number]: Sinon.SinonStubbedInstance }; let mockAxios: Sinon.SinonStubbedInstance; let mockConfirmation: Sinon.SinonStubbedInstance; let store: Sinon.SinonStubbedInstance; @@ -129,8 +129,8 @@ describe(testName, () => { chainService = Sinon.createStubInstance(VectorChainReader); hydratedProviders = { - 1337: Sinon.createStubInstance(ChainProvider), - 1338: Sinon.createStubInstance(ChainProvider), + 1337: Sinon.createStubInstance(ChainRpcProvider), + 1338: Sinon.createStubInstance(ChainRpcProvider), }; const parseBalanceStub = Sinon.stub(metrics, "getDecimals").resolves(18); hydratedProviders[1337].getGasPrice.resolves(BigNumber.from(138)); diff --git a/modules/router/src/test/utils/mocks.ts b/modules/router/src/test/utils/mocks.ts index 1d6c0e090..2d987ca59 100644 --- a/modules/router/src/test/utils/mocks.ts +++ b/modules/router/src/test/utils/mocks.ts @@ -1,7 +1,7 @@ -import { ChainProvider } from "@connext/vector-types"; +import { ChainRpcProvider } from "@connext/vector-types"; import { createStubInstance } from "sinon"; -export const mockProvider = createStubInstance(ChainProvider, { +export const mockProvider = createStubInstance(ChainRpcProvider, { waitForTransaction: Promise.resolve({ logs: [] } as any), getNetwork: Promise.resolve({ chainId: 1337, name: "" }), }); diff --git a/modules/types/src/chain.ts b/modules/types/src/chain.ts index 6048b28be..60c28e020 100644 --- a/modules/types/src/chain.ts +++ b/modules/types/src/chain.ts @@ -7,7 +7,7 @@ import { Balance, FullChannelState, FullTransferState } from "./channel"; import { ChannelDispute } from "./dispute"; import { Result, Values, VectorError } from "./error"; import { ChainServiceEvent, ChainServiceEventMap } from "./event"; -import { ChainProviders, HydratedProviders } from "./network"; +import { ChainRpcProviders, HydratedProviders } from "./network"; import { RegisteredTransfer, TransferName, TransferState, WithdrawCommitmentJson } from "./transferDefinitions"; export const GAS_ESTIMATES = { @@ -185,7 +185,7 @@ export interface IVectorChainReader { getDecimals(assetId: string, chainId: number): Promise>; - getChainProviders(): Result; + getChainRpcProviders(): Result; getHydratedProviders(): Result; diff --git a/modules/types/src/network.ts b/modules/types/src/network.ts index 56222a6dd..c2065a115 100644 --- a/modules/types/src/network.ts +++ b/modules/types/src/network.ts @@ -5,19 +5,19 @@ import { Event } from "@ethersproject/providers/lib/base-provider"; import { Transaction } from "@ethersproject/transactions"; import { Deferrable } from "@ethersproject/properties"; -export type ChainProviders = { +export type ChainRpcProviders = { [chainId: number]: string[] }; export type HydratedProviders = { - [chainId: number]: ChainProvider; + [chainId: number]: ChainRpcProvider; }; /* Represents an aggregate of providers for a particular chain. Leverages functionality from * @ethersproject/providers/FallbackProvider in order to fallback to other providers in the * event of failed requests. */ -export class ChainProvider implements BaseProvider { +export class ChainRpcProvider implements BaseProvider { readonly chainId: number; readonly providerUrls: string[]; readonly _provider: JsonRpcProvider | FallbackProvider; diff --git a/modules/types/src/vectorProvider.ts b/modules/types/src/vectorProvider.ts index bfd3b4e8f..950f410fc 100644 --- a/modules/types/src/vectorProvider.ts +++ b/modules/types/src/vectorProvider.ts @@ -2,7 +2,7 @@ import { MinimalTransaction } from "./chain"; import { FullTransferState, FullChannelState, ChainAddresses } from "./channel"; import { ChannelDispute, TransferDispute } from "./dispute"; import { VectorErrorJson } from "./error"; -import { ChainProviders } from "./network"; +import { ChainRpcProviders } from "./network"; import { EngineParams, NodeResponses } from "./schemas"; import { RegisteredTransfer } from "./transferDefinitions"; @@ -83,7 +83,7 @@ export type ChannelRpcMethodsPayloadMap = { [ChannelRpcMethods.connext_authenticate]: { signature?: string; signer?: string; - chainProviders: ChainProviders; + chainProviders: ChainRpcProviders; chainAddresses?: ChainAddresses; messagingUrl?: string; natsUrl?: string; diff --git a/modules/utils/src/eth.ts b/modules/utils/src/eth.ts index 5a22bba22..7cd2db4a0 100644 --- a/modules/utils/src/eth.ts +++ b/modules/utils/src/eth.ts @@ -1,8 +1,8 @@ -import { ChainProviders, HydratedProviders } from "@connext/vector-types"; +import { ChainRpcProviders, HydratedProviders } from "@connext/vector-types"; import { Provider } from "@ethersproject/abstract-provider"; import { BigNumber } from "@ethersproject/bignumber"; import { JsonRpcProvider, StaticJsonRpcProvider } from "@ethersproject/providers"; -import { ChainProvider } from "@connext/vector-types"; +import { ChainRpcProvider } from "@connext/vector-types"; const classicProviders = ["https://www.ethercluster.com/etc"]; const classicChainIds = [61]; @@ -21,10 +21,10 @@ export const getGasPrice = async (provider: Provider, providedChainId?: number): return chainId === 100 && price.lt(minGasPrice) ? minGasPrice : price; }; -/// Parse CSV formatted provider dict into ChainProviders, which uses a list of Urls per chainId. -export const parseProviders = (prevChainProviders: { [chainId: string]: string }): ChainProviders => { - var chainProviders: ChainProviders = {} - Object.entries(prevChainProviders).forEach( +/// Parse CSV formatted provider dict into ChainRpcProviders, which uses a list of Urls per chainId. +export const parseProviders = (prevChainRpcProviders: { [chainId: string]: string }): ChainRpcProviders => { + var chainProviders: ChainRpcProviders = {} + Object.entries(prevChainRpcProviders).forEach( ([chainId, urlString]) => { chainProviders[chainId] = urlString.split(","); } @@ -32,10 +32,10 @@ export const parseProviders = (prevChainProviders: { [chainId: string]: string } return chainProviders } -export const hydrateProviders = (chainProviders: ChainProviders): HydratedProviders => { - const hydratedProviders: { [url: string]: ChainProvider } = {}; +export const hydrateProviders = (chainProviders: ChainRpcProviders): HydratedProviders => { + const hydratedProviders: { [url: string]: ChainRpcProvider } = {}; Object.entries(chainProviders).map(([chainId, url]) => { - hydratedProviders[chainId] = new ChainProvider(parseInt(chainId), url); + hydratedProviders[chainId] = new ChainRpcProvider(parseInt(chainId), url); }); return hydratedProviders; }; diff --git a/modules/utils/src/fees.spec.ts b/modules/utils/src/fees.spec.ts index 3d7233b1f..68ace379f 100644 --- a/modules/utils/src/fees.spec.ts +++ b/modules/utils/src/fees.spec.ts @@ -2,7 +2,7 @@ import { REDUCED_GAS_PRICE, Balance, ChainError, - ChainProviders, + ChainRpcProviders, ChannelDispute, FullTransferState, HydratedProviders, @@ -157,7 +157,7 @@ class MockChainReader implements IVectorChainReader { ): Promise> { throw new Error("Method not implemented."); } - getChainProviders(): Result { + getChainRpcProviders(): Result { throw new Error("Method not implemented."); } getHydratedProviders(): Result { From 76aa028b6a376de459fd4b53d3881718138763ed Mon Sep 17 00:00:00 2001 From: Jake Kidd Date: Wed, 26 May 2021 13:03:08 -0700 Subject: [PATCH 10/11] change : implement Provider instead of BaseProvider --- modules/types/src/network.ts | 148 +++++++++++------------------------ modules/utils/src/eth.ts | 15 ++-- 2 files changed, 57 insertions(+), 106 deletions(-) diff --git a/modules/types/src/network.ts b/modules/types/src/network.ts index c2065a115..4dbb9ea56 100644 --- a/modules/types/src/network.ts +++ b/modules/types/src/network.ts @@ -1,8 +1,20 @@ import { FilterByBlockHash, BlockWithTransactions, TransactionRequest } from "@ethersproject/abstract-provider"; import { BigNumber, BigNumberish } from "@ethersproject/bignumber"; -import { JsonRpcProvider, FallbackProvider, BaseProvider, Block, BlockTag, EventType, Filter, Formatter, Listener, Log, Network, Provider, Resolver, TransactionReceipt, TransactionResponse } from "@ethersproject/providers"; -import { Event } from "@ethersproject/providers/lib/base-provider"; -import { Transaction } from "@ethersproject/transactions"; +import { + JsonRpcProvider, + FallbackProvider, + Block, + BlockTag, + EventType, + Filter, + Listener, + Log, + Network, + Provider, + Resolver, + TransactionReceipt, + TransactionResponse, +} from "@ethersproject/providers"; import { Deferrable } from "@ethersproject/properties"; export type ChainRpcProviders = { @@ -17,7 +29,7 @@ export type HydratedProviders = { * @ethersproject/providers/FallbackProvider in order to fallback to other providers in the * event of failed requests. */ -export class ChainRpcProvider implements BaseProvider { +export class ChainRpcProvider implements Provider { readonly chainId: number; readonly providerUrls: string[]; readonly _provider: JsonRpcProvider | FallbackProvider; @@ -25,18 +37,6 @@ export class ChainRpcProvider implements BaseProvider { _isProvider: boolean = true; _networkPromise: Promise; _network: Network; - _events: Event[]; - formatter: Formatter; - _emitted: { [eventName: string]: number | "pending"; }; - _pollingInterval: number; - _poller: NodeJS.Timer = setInterval(() => {}, this.pollingInterval); - _bootstrapPoll: NodeJS.Timer = setInterval(() => {}, this.pollingInterval); - _lastBlockNumber: number = -1; - _fastBlockNumber: number = -1; - _fastBlockNumberPromise: Promise = new Promise((res, rej) => {res(-1)}); - _fastQueryDate: number = -1; - _maxInternalBlockNumber: number = -1 - _internalBlockNumber: Promise<{ blockNumber: number; reqTime: number; respTime: number; }> = new Promise((res, rej) => {}); anyNetwork: boolean = false; constructor(chainId: number, providers: string[] | JsonRpcProvider[], stallTimeout?: number) { @@ -74,15 +74,6 @@ export class ChainRpcProvider implements BaseProvider { throw new Error("At least one provider must be defined.") } - // @TODO: These were copied directly from BaseProvider constructor, since they are required to - // do an implements in this case. Is there a better way to wrap BaseProvider? - this._events = []; - this._emitted = { block: -2 }; - this.formatter = BaseProvider.getFormatter(); - this._maxInternalBlockNumber = -1024; - this._lastBlockNumber = -2; - this._pollingInterval = 4000; - this._fastQueryDate = 0; this._networkPromise = provider.getNetwork(); this._network = provider.network; @@ -118,21 +109,6 @@ export class ChainRpcProvider implements BaseProvider { return this._provider.estimateGas(transaction); } - _wrapTransaction(tx: Transaction, hash?: string): TransactionResponse { - return this._provider._wrapTransaction(tx, hash); - } - - async _getTransactionRequest(transaction: Deferrable): Promise { - return this._provider._getTransactionRequest(transaction); - } - - _ready(): Promise { - return this._provider._ready(); - } - - _getInternalBlockNumber(maxAge: number): Promise { - return this._provider._getInternalBlockNumber(maxAge); - } poll(): Promise { return this._provider.poll(); } @@ -143,151 +119,121 @@ export class ChainRpcProvider implements BaseProvider { detectNetwork(): Promise { return this._provider.detectNetwork(); } + getNetwork(): Promise { return this._provider.getNetwork(); } - get ready(): Promise { - return this._provider.ready; - } - get network(): Network { - return this._provider.network; - } - get blockNumber(): number { - return this._provider.blockNumber; - } - get polling(): boolean { - return this._provider.polling; - } - set polling(value: boolean) { - this._provider.polling = value; - } - get pollingInterval(): number { - return this._provider ? this._provider.pollingInterval : this._pollingInterval; - } - set pollingInterval(value: number) { - this._pollingInterval = value; - this._provider.pollingInterval = value; - } - - _getFastBlockNumber(): Promise { - return this._provider._getFastBlockNumber(); - } - _setFastBlockNumber(blockNumber: number): void { - return this._provider._setFastBlockNumber(blockNumber); - } waitForTransaction(transactionHash: string, confirmations?: number, timeout?: number): Promise { return this._provider.waitForTransaction(transactionHash, confirmations, timeout); } + getBlockNumber(): Promise { return this._provider.getBlockNumber(); } + getGasPrice(): Promise { return this._provider.getGasPrice(); } + getBalance(addressOrName: string | Promise, blockTag?: BlockTag | Promise): Promise { return this._provider.getBalance(addressOrName, blockTag); } + getTransactionCount(addressOrName: string | Promise, blockTag?: BlockTag | Promise): Promise { return this._provider.getTransactionCount(addressOrName, blockTag); } + getCode(addressOrName: string | Promise, blockTag?: BlockTag | Promise): Promise { return this._provider.getCode(addressOrName, blockTag); } + getStorageAt(addressOrName: string | Promise, position: BigNumberish | Promise, blockTag?: BlockTag | Promise): Promise { return this._provider.getStorageAt(addressOrName, position); } + sendTransaction(signedTransaction: string | Promise): Promise { return this._provider.sendTransaction(signedTransaction); } - _getFilter(filter: Filter | FilterByBlockHash | Promise): Promise { - return this._provider._getFilter(filter); - } - _getAddress(addressOrName: string | Promise): Promise { - return this._provider._getAddress(addressOrName) - } - _getBlock(blockHashOrBlockTag: BlockTag | Promise, includeTransactions?: boolean): Promise { - return this._provider._getBlock(blockHashOrBlockTag, includeTransactions) - } + getBlock(blockHashOrBlockTag: BlockTag | Promise): Promise { return this._provider.getBlock(blockHashOrBlockTag); } + getBlockWithTransactions(blockHashOrBlockTag: BlockTag | Promise): Promise { return this._provider.getBlockWithTransactions(blockHashOrBlockTag); } + getTransaction(transactionHash: string | Promise): Promise { return this._provider.getTransaction(transactionHash); } + getTransactionReceipt(transactionHash: string | Promise): Promise { return this._provider.getTransactionReceipt(transactionHash); } + getLogs(filter: Filter | FilterByBlockHash | Promise): Promise { return this._provider.getLogs(filter); } + getEtherPrice(): Promise { return this._provider.getEtherPrice(); } - _getBlockTag(blockTag: BlockTag | Promise): Promise { - return this._provider._getBlockTag(blockTag); - } + getResolver(name: string): Promise { return this._provider.getResolver(name); } - _getResolver(name: string): Promise { - return this._provider._getResolver(name); - } + resolveName(name: string | Promise): Promise { return this._provider.resolveName(name); } + lookupAddress(address: string | Promise): Promise { return this._provider.lookupAddress(address); } + perform(method: string, params: any): Promise { return this._provider.perform(method, params); } - _startEvent(event: Event): void { - return this._provider._startEvent(event); - } - _stopEvent(event: Event): void { - return this._provider._stopEvent(event); - } - _addEventListener(eventName: EventType, listener: Listener, once: boolean): this { - this._provider._addEventListener(eventName, listener, once); - return this; - } + on(eventName: EventType, listener: Listener): this { this._provider.on(eventName, listener); return this; } + + off(eventName: EventType, listener?: Listener): this { + this._provider.off(eventName, listener); + return this; + } + once(eventName: EventType, listener: Listener): this { this._provider.once(eventName, listener); return this; } + emit(eventName: EventType, ...args: any[]): boolean { return this._provider.emit(eventName, ...args); } + listenerCount(eventName?: EventType): number { return this._provider.listenerCount(eventName); } + listeners(eventName?: EventType): Listener[] { return this._provider.listeners(eventName); } - off(eventName: EventType, listener?: Listener): this { - this._provider.off(eventName, listener); - return this; - } + removeAllListeners(eventName?: EventType): this { this._provider.removeAllListeners(eventName); return this; } + addListener(eventName: EventType, listener: Listener): Provider { return this._provider.addListener(eventName, listener); } + removeListener(eventName: EventType, listener: Listener): Provider { return this._provider.removeListener(eventName, listener); } - _waitForTransaction(transactionHash: string, confirmations: number, timeout: number, replaceable: { data: string; from: string; nonce: number; to: string; value: BigNumber; startBlock: number; }): Promise { - throw new Error("Method not implemented."); - } } \ No newline at end of file diff --git a/modules/utils/src/eth.ts b/modules/utils/src/eth.ts index 7cd2db4a0..6c6282a20 100644 --- a/modules/utils/src/eth.ts +++ b/modules/utils/src/eth.ts @@ -1,17 +1,22 @@ import { ChainRpcProviders, HydratedProviders } from "@connext/vector-types"; import { Provider } from "@ethersproject/abstract-provider"; import { BigNumber } from "@ethersproject/bignumber"; -import { JsonRpcProvider, StaticJsonRpcProvider } from "@ethersproject/providers"; +import { JsonRpcProvider } from "@ethersproject/providers"; import { ChainRpcProvider } from "@connext/vector-types"; const classicProviders = ["https://www.ethercluster.com/etc"]; const classicChainIds = [61]; const minGasPrice = BigNumber.from(1_000); -export const getEthProvider = (providerUrl: string, chainId?: number): JsonRpcProvider => - new JsonRpcProvider( - providerUrl, - classicProviders.includes(providerUrl) || classicChainIds.includes(chainId) ? "classic" : undefined, +export const getEthProvider = (providerUrl: string, chainId?: number): ChainRpcProvider => + new ChainRpcProvider( + chainId, + [ + new JsonRpcProvider( + providerUrl, + classicProviders.includes(providerUrl) || classicChainIds.includes(chainId) ? "classic" : undefined, + ) + ] ); // xDai hardcoded their gas price to 0 but it's not actually zero.. From afcdbb813ed1a02f2d88cc03c91136e8adb8994a Mon Sep 17 00:00:00 2001 From: Jake Kidd Date: Wed, 26 May 2021 21:10:58 -0700 Subject: [PATCH 11/11] fix : send method to use Promise race --- modules/types/src/network.ts | 38 ++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/modules/types/src/network.ts b/modules/types/src/network.ts index 4dbb9ea56..74176d4ce 100644 --- a/modules/types/src/network.ts +++ b/modules/types/src/network.ts @@ -34,6 +34,7 @@ export class ChainRpcProvider implements Provider { readonly providerUrls: string[]; readonly _provider: JsonRpcProvider | FallbackProvider; + RPC_TIMEOUT: number = 10_000; _isProvider: boolean = true; _networkPromise: Promise; _network: Network; @@ -86,18 +87,30 @@ export class ChainRpcProvider implements Provider { if (this._provider instanceof JsonRpcProvider) { return (this._provider as JsonRpcProvider).send(method, params); } else { - const providers = (this._provider as FallbackProvider).providerConfigs.map(p => p.provider); - return new Promise((resolve, reject) => { - var errors: any[] = []; - for (let i = 0; i < providers.length; i++) { - try { - resolve((providers[i] as JsonRpcProvider).send(method, params)); - } catch (e) { - errors.push(e); - } - } - reject(errors); - }); + const providers = (this._provider as FallbackProvider).providerConfigs.map(p => p.provider as JsonRpcProvider); + var errors: Error[] = []; + return Promise.race( + providers.map(provider => { + return new Promise(async (resolve, reject) => { + try { + const result = await provider.send(method, params); + resolve(result); + } catch (e) { + errors.push(e); + // If this was the last request, and we've gotten all errors, let's reject. + if (errors.length === providers.length) { + reject(errors); + } + } + }); + }) + .concat( + // Ten second timeout to reject with errors. + new Promise((_, reject) => { + setTimeout(() => reject(errors), this.RPC_TIMEOUT) + }) + ) + ); } } @@ -112,6 +125,7 @@ export class ChainRpcProvider implements Provider { poll(): Promise { return this._provider.poll(); } + resetEventsBlock(blockNumber: number): void { return this._provider.resetEventsBlock(blockNumber); }