Skip to content

Commit

Permalink
feat: add ability to mock chain ids in config (#891)
Browse files Browse the repository at this point in the history
  • Loading branch information
james-a-morris authored Aug 18, 2023
1 parent cf4fbb4 commit 913dcff
Show file tree
Hide file tree
Showing 12 changed files with 194 additions and 20 deletions.
27 changes: 27 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -222,3 +222,30 @@ RELAYER_TOKENS='["0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", "0xA0b86991c6218b
# },
# },
#}'

################################################################################
########################### Testing Configuration ##############################
################################################################################

# Note: This section is intended for advanced users only. It ONLY serves to aid
# developers in testing the relayer bot. It is NOT intended for any relayer
# or dataworker operator to use in production. It's recommended to consult
# the #relayers channel within the Across Discord server before making any
# changes to this section. See https://discord.across.to.
#
# Note: PLEASE DO NOT USE THIS SECTION IN PRODUCTION. IT IS FOR TESTING ONLY.

# Used to include an arbitrary chain ID to the Across Config Store for testing
# new chain additions. Including the block number and the chain ID will cause
# the relayer to include the chain ID in the Config Store. This is useful for
# testing new chains before they are officially supported by the relayer.
# Note: This logic does apply sanitizations so it is not possible to inject
# a new change prior to the most previous chain ID update within the
# Config Store.
#INJECT_CHAIN_ID_INCLUSION='{"blockNumber":17876743,"chainId":8453}'

# Used to force a proposal to be attempted regardless of whether there is a
# pending proposal. This is useful for testing the proposal logic.
# Note: This logic ONLY works if `SEND_PROPOSALS` is set to false.
#FORCE_PROPOSAL=false

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
},
"dependencies": {
"@across-protocol/contracts-v2": "2.4.2",
"@across-protocol/sdk-v2": "0.15.16",
"@across-protocol/sdk-v2": "0.15.18",
"@arbitrum/sdk": "^3.1.3",
"@defi-wonderland/smock": "^2.3.5",
"@eth-optimism/sdk": "^3.1.0",
Expand Down
116 changes: 114 additions & 2 deletions src/clients/ConfigStoreClient.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,117 @@
import { clients, utils } from "@across-protocol/sdk-v2";
import { clients, constants, utils } from "@across-protocol/sdk-v2";
import { Contract, EventSearchConfig, MakeOptional, isDefined, sortEventsDescending, winston } from "../utils";
export const { UBA_MIN_CONFIG_STORE_VERSION } = utils;
export const GLOBAL_CONFIG_STORE_KEYS = clients.GLOBAL_CONFIG_STORE_KEYS;

export class ConfigStoreClient extends clients.AcrossConfigStoreClient {}
export class ConfigStoreClient extends clients.AcrossConfigStoreClient {
private readonly injectedChain:
| {
chainId: number;
blockNumber: number;
}
| undefined;

constructor(
readonly logger: winston.Logger,
readonly configStore: Contract,
readonly eventSearchConfig: MakeOptional<EventSearchConfig, "toBlock"> = { fromBlock: 0, maxBlockLookBack: 0 },
readonly configStoreVersion: number
) {
super(logger, configStore, eventSearchConfig, configStoreVersion);

const injectedChains = process.env.INJECT_CHAIN_ID_INCLUSION;
if (isDefined(injectedChains)) {
// Attempt to parse the injected chains
const { chainId: injectedChainId, blockNumber: injectedBlockNumber } = JSON.parse(injectedChains);
// Sanity check to verify that the chain id & block number are positive integers
if (!utils.isPositiveInteger(injectedChainId) || !utils.isPositiveInteger(injectedBlockNumber)) {
this.logger.warn({
at: "ConfigStore[Relayer]#constructor",
message: `Invalid injected chain id inclusion: ${injectedChains}`,
});
}
this.injectedChain = {
chainId: injectedChainId,
blockNumber: injectedBlockNumber,
};
}
}

async update(): Promise<void> {
// We know that as we move forward in time, the injected chain id inclusion will
// eventually outdate the latest block number. Therefore, we want to remove the
// injected chain id inclusion from the chain id indices updates before we call
// the super update function. This is to prevent the injected chain id inclusion
// from issuing an error. We will re-add the injected chain id inclusion after
// in the overloaded _.update() function.
if (isDefined(this.injectedChain)) {
// Track the initial length of the chain id indices updates
const initialLength = this.chainIdIndicesUpdates.length;
// Because this chain is `injected` we know that it doesn't occur
// on-chain, and therefore we just need to remove it altogether
// wherever an instance of it appears.
this.chainIdIndicesUpdates = this.chainIdIndicesUpdates.filter(
({ value }) => !value.includes(this.injectedChain.chainId)
);
if (this.chainIdIndicesUpdates.length !== initialLength) {
this.logger.debug({
at: "ConfigStore[Relayer]#update",
message: "Removed injected chain id inclusion from chain id indices updates",
injectedChain: this.injectedChain,
});
}
}
await super.update();

if (isDefined(this.injectedChain)) {
const { chainId: injectedChainId, blockNumber: injectedBlockNumber } = this.injectedChain;
// Sanity check to ensure that this event doesn't happen in the future
if (injectedBlockNumber > this.latestBlockNumber) {
this.logger.debug({
at: "ConfigStore[Relayer]#update",
message: `Injected block number ${injectedBlockNumber} is greater than the latest block number ${this.latestBlockNumber}`,
});
return;
}
// Sanity check to ensure that the injected chain id is not already included
if (this.chainIdIndicesUpdates.some(({ value }) => value.includes(injectedChainId))) {
this.logger.debug({
at: "ConfigStore[Relayer]#update",
message: `Injected chain id ${injectedChainId} is already included`,
});
return;
}

// Partially create the meta-data information regarding the injected chain id inclusion
const partialChainIdIndicesUpdate = {
blockNumber: injectedBlockNumber,
transactionIndex: 0,
logIndex: 0,
transactionHash: "",
};

// We need to now resolve the last chain id indices update
const lastChainIdIndicesUpdate = sortEventsDescending(this.chainIdIndicesUpdates)?.[0];
if (!isDefined(lastChainIdIndicesUpdate)) {
this.chainIdIndicesUpdates.push({
...partialChainIdIndicesUpdate,
value: [...constants.PROTOCOL_DEFAULT_CHAIN_ID_INDICES, injectedChainId],
});
} else {
// Sanity check to ensure that the injected chain id is after the last chain id indices update
if (lastChainIdIndicesUpdate.blockNumber > injectedBlockNumber) {
this.logger.debug({
at: "ConfigStore[Relayer]#update",
message: `Injected block number ${injectedBlockNumber} is before the last chain id indices update ${lastChainIdIndicesUpdate.blockNumber}`,
});
return;
}
// We can now add the injected chain id to the last chain id indices update
this.chainIdIndicesUpdates.push({
...partialChainIdIndicesUpdate,
value: [...lastChainIdIndicesUpdate.value, injectedChainId],
});
}
}
}
}
3 changes: 1 addition & 2 deletions src/clients/HubPoolClient.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { clients } from "@across-protocol/sdk-v2";
import { ConfigStoreClient } from "./";
import { Contract } from "ethers";
import winston from "winston";
import { MakeOptional, EventSearchConfig } from "../utils";
Expand All @@ -9,7 +8,7 @@ export class HubPoolClient extends clients.HubPoolClient {
constructor(
logger: winston.Logger,
hubPool: Contract,
configStoreClient: ConfigStoreClient,
configStoreClient: clients.AcrossConfigStoreClient,
deploymentBlock?: number,
chainId = 1,
eventSearchConfig: MakeOptional<EventSearchConfig, "toBlock"> = { fromBlock: 0, maxBlockLookBack: 0 }
Expand Down
3 changes: 2 additions & 1 deletion src/common/ClientHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
import { HubPoolClient, MultiCallerClient, ConfigStoreClient, SpokePoolClient } from "../clients";
import { CommonConfig } from "./Config";
import { SpokePoolClientsByChain } from "../interfaces";
import { clients } from "@across-protocol/sdk-v2";

export interface Clients {
hubPoolClient: HubPoolClient;
Expand Down Expand Up @@ -102,7 +103,7 @@ export async function constructSpokePoolClientsWithLookback(
* @returns number[] List of enabled spoke pool chains.
*/
function getEnabledChainsInBlockRange(
configStoreClient: ConfigStoreClient,
configStoreClient: clients.AcrossConfigStoreClient,
spokePoolChainsOverride: number[],
mainnetStartBlock: number,
mainnetEndBlock?: number
Expand Down
2 changes: 1 addition & 1 deletion src/common/Constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export const CHAIN_MAX_BLOCK_LOOKBACK = {
137: 3490,
288: 4990,
324: 10000,
8453: 10000,
8453: 1500,
42161: 10000,
// Testnets:
5: 10000,
Expand Down
12 changes: 7 additions & 5 deletions src/dataworker/Dataworker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ export class Dataworker {
readonly tokenTransferThreshold: BigNumberForToken = {},
readonly blockRangeEndBlockBuffer: { [chainId: number]: number } = {},
readonly spokeRootsLookbackCount = 0,
readonly bufferToPropose = 0
readonly bufferToPropose = 0,
readonly forceProposal = false
) {
if (
maxRefundCountOverride !== undefined ||
Expand Down Expand Up @@ -277,7 +278,7 @@ export class Dataworker {
if (!hubPoolClient.isUpdated || !hubPoolClient.latestBlockNumber) {
throw new Error("HubPoolClient not updated");
}
if (hubPoolClient.hasPendingProposal()) {
if (!this.forceProposal && hubPoolClient.hasPendingProposal()) {
this.logger.debug({ at: "Dataworker#propose", message: "Has pending proposal, cannot propose" });
return;
}
Expand Down Expand Up @@ -1645,17 +1646,18 @@ export class Dataworker {

if (!valid) {
this.logger.error({
at: "Dataworke#executePoolRebalanceLeaves",
at: "Dataworker#executePoolRebalanceLeaves",
message: "Found invalid proposal after challenge period!",
reason,
e: reason,
notificationPath: "across-error",
});
return;
}

if (valid && !expectedTrees) {
this.logger.error({
at: "Dataworke#executePoolRebalanceLeaves",
at: "Dataworker#executePoolRebalanceLeaves",
message:
"Found valid proposal, but no trees could be generated. This probably means that the proposal was never evaluated during liveness due to an odd block range!",
reason,
Expand Down Expand Up @@ -1684,7 +1686,7 @@ export class Dataworker {
// Exit early if challenge period timestamp has not passed:
if (this.clients.hubPoolClient.currentTime <= pendingRootBundle.challengePeriodEndTimestamp) {
this.logger.debug({
at: "Dataworke#executePoolRebalanceLeaves",
at: "Dataworker#executePoolRebalanceLeaves",
message: `Challenge period not passed, cannot execute until ${pendingRootBundle.challengePeriodEndTimestamp}`,
expirationTime: pendingRootBundle.challengePeriodEndTimestamp,
});
Expand Down
12 changes: 12 additions & 0 deletions src/dataworker/DataworkerConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ export class DataworkerConfig extends CommonConfig {
readonly executorEnabled: boolean;
readonly finalizerEnabled: boolean;

// This variable can be toggled to bypass the proposer logic and always attempt to propose
// a bundle. This is useful for testing the disputer logic.
readonly forcePropose: boolean;

// These variables can be toggled to choose whether the bot will submit transactions created
// by each function. For example, setting `sendingDisputesEnabled=false` but `disputerEnabled=true`
// means that the disputer logic will be run but won't send disputes on-chain.
Expand Down Expand Up @@ -46,6 +50,7 @@ export class DataworkerConfig extends CommonConfig {
BUFFER_TO_PROPOSE,
DATAWORKER_FAST_LOOKBACK_COUNT,
DATAWORKER_FAST_START_BUNDLE,
FORCE_PROPOSAL,
} = env;
super(env);

Expand Down Expand Up @@ -84,6 +89,13 @@ export class DataworkerConfig extends CommonConfig {
this.sendingExecutionsEnabled = SEND_EXECUTIONS === "true";
this.finalizerEnabled = FINALIZER_ENABLED === "true";

this.forcePropose = FORCE_PROPOSAL === "true";

// We NEVER want to force propose if the proposer is enabled.
if (this.sendingProposalsEnabled) {
assert(!this.forcePropose, "Cannot force propose if sending proposals is enabled");
}

// `dataworkerFastLookbackCount` affects how far we fetch events from, modifying the search config's 'fromBlock'.
// Set to 0 to load all events, but be careful as this will cause the Dataworker to take 30+ minutes to complete.
// The average bundle frequency is 4-6 bundles per day so 16 bundles is a reasonable default
Expand Down
3 changes: 2 additions & 1 deletion src/dataworker/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ export async function createDataworker(
config.tokenTransferThresholdOverride,
config.blockRangeEndBlockBuffer,
config.spokeRootsLookbackCount,
config.bufferToPropose
config.bufferToPropose,
config.forcePropose
);

return {
Expand Down
3 changes: 2 additions & 1 deletion src/scripts/testUBAClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import * as sdk from "@across-protocol/sdk-v2";
import { isDefined } from "@uma/financial-templates-lib/dist/types";
import { createDataworker } from "../dataworker";
import { RedisCache } from "../caching/RedisCache";
import { ConfigStoreClient } from "../clients";

config();
let logger: winston.Logger;
Expand Down Expand Up @@ -75,7 +76,7 @@ export async function testUBAClient(_logger: winston.Logger, baseSigner: Wallet)
configStoreClient.getEnabledChains()
);
mockConfigStoreClient.setUBAActivationBlock(mockedUBAActivationBlock);
clients.configStoreClient = mockConfigStoreClient;
clients.configStoreClient = mockConfigStoreClient as unknown as ConfigStoreClient;
clients.hubPoolClient.configStoreClient = mockConfigStoreClient;
}
await updateClients(clients, config);
Expand Down
2 changes: 2 additions & 0 deletions src/utils/ExecutionUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export async function processCrash(
logger.error({
at: `${fileName}#index`,
message: `There was an execution error! ${pollingDelay != 0 ? "Re-running loop" : ""}`,
reason: error,
e: error,
error,
notificationPath: "across-error",
});
Expand Down
29 changes: 23 additions & 6 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"@uma/common" "^2.17.0"
hardhat "^2.9.3"

"@across-protocol/contracts-v2@2.4.2", "@across-protocol/contracts-v2@^2.4.2":
"@across-protocol/contracts-v2@2.4.2":
version "2.4.2"
resolved "https://registry.yarnpkg.com/@across-protocol/contracts-v2/-/contracts-v2-2.4.2.tgz#4799c84abfb6fc3c9b13c48ffb9899f20ab867c2"
integrity sha512-4KY3bNrF2YNM4RCbbdHqze+E4AxWRHvQe2Yu/UmHKOr7h3zSU1PBdjeuV4dRtnsvU9XMNRQ4jGxf/cVo1EmtlQ==
Expand All @@ -28,6 +28,23 @@
"@uma/core" "^2.56.0"
zksync-web3 "^0.14.3"

"@across-protocol/contracts-v2@^2.4.3":
version "2.4.3"
resolved "https://registry.yarnpkg.com/@across-protocol/contracts-v2/-/contracts-v2-2.4.3.tgz#9cc0b1f52b4f819b32ca1524ef84af9dfed8687a"
integrity sha512-NT5zBhTMYk7jUgZ6Q+xXz0p3ukXth8F6lBTiNCIrrzFSBl5JLVrhk00+TIIIOfwtpGSiG+MGkKuwCOKWMhwOMg==
dependencies:
"@defi-wonderland/smock" "^2.3.4"
"@eth-optimism/contracts" "^0.5.40"
"@ethersproject/abstract-provider" "5.7.0"
"@ethersproject/abstract-signer" "5.7.0"
"@ethersproject/bignumber" "5.7.0"
"@openzeppelin/contracts" "4.9.2"
"@openzeppelin/contracts-upgradeable" "4.9.2"
"@uma/common" "^2.34.0"
"@uma/contracts-node" "^0.4.17"
"@uma/core" "^2.56.0"
zksync-web3 "^0.14.3"

"@across-protocol/contracts@^0.1.4":
version "0.1.4"
resolved "https://registry.yarnpkg.com/@across-protocol/contracts/-/contracts-0.1.4.tgz#64b3d91e639d2bb120ea94ddef3d160967047fa5"
Expand All @@ -37,13 +54,13 @@
"@openzeppelin/contracts" "4.1.0"
"@uma/core" "^2.18.0"

"@across-protocol/sdk-v2@0.15.16":
version "0.15.16"
resolved "https://registry.yarnpkg.com/@across-protocol/sdk-v2/-/sdk-v2-0.15.16.tgz#913c5c3eb35737af20da6e0e883459bca40d2f15"
integrity sha512-7UlKx8q5cdIVRC4FA+H95CkRLRsJuTUdBp3LMoy1kKIsxpmc3N9i/OQxymL4pmBeMBMX3UYm+AmaI8PVgkPJeg==
"@across-protocol/sdk-v2@0.15.18":
version "0.15.18"
resolved "https://registry.yarnpkg.com/@across-protocol/sdk-v2/-/sdk-v2-0.15.18.tgz#5334e36fda7cf1cab753549fd309ff2595155950"
integrity sha512-Wcf6A/zy8SG+UXcGDsDI2k5jVMbvlHLqG/iJ3H9YgQ5A5KeRdw5H8mLTdXavTN86aIZCD1Ls5RskCVT6SujoIA==
dependencies:
"@across-protocol/across-token" "^1.0.0"
"@across-protocol/contracts-v2" "^2.4.2"
"@across-protocol/contracts-v2" "^2.4.3"
"@eth-optimism/sdk" "^2.1.0"
"@pinata/sdk" "^2.1.0"
"@uma/sdk" "^0.34.1"
Expand Down

0 comments on commit 913dcff

Please sign in to comment.