Skip to content

improve: implement a common interface in sdk providers #932

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions src/clients/BundleDataClient/BundleDataClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import {
verifyFillRepayment,
} from "./utils";
import { UNDEFINED_MESSAGE_HASH } from "../../constants";
import { CrosschainProvider } from "../../providers";

// max(uint256) - 1
export const INFINITE_FILL_DEADLINE = bnUint32Max;
Expand Down Expand Up @@ -158,7 +159,7 @@ function updateBundleSlowFills(dict: BundleSlowFills, deposit: V3DepositWithBloc
}

// @notice Shared client for computing data needed to construct or validate a bundle.
export class BundleDataClient {
export class BundleDataClient<P extends CrosschainProvider> {
private loadDataCache: DataCache = {};
private arweaveDataCache: Record<string, Promise<LoadDataReturnValue | undefined>> = {};

Expand All @@ -167,8 +168,8 @@ export class BundleDataClient {
// eslint-disable-next-line no-useless-constructor
constructor(
readonly logger: winston.Logger,
readonly clients: Clients,
readonly spokePoolClients: { [chainId: number]: SpokePoolClient },
readonly clients: Clients<P>,
readonly spokePoolClients: { [chainId: number]: SpokePoolClient<P> },
readonly chainIdListForBundleEvaluationBlockNumbers: number[],
readonly blockRangeEndBlockBuffer: { [chainId: number]: number } = {}
) {}
Expand Down Expand Up @@ -520,7 +521,7 @@ export class BundleDataClient {

// @dev This helper function should probably be moved to the InventoryClient
getExecutedRefunds(
spokePoolClient: SpokePoolClient,
spokePoolClient: SpokePoolClient<P>,
relayerRefundRoot: string
): {
[tokenAddress: string]: {
Expand Down Expand Up @@ -628,7 +629,7 @@ export class BundleDataClient {
// on deprecated spoke pools.
async loadData(
blockRangesForChains: number[][],
spokePoolClients: SpokePoolClientsByChain,
spokePoolClients: SpokePoolClientsByChain<P>,
attemptArweaveLoad = false
): Promise<LoadDataReturnValue> {
const key = JSON.stringify(blockRangesForChains);
Expand All @@ -653,7 +654,7 @@ export class BundleDataClient {

private async loadDataFromScratch(
blockRangesForChains: number[][],
spokePoolClients: SpokePoolClientsByChain
spokePoolClients: SpokePoolClientsByChain<P>
): Promise<LoadDataReturnValue> {
let start = performance.now();
const key = JSON.stringify(blockRangesForChains);
Expand Down Expand Up @@ -1509,7 +1510,7 @@ export class BundleDataClient {

protected async findMatchingFillEvent(
deposit: DepositWithBlock,
spokePoolClient: SpokePoolClient
spokePoolClient: SpokePoolClient<P>
): Promise<FillWithBlock | undefined> {
return await findFillEvent(
spokePoolClient.spokePool,
Expand All @@ -1522,7 +1523,7 @@ export class BundleDataClient {
async getBundleBlockTimestamps(
chainIds: number[],
blockRangesForChains: number[][],
spokePoolClients: SpokePoolClientsByChain
spokePoolClients: SpokePoolClientsByChain<P>
): Promise<{ [chainId: string]: number[] }> {
return Object.fromEntries(
(
Expand Down
5 changes: 3 additions & 2 deletions src/clients/BundleDataClient/utils/DataworkerUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
} from "./PoolRebalanceUtils";
import { AcrossConfigStoreClient } from "../../AcrossConfigStoreClient";
import { HubPoolClient } from "../../HubPoolClient";
import { CrosschainProvider } from "../../../providers";
import { buildPoolRebalanceLeafTree } from "./MerkleTreeUtils";

// and expired deposits.
Expand Down Expand Up @@ -113,15 +114,15 @@ export function getEndBlockBuffers(
return chainIdListForBundleEvaluationBlockNumbers.map((chainId: number) => blockRangeEndBlockBuffer[chainId] ?? 0);
}

export function _buildPoolRebalanceRoot(
export function _buildPoolRebalanceRoot<P extends CrosschainProvider>(
latestMainnetBlock: number,
mainnetBundleEndBlock: number,
bundleV3Deposits: BundleDepositsV3,
bundleFillsV3: BundleFillsV3,
bundleSlowFillsV3: BundleSlowFills,
unexecutableSlowFills: BundleExcessSlowFills,
expiredDepositsToRefundV3: ExpiredDepositsToRefundV3,
clients: { hubPoolClient: HubPoolClient; configStoreClient: AcrossConfigStoreClient },
clients: { hubPoolClient: HubPoolClient<P>; configStoreClient: AcrossConfigStoreClient },
maxL1TokenCountOverride?: number
): PoolRebalanceRoot {
// Running balances are the amount of tokens that we need to send to each SpokePool to pay for all instant and
Expand Down
13 changes: 7 additions & 6 deletions src/clients/BundleDataClient/utils/FillUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import { providers } from "ethers";
import { Deposit, DepositWithBlock, Fill, FillWithBlock } from "../../../interfaces";
import { getBlockRangeForChain, isSlowFill, isValidEvmAddress, isDefined, chainIsEvm } from "../../../utils";
import { HubPoolClient } from "../../HubPoolClient";
import { CrosschainProvider } from "../../../providers";

export function getRefundInformationFromFill(
export function getRefundInformationFromFill<P extends CrosschainProvider>(
fill: Fill,
hubPoolClient: HubPoolClient,
hubPoolClient: HubPoolClient<P>,
blockRangesForChains: number[][],
chainIdListForBundleEvaluationBlockNumbers: number[],
fromLiteChain: boolean
Expand Down Expand Up @@ -53,10 +54,10 @@ export function getRepaymentChainId(fill: Fill, matchedDeposit: Deposit): number
return matchedDeposit.fromLiteChain ? matchedDeposit.originChainId : fill.repaymentChainId;
}

export function forceDestinationRepayment(
export function forceDestinationRepayment<P extends CrosschainProvider>(
repaymentChainId: number,
matchedDeposit: Deposit & { quoteBlockNumber: number },
hubPoolClient: HubPoolClient
hubPoolClient: HubPoolClient<P>
): boolean {
if (!matchedDeposit.fromLiteChain) {
try {
Expand All @@ -79,11 +80,11 @@ export function forceDestinationRepayment(

// Verify that a fill sent to an EVM chain has a 20 byte address. If the fill does not, then attempt
// to repay the `msg.sender` of the relay transaction. Otherwise, return undefined.
export async function verifyFillRepayment(
export async function verifyFillRepayment<P extends CrosschainProvider>(
_fill: FillWithBlock,
destinationChainProvider: providers.Provider,
matchedDeposit: DepositWithBlock,
hubPoolClient: HubPoolClient
hubPoolClient: HubPoolClient<P>
): Promise<FillWithBlock | undefined> {
const fill = _.cloneDeep(_fill);

Expand Down
15 changes: 8 additions & 7 deletions src/clients/BundleDataClient/utils/PoolRebalanceUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { BigNumber, bnZero, compareAddresses } from "../../../utils";
import { HubPoolClient } from "../../HubPoolClient";
import { V3DepositWithBlock } from "./shims";
import { AcrossConfigStoreClient } from "../../AcrossConfigStoreClient";
import { CrosschainProvider } from "../../../providers";

export type PoolRebalanceRoot = {
runningBalances: RunningBalances;
Expand All @@ -17,11 +18,11 @@ export type PoolRebalanceRoot = {
// when evaluating pending root bundle. The block end numbers must be less than the latest blocks for each chain ID
// (because we can't evaluate events in the future), and greater than the expected start blocks, which are the
// greater of 0 and the latest bundle end block for an executed root bundle proposal + 1.
export function getWidestPossibleExpectedBlockRange(
export function getWidestPossibleExpectedBlockRange<P extends CrosschainProvider>(
chainIdListForBundleEvaluationBlockNumbers: number[],
spokeClients: { [chainId: number]: SpokePoolClient },
spokeClients: { [chainId: number]: SpokePoolClient<P> },
endBlockBuffers: number[],
clients: Clients,
clients: Clients<P>,
latestMainnetBlock: number,
enabledChains: number[]
): number[][] {
Expand Down Expand Up @@ -140,10 +141,10 @@ export function updateRunningBalance(
}
}

export function addLastRunningBalance(
export function addLastRunningBalance<P extends CrosschainProvider>(
latestMainnetBlock: number,
runningBalances: RunningBalances,
hubPoolClient: HubPoolClient
hubPoolClient: HubPoolClient<P>
): void {
Object.keys(runningBalances).forEach((repaymentChainId) => {
Object.keys(runningBalances[Number(repaymentChainId)]).forEach((l1TokenAddress) => {
Expand All @@ -159,9 +160,9 @@ export function addLastRunningBalance(
});
}

export function updateRunningBalanceForDeposit(
export function updateRunningBalanceForDeposit<P extends CrosschainProvider>(
runningBalances: RunningBalances,
hubPoolClient: HubPoolClient,
hubPoolClient: HubPoolClient<P>,
deposit: V3DepositWithBlock,
updateAmount: BigNumber
): void {
Expand Down
9 changes: 6 additions & 3 deletions src/clients/HubPoolClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import {
} from "../utils";
import { AcrossConfigStoreClient as ConfigStoreClient } from "./AcrossConfigStoreClient/AcrossConfigStoreClient";
import { BaseAbstractClient, isUpdateFailureReason, UpdateFailureReason } from "./BaseAbstractClient";
import { CrosschainProvider } from "../providers";

type HubPoolUpdateSuccess = {
success: true;
Expand Down Expand Up @@ -78,7 +79,7 @@ export type LpFeeRequest = Pick<Deposit, "originChainId" | "inputToken" | "input
paymentChainId?: number;
};

export class HubPoolClient extends BaseAbstractClient {
export class HubPoolClient<P extends CrosschainProvider> extends BaseAbstractClient {
// L1Token -> destinationChainId -> destinationToken
protected l1TokensToDestinationTokens: L1TokensToDestinationTokens = {};
protected l1Tokens: L1Token[] = []; // L1Tokens and their associated info.
Expand All @@ -94,7 +95,7 @@ export class HubPoolClient extends BaseAbstractClient {
protected pendingRootBundle: PendingRootBundle | undefined;

public currentTime: number | undefined;
public readonly blockFinder: BlockFinder;
public readonly blockFinder: BlockFinder<P>;

constructor(
readonly logger: winston.Logger,
Expand All @@ -117,7 +118,9 @@ export class HubPoolClient extends BaseAbstractClient {
this.latestBlockSearched = Math.min(deploymentBlock - 1, 0);
this.firstBlockToSearch = eventSearchConfig.fromBlock;

const provider = this.hubPool.provider;
// Casting to a generic type which extends crosschain provider is safe here iff the hub pool client is using one of the provider
// wrappers exposed by this SDK.
const provider = this.hubPool.provider as unknown as P;
this.blockFinder = new BlockFinder(provider);
}

Expand Down
5 changes: 3 additions & 2 deletions src/clients/SpokePoolClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import {
} from "../utils/SpokeUtils";
import { BaseAbstractClient, isUpdateFailureReason, UpdateFailureReason } from "./BaseAbstractClient";
import { HubPoolClient } from "./HubPoolClient";
import { CrosschainProvider } from "../providers";
import { AcrossConfigStoreClient } from "./AcrossConfigStoreClient";
import { getRepaymentChainId, forceDestinationRepayment } from "./BundleDataClient/utils/FillUtils";

Expand All @@ -73,7 +74,7 @@ export type SpokePoolUpdate = SpokePoolUpdateSuccess | SpokePoolUpdateFailure;
* SpokePoolClient is a client for the SpokePool contract. It is responsible for querying the SpokePool contract
* for events and storing them in memory. It also provides some convenience methods for querying the stored events.
*/
export class SpokePoolClient extends BaseAbstractClient {
export class SpokePoolClient<P extends CrosschainProvider> extends BaseAbstractClient {
protected currentTime = 0;
protected depositHashes: { [depositHash: string]: DepositWithBlock } = {};
protected duplicateDepositHashes: { [depositHash: string]: DepositWithBlock[] } = {};
Expand Down Expand Up @@ -102,7 +103,7 @@ export class SpokePoolClient extends BaseAbstractClient {
readonly logger: winston.Logger,
readonly spokePool: Contract,
// Can be excluded. This disables some deposit validation.
readonly hubPoolClient: HubPoolClient | null,
readonly hubPoolClient: HubPoolClient<P> | null,
readonly chainId: number,
public deploymentBlock: number,
eventSearchConfig: MakeOptional<EventSearchConfig, "toBlock"> = { fromBlock: 0, maxBlockLookBack: 0 }
Expand Down
3 changes: 2 additions & 1 deletion src/clients/mocks/MockHubPoolClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { L1Token, Log, PendingRootBundle, RealizedLpFee } from "../../interfaces
import { AcrossConfigStoreClient as ConfigStoreClient } from "../AcrossConfigStoreClient";
import { HubPoolClient, HubPoolUpdate, LpFeeRequest } from "../HubPoolClient";
import { EventManager, EventOverrides, getEventManager } from "./MockEvents";
import { CrosschainProvider } from "../../providers";

const emptyRootBundle: PendingRootBundle = {
poolRebalanceRoot: "",
Expand All @@ -17,7 +18,7 @@ const emptyRootBundle: PendingRootBundle = {
proposalBlockNumber: undefined,
};

export class MockHubPoolClient extends HubPoolClient {
export class MockHubPoolClient<P extends CrosschainProvider> extends HubPoolClient<P> {
public rootBundleProposal = emptyRootBundle;
private realizedLpFeePct: BigNumber = bnZero;
private realizedLpFeePctOverride = false;
Expand Down
5 changes: 3 additions & 2 deletions src/clients/mocks/MockSpokePoolClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,11 @@ import { SpokePoolClient, SpokePoolUpdate } from "../SpokePoolClient";
import { HubPoolClient } from "../HubPoolClient";
import { EventManager, EventOverrides, getEventManager } from "./MockEvents";
import { AcrossConfigStoreClient } from "../AcrossConfigStoreClient";
import { CrosschainProvider } from "../../providers";

// This class replaces internal SpokePoolClient functionality, enabling
// the user to bypass on-chain queries and inject Log objects directly.
export class MockSpokePoolClient extends SpokePoolClient {
export class MockSpokePoolClient<P extends CrosschainProvider> extends SpokePoolClient<P> {
public eventManager: EventManager;
private destinationTokenForChainOverride: Record<number, string> = {};
// Allow tester to set the numberOfDeposits() returned by SpokePool at a block height.
Expand All @@ -47,7 +48,7 @@ export class MockSpokePoolClient extends SpokePoolClient {
spokePool: Contract,
chainId: number,
deploymentBlock: number,
opts: { hubPoolClient: HubPoolClient | null } = { hubPoolClient: null }
opts: { hubPoolClient: HubPoolClient<P> | null } = { hubPoolClient: null }
) {
super(logger, spokePool, opts.hubPoolClient, chainId, deploymentBlock);
this.latestBlockSearched = deploymentBlock;
Expand Down
5 changes: 3 additions & 2 deletions src/interfaces/BundleData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { HubPoolClient } from "../clients/HubPoolClient";
import { AcrossConfigStoreClient } from "../clients";
import { ArweaveClient } from "../caching";
import { BigNumber } from "../utils";
import { CrosschainProvider } from "../providers";

export type ExpiredDepositsToRefundV3 = {
[originChainId: number]: {
Expand Down Expand Up @@ -55,8 +56,8 @@ export type BundleData = LoadDataReturnValue & {
bundleBlockRanges: number[][];
};

export interface Clients {
hubPoolClient: HubPoolClient;
export interface Clients<P extends CrosschainProvider> {
hubPoolClient: HubPoolClient<P>;
configStoreClient: AcrossConfigStoreClient;
hubSigner?: Signer;
arweaveClient: ArweaveClient;
Expand Down
5 changes: 3 additions & 2 deletions src/interfaces/SpokePool.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { SortableEvent } from "./Common";
import { FilledV3RelayEvent, V3FundsDepositedEvent } from "../typechain";
import { SpokePoolClient } from "../clients";
import { CrosschainProvider } from "../providers";
import { BigNumber } from "../utils";
import { RelayerRefundLeaf } from "./HubPool";

Expand Down Expand Up @@ -122,6 +123,6 @@ export interface TokensBridged extends SortableEvent {
l2TokenAddress: string;
}

export interface SpokePoolClientsByChain {
[chainId: number]: SpokePoolClient;
export interface SpokePoolClientsByChain<P extends CrosschainProvider> {
[chainId: number]: SpokePoolClient<P>;
}
1 change: 1 addition & 0 deletions src/providers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ export * from "./speedProvider";
export * from "./constants";
export * from "./types";
export * from "./utils";
export * from "./interface";
export * as mocks from "./mockProvider";
export * from "./solana";
6 changes: 6 additions & 0 deletions src/providers/interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface CrosschainProvider {
send(method: string, params: Array<unknown>): Promise<unknown>;
getBlock(blockTag: number | string): Promise<unknown>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getTransactionCount is another one that seems to be common.

getNetworkId(): Promise<number>;
getBlockNumber(): Promise<number>;
}
9 changes: 8 additions & 1 deletion src/providers/rateLimitedProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import { ethers } from "ethers";
import { RateLimitTask } from "./utils";
import { getOriginFromURL } from "../utils/NetworkUtils";
import winston, { Logger } from "winston";
import { CrosschainProvider } from "./interface";

// This provider is a very small addition to the StaticJsonRpcProvider that ensures that no more than `maxConcurrency`
// requests are ever in flight. It uses the async/queue library to manage this.
export class RateLimitedProvider extends ethers.providers.StaticJsonRpcProvider {
export class RateLimitedProvider extends ethers.providers.StaticJsonRpcProvider implements CrosschainProvider {
// The queue object that manages the tasks.
private queue: QueueObject<RateLimitTask>;

Expand Down Expand Up @@ -93,4 +94,10 @@ export class RateLimitedProvider extends ethers.providers.StaticJsonRpcProvider
void this.queue.push(task);
});
}

// Returns the chain ID of the provider's network.
async getNetworkId(): Promise<number> {
const { chainId } = await this.getNetwork();
return chainId;
}
}
9 changes: 8 additions & 1 deletion src/providers/retryProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import { compareRpcResults, createSendErrorWithMessage, formatProviderError } fr
import { PROVIDER_CACHE_TTL } from "./constants";
import { JsonRpcError, RpcError } from "./types";
import { Logger } from "winston";
import { CrosschainProvider } from "./interface";

export class RetryProvider extends ethers.providers.StaticJsonRpcProvider {
export class RetryProvider extends ethers.providers.StaticJsonRpcProvider implements CrosschainProvider {
readonly providers: ethers.providers.StaticJsonRpcProvider[];
constructor(
params: ConstructorParameters<typeof ethers.providers.StaticJsonRpcProvider>[],
Expand Down Expand Up @@ -230,6 +231,12 @@ export class RetryProvider extends ethers.providers.StaticJsonRpcProvider {
return quorumResult;
}

// Returns the chain ID of the provider's network.
async getNetworkId(): Promise<number> {
const { chainId } = await this.getNetwork();
return chainId;
}

_validateResponse(method: string, _: Array<unknown>, response: unknown): boolean {
// Basic validation logic to start.
// Note: eth_getTransactionReceipt is ignored here because null responses are expected in the case that ethers is
Expand Down
Loading