Skip to content

Commit 1d3734a

Browse files
authored
Merge pull request #1616 from qbzzt/250519-replace-audit-of-message-passing
Audit of message passing tutorial
2 parents ee7eee0 + 75b1c8d commit 1d3734a

File tree

10 files changed

+562
-709
lines changed

10 files changed

+562
-709
lines changed

pages/interop/tutorials/_meta.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
"transfer-superchainERC20": "Transferring a SuperchainERC20",
55
"custom-superchain-erc20": "Custom SuperchainERC20 tokens",
66
"bridge-crosschain-eth": "Bridging native cross-chain ETH transfers",
7-
"relay-messages-cast": "Relaying interop messages using `cast`",
8-
"relay-messages-viem": "Relaying interop messages using `viem`",
97
"contract-calls": "Making crosschain contract calls (ping pong)",
108
"event-reads": "Making crosschain event reads (tic-tac-toe)",
119
"event-contests": "Deploying crosschain event composability (contests)",

pages/interop/tutorials/message-passing.mdx

Lines changed: 169 additions & 377 deletions
Large diffs are not rendered by default.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"relay-with-cast": "Relay transactions manually"
2+
"manual-relay": "Relay transactions manually"
33
}
Lines changed: 353 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
1+
---
2+
title: Relay transactions manually
3+
description: >-
4+
Learn to relay transactions directly by sending the correct transaction.
5+
lang: en-US
6+
content_type: tutorial
7+
topic: interop-cast-manual-relay-tutorial
8+
personas:
9+
- protocol-developer
10+
- chain-operator
11+
- app-developer
12+
categories:
13+
- protocol
14+
- interoperability
15+
- cross-chain-messaging
16+
- message-relaying
17+
- cross-domain-messenger
18+
- smart-contracts
19+
- testnet
20+
- superchain
21+
is_imported_content: 'false'
22+
---
23+
24+
import { Callout, Steps } from 'nextra/components'
25+
import { InteropCallout } from '@/components/WipCallout'
26+
import { AutorelayCallout } from '@/components/AutorelayCallout'
27+
28+
# Relay transactions manually
29+
30+
<InteropCallout />
31+
32+
<AutorelayCallout />
33+
34+
## Overview
35+
36+
Learn to relay transactions directly by sending the correct transaction.
37+
38+
<details>
39+
<summary>About this tutorial</summary>
40+
41+
**Prerequisite technical knowledge**
42+
43+
* Familiarity with blockchain concepts
44+
* Familiarity with [Foundry](https://book.getfoundry.sh/getting-started/installation), especially `cast`
45+
46+
**What you'll learn**
47+
48+
* How to use `cast` to relay transactions when autorelay does not work
49+
* How to relay transactions using JavaScript
50+
51+
**Development environment requirements**
52+
53+
* Unix-like operating system (Linux, macOS, or WSL for Windows)
54+
* Node.js version 16 or higher
55+
* Git for version control
56+
* Supersim environment configured and running
57+
* Foundry tools installed (forge, cast, anvil)
58+
</details>
59+
60+
### What you'll build
61+
62+
* A program to relay messages using [the JavaScript library](https://www.npmjs.com/package/@eth-optimism/viem)
63+
* A shell script to relay messages using [`cast`](https://book.getfoundry.sh/cast/)
64+
65+
## Setup
66+
67+
These steps are necessary to run the tutorial, regardless of whether you are using `cast` or the JavaScript API.
68+
69+
<Steps>
70+
### Run Supersim
71+
72+
This exercise needs to be done on Supersim.
73+
You cannot use the devnets because you cannot disable autorelay on them.
74+
75+
1. Follow the [installation guide](/app-developers/tutorials/supersim/getting-started/installation).
76+
77+
2. Run Supersim *without* `--interop.relay`.
78+
79+
```sh
80+
./supersim
81+
```
82+
83+
### Create the state for relaying messages
84+
85+
The results of this step are similar to what the [message passing tutorial](/interop/tutorials/message-passing) would produce if you did not have autorelay on.
86+
87+
Execute this script.
88+
89+
```sh file=<rootDir>/public/tutorials/setup-for-manual-relay.sh hash=eea0a71ff6b4c27b91225a715eb2718e
90+
```
91+
92+
This script installs `Greeter.sol` on chain B and `GreetingSender.sol` on chain A.
93+
These smart contracts let us send a message from chain A that needs to be relayed to chain B.
94+
95+
Then, the script creates `./manual-relay/sendAndRelay.sh` to manually relay a message from chain A to chain B.
96+
That script is [explained below](#manual-relay-using-cast).
97+
98+
Finally, this script writes out some parameter setting lines that should be executed on the main shell before you continue.
99+
With a fresh Supersim running, these should be:
100+
101+
```sh
102+
GREETER_A_ADDRESS=0x5FbDB2315678afecb367f032d93F642f64180aa3
103+
GREETER_B_ADDRESS=0x5FbDB2315678afecb367f032d93F642f64180aa3
104+
PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
105+
```
106+
107+
</Steps>
108+
109+
## Manual relay using the API
110+
111+
<Steps>
112+
### Setup
113+
114+
Use a [Node](https://nodejs.org/en) project.
115+
116+
1. Initialize a new Node project.
117+
118+
```sh
119+
mkdir -p manual-relay/offchain
120+
cd manual-relay/offchain
121+
npm init -y
122+
npm install --save-dev viem @eth-optimism/viem
123+
mkdir src
124+
```
125+
126+
2. Export environment variables.
127+
This is necessary because those variables are currently limited to the shell process.
128+
We need them in the Node process that the shell creates.
129+
130+
```sh
131+
export GREETER_A_ADDRESS GREETER_B_ADDRESS PRIVATE_KEY
132+
```
133+
134+
### Manual relaying app
135+
136+
1. Create a file `manual-relay.mjs` with:
137+
138+
```javascript file=<rootDir>/public/tutorials/manual-relay.mjs hash=b0c44b327af300437f3d71ff049842f9
139+
```
140+
141+
<details>
142+
<summary>Explanation</summary>
143+
144+
```javascript file=<rootDir>/public/tutorials/manual-relay.mjs#L8-L9 hash=2d062eb374989a8a40199a4d7dc8be6e
145+
```
146+
147+
Import from the [`@eth-optimism/viem`](https://www.npmjs.com/package/@eth-optimism/viem) package.
148+
149+
```javascript file=<rootDir>/public/tutorials/manual-relay.mjs#L18-L32 hash=cf5ce47bcbcd80327230a6da689688f8
150+
```
151+
152+
In addition to extending the wallets with [Viem public actions](https://viem.sh/docs/accounts/local#5-optional-extend-with-public-actions), extend with the OP-Stack actions.
153+
On wallet A we need the public actions, those that only read information.
154+
On wallet B we need the wallet actions, the ones that require an account.
155+
156+
```javascript file=<rootDir>/public/tutorials/manual-relay.mjs#L55 hash=23aa6f24baeb5757130361f30c1b0e9c
157+
```
158+
159+
To relay a message we need the information in the receipt.
160+
Also, we need to wait until the transaction with the relayed message is actually part of a block.
161+
162+
```javascript file=<rootDir>/public/tutorials/manual-relay.mjs#L57-L58 hash=c0d3d8a60143c30b93db256518e8b583
163+
```
164+
165+
Show the user that until the relay transaction happens on chain B, the greeting is unchanged.
166+
167+
```javascript file=<rootDir>/public/tutorials/manual-relay.mjs#L60-L63 hash=8cc99e67ee36474c81183108531cb295
168+
```
169+
170+
A single transaction can send multiple messages.
171+
But here we know we sent just one, so we look for the first one in the list.
172+
173+
```javascript file=<rootDir>/public/tutorials/manual-relay.mjs#L64-L71 hash=b7ed7d70ba5ec84322beee5369c5bee5
174+
```
175+
176+
Here we first send the relay message on chain B, and then wait for the receipt for it.
177+
</details>
178+
179+
2. Run JavaScript program, and see that the message is relayed.
180+
181+
```sh
182+
node manual-relay.mjs
183+
```
184+
185+
### Debugging
186+
187+
To see what messages were relayed by a specific transaction you can use this code:
188+
189+
```javascript
190+
import { decodeRelayedL2ToL2Messages } from '@eth-optimism/viem'
191+
192+
const decodedRelays = decodeRelayedL2ToL2Messages(
193+
{receipt: receiptRelay})
194+
195+
console.log(decodedRelays)
196+
console.log(decodedRelays.successfulMessages[0].log)
197+
```
198+
</Steps>
199+
200+
## Manual relay using `cast`
201+
202+
You can see an example of how to manually relay using `cast` in `manual-relay/sendAndRelay.sh`.
203+
It is somewhat complicated, so the setup creates one that is tailored to your environment.
204+
205+
You can run the script using this command:
206+
207+
```sh
208+
./manual-relay/sendAndRelay.sh
209+
```
210+
211+
Here is the detailed explanation:
212+
213+
1. Configuration parameters
214+
215+
```sh
216+
#! /bin/sh
217+
PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
218+
USER_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
219+
URL_CHAIN_A=http://localhost:9545
220+
URL_CHAIN_B=http://localhost:9546
221+
GREETER_A_ADDRESS=0x5FbDB2315678afecb367f032d93F642f64180aa3
222+
GREETER_B_ADDRESS=0x5FbDB2315678afecb367f032d93F642f64180aa3
223+
CHAIN_ID_B=902
224+
```
225+
226+
This is the configuration.
227+
The greeter addresses are identical because the nonce for the user address has an identical nonce on both chains.
228+
229+
2. Send a message that needs to be relayed
230+
231+
```sh
232+
cast send -q --private-key $PRIVATE_KEY --rpc-url $URL_CHAIN_A $GREETER_A_ADDRESS "setGreeting(string)" "Hello from chain A $$"
233+
```
234+
235+
Send a message from chain A to chain B. The `$$` is the process ID, so if you rerun the script you'll see that the information changes.
236+
237+
3. Find the log entry to relay
238+
239+
```sh
240+
cast logs "SentMessage(uint256,address,uint256,address,bytes)" --rpc-url $URL_CHAIN_A | tail -14 > log-entry
241+
```
242+
243+
Whenever `L2ToL2CrossDomainMessenger` sends a message to a different blockchain, it emits a [`SendMessage`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol#L83-L91) event.
244+
Extract only the latest `SendMessage` event from the logs.
245+
246+
<details>
247+
<summary>Example `log-entry`</summary>
248+
249+
```yaml
250+
- address: 0x4200000000000000000000000000000000000023
251+
blockHash: 0xcd0be97ffb41694faf3a172ac612a23f224afc1bfecd7cb737a7a464cf5d133e
252+
blockNumber: 426
253+
data: 0x0000000000000000000000005fbdb2315678afecb367f032d93f642f64180aa300000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000064a41368620000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001948656c6c6f2066726f6d20636861696e2041203131333030370000000000000000000000000000000000000000000000000000000000000000000000
254+
logIndex: 0
255+
removed: false
256+
topics: [
257+
0x382409ac69001e11931a28435afef442cbfd20d9891907e8fa373ba7d351f320
258+
0x0000000000000000000000000000000000000000000000000000000000000386
259+
0x0000000000000000000000005fbdb2315678afecb367f032d93f642f64180aa3
260+
0x0000000000000000000000000000000000000000000000000000000000000000
261+
]
262+
transactionHash: 0x1d6f2e5e2c8f3eb055e95741380ca36492f784b9782848b66b66c65c5937ff3a
263+
transactionIndex: 0
264+
```
265+
</details>
266+
267+
4. Manipulate the log entry to obtain information
268+
269+
```sh
270+
TOPICS=`cat log-entry | grep -A4 topics | awk '{print $1}' | tail -4 | sed 's/0x//'`
271+
TOPICS=`echo $TOPICS | sed 's/ //g'`
272+
```
273+
274+
Consolidate the log topics into a single hex string.
275+
276+
```sh
277+
ORIGIN=0x4200000000000000000000000000000000000023
278+
BLOCK_NUMBER=`cat log-entry | awk '/blockNumber/ {print $2}'`
279+
LOG_INDEX=`cat log-entry | awk '/logIndex/ {print $2}'`
280+
TIMESTAMP=`cast block $BLOCK_NUMBER --rpc-url $URL_CHAIN_A | awk '/timestamp/ {print $2}'`
281+
CHAIN_ID_A=`cast chain-id --rpc-url $URL_CHAIN_A`
282+
SENT_MESSAGE=`cat log-entry | awk '/data/ {print $2}'`
283+
```
284+
285+
Read additional fields from the log entry.
286+
287+
```sh
288+
LOG_ENTRY=0x`echo $TOPICS$SENT_MESSAGE | sed 's/0x//'`
289+
```
290+
291+
Consolidate the entire log entry.
292+
293+
5. Create the access list for the executing message
294+
295+
```sh
296+
RPC_PARAMS=$(cat <<INNER_END_OF_FILE
297+
{
298+
"origin": "$ORIGIN",
299+
"blockNumber": "$BLOCK_NUMBER",
300+
"logIndex": "$LOG_INDEX",
301+
"timestamp": "$TIMESTAMP",
302+
"chainId": "$CHAIN_ID_A",
303+
"payload": "$LOG_ENTRY"
304+
}
305+
INNER_END_OF_FILE
306+
)
307+
308+
ACCESS_LIST=`cast rpc admin_getAccessListForIdentifier --rpc-url http://localhost:8420 "$RPC_PARAMS" | jq .accessList`
309+
```
310+
311+
To secure cross-chain messaging and prevent potential [denial-of-service attacks](https://github.com/ethereum-optimism/design-docs/blob/main/protocol/interop-access-list.md), relay transactions require properly formatted access lists that include a checksum derived from the message data.
312+
This lets sequencers know what executing messages to expect in a transaction, which makes it easy not to include transactions that are invalid because they rely on messages that were never sent.
313+
314+
The [algorithm to calculate the access list](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/CrossL2Inbox.sol#L87-L115) is a bit complicated, but you don't need to worry about it.
315+
Supersim exposes [RPC calls](https://supersim.pages.dev/guides/interop/cast?highlight=manuall#7-construct-the-access-list-for-the-message) that calculates it for you on port 8420.
316+
The code above will calculate the correct access list even if you're using a different interop cluster where autorelay is not functioning.
317+
This is because the code implements a [pure function](https://en.wikipedia.org/wiki/Pure_function), which produces consistent results regardless of external state.
318+
In contrast, the `admin_getAccessListByMsgHash` RPC call is not a pure function, it is dependent on system state and therefore less flexible in these situations.
319+
320+
6. Show that the manual relay is necessary
321+
322+
```sh
323+
echo Old greeting
324+
cast call $GREETER_B_ADDRESS "greet()(string)" --rpc-url $URL_CHAIN_B
325+
```
326+
327+
Show the current greeting.
328+
The message has not been relayed yet, so it's still the old greeting.
329+
330+
7. Actually relay the message
331+
332+
```sh
333+
cast send -q $ORIGIN "relayMessage((address,uint256,uint256,uint256,uint256),bytes)" "($ORIGIN,$BLOCK_NUMBER,$LOG_INDEX,$TIMESTAMP,$CHAIN_ID_A)" $LOG_ENTRY --access-list "$ACCESS_LIST" --rpc-url $URL_CHAIN_B --private-key $PRIVATE_KEY
334+
```
335+
336+
Call [`relayMessage`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol#L197-L256) to relay the message.
337+
338+
8. Show the relay results
339+
340+
```sh
341+
echo New greeting
342+
cast call $GREETER_B_ADDRESS "greet()(string)" --rpc-url $URL_CHAIN_B
343+
```
344+
345+
Again, show the current greeting.
346+
Now it's the new one.
347+
348+
349+
## Next steps
350+
351+
* Review the [Superchain interop explainer](/interop/explainer) for answers to common questions about interoperability.
352+
* Read the [message passing explainer](/interop/message-passing) to understand what happens "under the hood".
353+
* Write a revolutionary app that uses multiple blockchains within the Superchain.

0 commit comments

Comments
 (0)