Commit b66e3131 authored by Kelvin Fichter's avatar Kelvin Fichter

feat(sdk): add fn to wait for message status

Adds a function to the SDK (waitForMessageStatus) which will wait until
the status of a particular message matches the target status.
waitForMessageStatus also handles certain special cases where different
status messages are incompatible (e.g., the RELAYED and
FAILED_L1_TO_L2_MESSAGE status for L1 to L2 messages). Useful to avoid
including looping logic in your own application.
parent 66df4cd4
---
'@eth-optimism/sdk': patch
---
Add a function for waiting for a particular message status
......@@ -74,11 +74,10 @@ describe('Basic L1<>L2 Communication', async () => {
}
)
let status: MessageStatus
while (status !== MessageStatus.READY_FOR_RELAY) {
status = await env.messenger.getMessageStatus(transaction)
await sleep(1000)
}
await env.messenger.waitForMessageStatus(
transaction,
MessageStatus.READY_FOR_RELAY
)
await env.messenger.finalizeMessage(transaction)
await env.messenger.waitForMessageReceipt(transaction)
......
......@@ -3,7 +3,6 @@ import { BigNumber, Contract, ContractFactory, utils, Wallet } from 'ethers'
import { ethers } from 'hardhat'
import { getContractFactory } from '@eth-optimism/contracts'
import { MessageStatus } from '@eth-optimism/sdk'
import { sleep } from '@eth-optimism/core-utils'
/* Imports: Internal */
import { expect } from './shared/setup'
......@@ -108,12 +107,10 @@ describe('Bridged tokens', () => {
500
)
// TODO: Maybe this should be built into the SDK
let status: MessageStatus
while (status !== MessageStatus.READY_FOR_RELAY) {
status = await env.messenger.getMessageStatus(tx)
await sleep(1000)
}
await env.messenger.waitForMessageStatus(
tx,
MessageStatus.READY_FOR_RELAY
)
await env.messenger.finalizeMessage(tx)
await env.messenger.waitForMessageReceipt(tx)
......@@ -139,12 +136,10 @@ describe('Bridged tokens', () => {
}
)
// TODO: Maybe this should be built into the SDK
let status: MessageStatus
while (status !== MessageStatus.READY_FOR_RELAY) {
status = await env.messenger.getMessageStatus(tx)
await sleep(1000)
}
await env.messenger.waitForMessageStatus(
tx,
MessageStatus.READY_FOR_RELAY
)
await env.messenger.finalizeMessage(tx)
await env.messenger.waitForMessageReceipt(tx)
......@@ -201,12 +196,10 @@ describe('Bridged tokens', () => {
}
)
// TODO: Maybe this should be built into the SDK
let status: MessageStatus
while (status !== MessageStatus.READY_FOR_RELAY) {
status = await env.messenger.getMessageStatus(withdrawalTx)
await sleep(1000)
}
await env.messenger.waitForMessageStatus(
withdrawalTx,
MessageStatus.READY_FOR_RELAY
)
await env.messenger.finalizeMessage(withdrawalTx)
await env.messenger.waitForMessageReceipt(withdrawalTx)
......
......@@ -129,14 +129,10 @@ export class OptimismEnv {
}
for (const message of messages) {
let status: MessageStatus
while (
status !== MessageStatus.READY_FOR_RELAY &&
status !== MessageStatus.RELAYED
) {
status = await this.messenger.getMessageStatus(message)
await sleep(1000)
}
await this.messenger.waitForMessageStatus(
message,
MessageStatus.READY_FOR_RELAY
)
let relayed = false
while (!relayed) {
......
......@@ -453,10 +453,13 @@ export class CrossChainMessenger implements ICrossChainMessenger {
timeoutMs?: number
} = {}
): Promise<MessageReceipt> {
// Resolving once up-front is slightly more efficient.
const resolved = await this.toCrossChainMessage(message)
let totalTimeMs = 0
while (totalTimeMs < (opts.timeoutMs || Infinity)) {
const tick = Date.now()
const receipt = await this.getMessageReceipt(message)
const receipt = await this.getMessageReceipt(resolved)
if (receipt !== null) {
return receipt
} else {
......@@ -468,6 +471,73 @@ export class CrossChainMessenger implements ICrossChainMessenger {
throw new Error(`timed out waiting for message receipt`)
}
public async waitForMessageStatus(
message: MessageLike,
status: MessageStatus,
opts: {
pollIntervalMs?: number
timeoutMs?: number
} = {}
): Promise<void> {
// Resolving once up-front is slightly more efficient.
const resolved = await this.toCrossChainMessage(message)
let totalTimeMs = 0
while (totalTimeMs < (opts.timeoutMs || Infinity)) {
const tick = Date.now()
const currentStatus = await this.getMessageStatus(resolved)
// Handle special cases for L1 to L2 messages.
if (resolved.direction === MessageDirection.L1_TO_L2) {
// If we're at the expected status, we're done.
if (currentStatus === status) {
return
}
if (
status === MessageStatus.UNCONFIRMED_L1_TO_L2_MESSAGE &&
currentStatus > status
) {
// Anything other than UNCONFIRMED_L1_TO_L2_MESSAGE implies that the message was at one
// point "unconfirmed", so we can stop waiting.
return
}
if (
status === MessageStatus.FAILED_L1_TO_L2_MESSAGE &&
currentStatus === MessageStatus.RELAYED
) {
throw new Error(
`incompatible message status, expected FAILED_L1_TO_L2_MESSAGE got RELAYED`
)
}
if (
status === MessageStatus.RELAYED &&
currentStatus === MessageStatus.FAILED_L1_TO_L2_MESSAGE
) {
throw new Error(
`incompatible message status, expected RELAYED got FAILED_L1_TO_L2_MESSAGE`
)
}
}
// Handle special cases for L2 to L1 messages.
if (resolved.direction === MessageDirection.L2_TO_L1) {
if (currentStatus >= status) {
// For L2 to L1 messages, anything after the expected status implies the previous status,
// so we can safely return if the current status enum is larger than the expected one.
return
}
}
await sleep(opts.pollIntervalMs || 4000)
totalTimeMs += Date.now() - tick
}
throw new Error(`timed out waiting for message status change`)
}
public async estimateL2MessageGasLimit(
message: MessageRequestLike,
opts?: {
......
......@@ -226,6 +226,27 @@ export interface ICrossChainMessenger {
}
): Promise<MessageReceipt>
/**
* Waits until the status of a given message changes to the expected status. Note that if the
* status of the given message changes to a status that implies the expected status, this will
* still return. If the status of the message changes to a status that exclues the expected
* status, this will throw an error.
*
* @param message Message to wait for.
* @param status Expected status of the message.
* @param opts Options to pass to the waiting function.
* @param opts.pollIntervalMs Number of milliseconds to wait when polling.
* @param opts.timeoutMs Milliseconds to wait before timing out.
*/
waitForMessageStatus(
message: MessageLike,
status: MessageStatus,
opts?: {
pollIntervalMs?: number
timeoutMs?: number
}
): Promise<void>
/**
* Estimates the amount of gas required to fully execute a given message on L2. Only applies to
* L1 => L2 messages. You would supply this gas limit when sending the message to L2.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment