Skip to content

Commit

Permalink
fix: Replay transactions that can be finalized
Browse files Browse the repository at this point in the history
comments

Update packages/sdk/src/cross-chain-messenger.ts

feat: Add test

remove stale changeset

fix: remove the stale goerli tests

fix: run pnpm nx build instead of pnpm build

dpeend on pnpm monorepo instead of nx building

wrong ports

http not https

fix: sepolia chain ids

debugging why it's broke

linter: rip

rekick the tests with env variables set to sepolia op rather than mainnet op

fix: Update to a withdrawal that should actually work

fix:test
  • Loading branch information
Will Cory authored and Will Cory committed Apr 1, 2024
1 parent 78f40a3 commit c2d69aa
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 8 deletions.
5 changes: 5 additions & 0 deletions .changeset/young-gorillas-roll.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@eth-optimism/sdk': patch
---

Fixed bug where replayable transactions would fail `finalize` if they previously were marked as errors but replayable.
60 changes: 52 additions & 8 deletions packages/sdk/src/cross-chain-messenger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -694,7 +694,13 @@ export class CrossChainMessenger {
message: MessageLike,
// consider making this an options object next breaking release
messageIndex = 0,
/**
* @deprecated no longer used since no log filters are used
*/
fromBlockOrBlockHash?: BlockTag,
/**
* @deprecated no longer used since no log filters are used
*/
toBlockOrBlockHash?: BlockTag
): Promise<MessageStatus> {
const resolved = await this.toCrossChainMessage(message, messageIndex)
Expand Down Expand Up @@ -1243,13 +1249,13 @@ export class CrossChainMessenger {
const challengePeriod =
oracleVersion === '1.0.0'
? // The ABI in the SDK does not contain FINALIZATION_PERIOD_SECONDS
// in OptimismPortal, so making an explicit call instead.
BigNumber.from(
await this.contracts.l1.OptimismPortal.provider.call({
to: this.contracts.l1.OptimismPortal.address,
data: '0xf4daa291', // FINALIZATION_PERIOD_SECONDS
})
)
// in OptimismPortal, so making an explicit call instead.
BigNumber.from(
await this.contracts.l1.OptimismPortal.provider.call({
to: this.contracts.l1.OptimismPortal.address,
data: '0xf4daa291', // FINALIZATION_PERIOD_SECONDS
})
)
: await this.contracts.l1.L2OutputOracle.FINALIZATION_PERIOD_SECONDS()
return challengePeriod.toNumber()
}
Expand Down Expand Up @@ -1490,7 +1496,7 @@ export class CrossChainMessenger {
// latest games are all invalid and the SDK would be forced to make a bunch of archive calls.
for (let i = matches.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1))
;[matches[i], matches[j]] = [matches[j], matches[i]]
;[matches[i], matches[j]] = [matches[j], matches[i]]
}

// Now we verify the proposals in the matches array.
Expand Down Expand Up @@ -2301,7 +2307,45 @@ export class CrossChainMessenger {
}

