Skip to content

Commit

Permalink
fix(across-relayer): more accurately determine a safe last block numb…
Browse files Browse the repository at this point in the history
…er to query (UMAprotocol#3856)

* fix: more accurately determine a safe block number to query

Signed-off-by: Matt Rice <[email protected]>

* WIP

Signed-off-by: Matt Rice <[email protected]>
  • Loading branch information
mrice32 authored Mar 23, 2022
1 parent 4f67e4d commit 02ea4c4
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ export class InsuredBridgeL2Client {
readonly chainId: number = 0,
readonly startingBlockNumber: number = 0,
readonly endingBlockNumber: number | null = null,
readonly redundantL2Web3s: Web3[] = [l2Web3]
readonly redundantL2Web3s: Web3[] = [l2Web3],
readonly maxBlockTimestampVariance: number = 300 // 5 minutes
) {
this.bridgeDepositBox = (new l2Web3.eth.Contract(
getAbi("BridgeDepositBox"),
Expand Down Expand Up @@ -65,7 +66,7 @@ export class InsuredBridgeL2Client {
// Define a config to bound the queries by.
const blockSearchConfig = {
fromBlock: this.firstBlockToSearch,
toBlock: this.endingBlockNumber || (await this.l2Web3.eth.getBlockNumber()),
toBlock: this.endingBlockNumber || (await this.getLatestBlockNumber()),
};
if (blockSearchConfig.fromBlock > blockSearchConfig.toBlock) {
this.logger.debug({
Expand Down Expand Up @@ -105,6 +106,30 @@ export class InsuredBridgeL2Client {
});
}

async getLatestBlockNumber(): Promise<number> {
const blocks = await Promise.all(this.redundantL2Web3s.map((web3) => web3.eth.getBlock("latest")));

// Sorts from smallest to largest.
const timestamps = blocks.map((block) => Number(block.timestamp));

// Throw if one of the blocks is too far back in time. This is used to detect providers that are hanging or blocked.
if (Math.max(...timestamps) - Math.min(...timestamps) > this.maxBlockTimestampVariance) {
const error = new Error(`Timestamps for chainId ${this.chainId} differ by too much time.`);

this.logger.error({
at: "InsuredBridgeL2Client",
message: "Timestamps from providers differ by too much time ⏰",
chainId: this.chainId,
error,
});
throw error;
}

const blockNumbers = blocks.map((block) => block.number);

return Math.min(...blockNumbers);
}

async getBridgeDepositBoxEvents(eventSearchOptions: EventSearchOptions, eventName: string): Promise<EventData[]> {
// Note: the primary l2Web3 is not used here because the main l2Web3 usually uses a combination of the list of
// redundant providers. Including both would mean calling the same provider(s) twice.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const sinon = require("sinon");

// Client to test
const { InsuredBridgeL2Client } = require("../../dist/clients/InsuredBridgeL2Client");
const { ZERO_ADDRESS } = require("@uma/common");
const { ZERO_ADDRESS, advanceBlockAndSetTime } = require("@uma/common");

// Helper contracts
const chainId = 10;
Expand Down Expand Up @@ -167,15 +167,17 @@ describe("InsuredBridgeL2Client", () => {
transports: [new SpyTransport({ level: "debug" }, { spy: spy })],
});

const ganacheWeb3 = new Web3(ganache.provider());

// Construct new client where we pass in a fallback L2 web3.
const clientWithFallbackWeb3s = new InsuredBridgeL2Client(
let clientWithFallbackWeb3s = new InsuredBridgeL2Client(
spyLogger,
web3,
depositBox.options.address,
chainId,
0,
null,
[web3, new Web3(ganache.provider())] // Ganache provider will be different from hardhat provider that is already
[web3, ganacheWeb3] // Ganache provider will be different from hardhat provider that is already
// connected to the BridgeDepositBox.
);

Expand Down Expand Up @@ -211,6 +213,17 @@ describe("InsuredBridgeL2Client", () => {
assert.equal(spy.getCall(-1).lastArg.eventName, "FundsDeposited");
}

clientWithFallbackWeb3s = new InsuredBridgeL2Client(
spyLogger,
web3,
depositBox.options.address,
chainId,
0,
(await web3.eth.getBlock("latest")).number, // Ensure that we query to the end.
[web3, ganacheWeb3] // Ganache provider will be different from hardhat provider that is already
// connected to the BridgeDepositBox.
);

// Update will throw an error.
try {
await clientWithFallbackWeb3s.update();
Expand All @@ -219,5 +232,28 @@ describe("InsuredBridgeL2Client", () => {
assert.equal(lastSpyLogLevel(spy), "error");
assert.isTrue(lastSpyLogIncludes(spy, "L2 RPC endpoint state disagreement"));
}

// Set the ganache time past the latest block time to generate a different error.
await advanceBlockAndSetTime(ganacheWeb3, Number((await web3.eth.getBlock("latest")).timestamp + 1000));

clientWithFallbackWeb3s = new InsuredBridgeL2Client(
spyLogger,
web3,
depositBox.options.address,
chainId,
0,
null, // Allow the client to determine how far we query.
[web3, ganacheWeb3] // Ganache provider will be different from hardhat provider that is already
// connected to the BridgeDepositBox.
);

// Update will throw an error.
try {
await clientWithFallbackWeb3s.update();
assert.isTrue(false);
} catch (e) {
assert.equal(lastSpyLogLevel(spy), "error");
assert.isTrue(lastSpyLogIncludes(spy, "differ by too much time."));
}
});
});

0 comments on commit 02ea4c4

Please sign in to comment.