if (this.bedrock) {
// get everything we need to finalize
const withdrawal = await this.toLowLevelMessage(resolved, messageIndex)
const xdmWithdrawal =
this.contracts.l1.L1CrossDomainMessenger.interface.decodeFunctionData(
'relayMessage',
withdrawal.message
)
const messageHashV1 = hashCrossDomainMessagev1(
resolved.messageNonce,
resolved.sender,
resolved.target,
resolved.value,
resolved.minGasLimit,
resolved.message
)

const isFailed =
await this.contracts.l1.L1CrossDomainMessenger.failedMessages(
messageHashV1
)


// if failed we need to replay the message rather than finalizing it
if (isFailed === true) {
const tx = await this.contracts.l1.L1CrossDomainMessenger.populateTransaction.relayMessage(
xdmWithdrawal._nonce,
xdmWithdrawal._sender,
xdmWithdrawal._target,
xdmWithdrawal._value,
xdmWithdrawal._minGasLimit,
xdmWithdrawal._message,
opts?.overrides || {}
)
return tx
}
if ('todo remove me') {
throw new Error('should not be trying to finalize')
}

return this.contracts.l1.OptimismPortal.populateTransaction.finalizeWithdrawalTransaction(
[
withdrawal.messageNonce,
Expand Down
92 changes: 92 additions & 0 deletions packages/sdk/test-next/failedMessages.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { describe, it, expect } from 'vitest'
import { Address, Hex, encodePacked, keccak256, toHex } from 'viem'
import { ethers } from 'ethers'
import { z } from 'zod'
import { hashCrossDomainMessagev1 } from '@eth-optimism/core-utils'

import { CONTRACT_ADDRESSES, CrossChainMessenger } from '../src'
import { sepoliaPublicClient, sepoliaTestClient } from './testUtils/viemClients'
import { sepoliaProvider, opSepoliaProvider } from './testUtils/ethersProviders'
import { optimismSepolia } from 'viem/chains'

/**
* Generated on Mar 28 2024 using
* `forge inspect L1CrossDomainMessenger storage-layout`
**/
const failedMessagesStorageLayout = {
astId: 7989,
contract: 'src/L1/L1CrossDomainMessenger.sol:L1CrossDomainMessenger',
label: 'failedMessages',
offset: 0,
slot: 206n,
type: 't_mapping(t_bytes32,t_bool)',
}

const sepoliaCrossDomainMessengerAddress =
CONTRACT_ADDRESSES[optimismSepolia.id].l1.L1CrossDomainMessenger as Address

const setMessageAsFailed = async (tx: Hex) => {
const message = await crossChainMessenger.toCrossChainMessage(tx)
const messageHash = hashCrossDomainMessagev1(
message.messageNonce,
message.sender,
message.target,
message.value,
message.minGasLimit,
message.message
) as Hex

const keySlotHash = keccak256(encodePacked(
['bytes32', 'uint256'],
[messageHash, failedMessagesStorageLayout.slot]

))
return sepoliaTestClient.setStorageAt({
address: sepoliaCrossDomainMessengerAddress,
index: keySlotHash,
value: toHex(true, { size: 32 }),
})
}

const E2E_PRIVATE_KEY = z
.string()
.describe('Private key')
// Mnemonic: test test test test test test test test test test test junk
.default('0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6')
.parse(import.meta.env.VITE_E2E_PRIVATE_KEY)

const sepoliaWallet = new ethers.Wallet(E2E_PRIVATE_KEY, sepoliaProvider)
const crossChainMessenger = new CrossChainMessenger({
l1SignerOrProvider: sepoliaWallet,
l2SignerOrProvider: opSepoliaProvider,
l1ChainId: 11155111,
l2ChainId: 11155420,
bedrock: true,
})

describe('replaying failed messages', () => {
it('should be able to replay failed messages', async () => {
// Grab an existing tx but mark it as failed
// @see https://sepolia-optimism.etherscan.io/tx/0x28249a36f764afab583a4633d59ff6c2a0e934293062bffa7cedb662e5da9abd
const tx =
'0x28249a36f764afab583a4633d59ff6c2a0e934293062bffa7cedb662e5da9abd'

await setMessageAsFailed(tx)

// debugging ethers.js is brutal because of error message so let's instead
// send the tx with viem. If it succeeds we will then test with ethers
const txData = await crossChainMessenger.populateTransaction.finalizeMessage(tx)

await sepoliaPublicClient.call({
data: txData.data as Hex,
to: txData.to as Address,
})

// finalize the message
const finalizeTx = await crossChainMessenger.finalizeMessage(tx)

const receipt = await finalizeTx.wait()

expect(receipt.transactionHash).toBeDefined()
})
})
1 change: 1 addition & 0 deletions packages/sdk/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"lib": ["ES2021"],
"rootDir": "./src",
"outDir": "./dist"
},
Expand Down

0 comments on commit c2d69aa

Please sign in to comment.