Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WEB3-77: add message-passing example #194

Closed
wants to merge 46 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
7e4b8c6
add message-passing example
capossele Aug 12, 2024
c7c7393
fix copyright
capossele Aug 12, 2024
5518511
fix license
capossele Aug 12, 2024
d9be70e
update the README
Wollac Aug 13, 2024
ece43d5
add .env
Wollac Aug 13, 2024
3e20bd1
simplify counter
capossele Aug 13, 2024
329ddd4
fix Commitment
capossele Aug 23, 2024
151be94
update READMEs
capossele Aug 23, 2024
3cef861
fix test
capossele Aug 23, 2024
f8c919c
improve example README
capossele Aug 23, 2024
f9a1ada
add message-passing to CI
capossele Aug 23, 2024
72859c8
fix comments
capossele Aug 23, 2024
cd147fe
improve README
Wollac Aug 24, 2024
e7604be
address review comments
capossele Aug 26, 2024
5c8af0d
fix deploy script
capossele Aug 26, 2024
93bfb90
us OP devnet
Wollac Sep 6, 2024
b2d8ef6
Update main.yml
Wollac Sep 6, 2024
17a7a58
Update main.yml
Wollac Sep 6, 2024
8f2f448
Update main.yml
Wollac Sep 6, 2024
edd4305
Update main.yml
Wollac Sep 6, 2024
da1ce81
Update main.yml
Wollac Sep 6, 2024
8cab6bf
Update main.yml
Wollac Sep 6, 2024
bacbeb2
Update main.yml
Wollac Sep 6, 2024
cc9eb4c
Update main.yml
Wollac Sep 6, 2024
3a3afb9
Update main.yml
Wollac Sep 6, 2024
1fe5447
Update main.yml
Wollac Sep 6, 2024
937a15f
Update main.yml
Wollac Sep 6, 2024
053135d
Update main.yml
Wollac Sep 6, 2024
cd15dcb
Update main.yml
Wollac Sep 6, 2024
2cb7041
Update main.yml
Wollac Sep 6, 2024
c341cf3
Update main.yml
Wollac Sep 6, 2024
cf8fa5a
run e2e test in the CI
Wollac Sep 6, 2024
e09f1e6
add missing script
Wollac Sep 6, 2024
2dc02b8
use random Anvil port
Wollac Sep 6, 2024
3799d92
Merge branch 'main' into example/message-passing
Wollac Sep 6, 2024
48d034a
CI cleanups
Wollac Sep 6, 2024
035111c
WIP: beacon commitments
Wollac Sep 6, 2024
c023901
only prover
Wollac Sep 9, 2024
de9f4de
fix script
Wollac Sep 9, 2024
1f1d066
enable bookmarking
Wollac Sep 9, 2024
771a8af
revert steel changes
Wollac Sep 9, 2024
85e5810
revert steel changes
Wollac Sep 9, 2024
74e7f9e
Merge branch 'main' into example/message-passing
Wollac Sep 9, 2024
4d3ff58
Update script name for CI
Wollac Sep 9, 2024
b07c51d
fix script
Wollac Sep 9, 2024
7b206d7
update alloy
Wollac Sep 9, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,60 @@ jobs:
working-directory: contracts
- run: sccache --show-stats

op-message-passing:
# Run only on Linux with GPU.
runs-on: [ self-hosted, prod, "Linux", "nvidia_rtx_a5000" ]
defaults:
run:
working-directory: examples/message-passing
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Load OP versions
run: jq -r 'to_entries[] | ["version_\(.key)",.value] | join("=")' optimism/versions.json >> "$GITHUB_ENV"
- uses: actions/setup-go@v5
with:
go-version: ${{ env.version_go }}
- uses: actions/setup-python@v5
with:
python-version: '3.10'
- uses: risc0/risc0/.github/actions/cuda@main
- uses: risc0/risc0/.github/actions/rustup@main
- uses: risc0/risc0/.github/actions/sccache@main
with:
key: Linux-cuda
- uses: risc0/foundry-toolchain@2fe7e70b520f62368a0e3c464f997df07ede420f
with:
version: nightly-${{ env.version_foundry }}
- name: Pull and tag OP docker images
run: |
docker pull "$IMAGE_PREFIX/op-node:$OP_STACK_VERSION"
docker pull "$IMAGE_PREFIX/op-proposer:$OP_STACK_VERSION"
docker pull "$IMAGE_PREFIX/op-batcher:$OP_STACK_VERSION"
docker tag "$IMAGE_PREFIX/op-node:$OP_STACK_VERSION" "$IMAGE_PREFIX/op-node:devnet"
docker tag "$IMAGE_PREFIX/op-proposer:$OP_STACK_VERSION" "$IMAGE_PREFIX/op-proposer:devnet"
docker tag "$IMAGE_PREFIX/op-batcher:$OP_STACK_VERSION" "$IMAGE_PREFIX/op-batcher:devnet"
env:
IMAGE_PREFIX: "us-docker.pkg.dev/oplabs-tools-artifacts/images"
OP_STACK_VERSION: 'v1.9.1'
- name: Bring up the OP stack
run: make -C optimism devnet-up
env:
DEVNET_NO_BUILD: true
DEVNET_L2OO: true
DEVNET_ALTDA: false
- uses: ./.github/actions/cargo-risczero-install
with:
ref: ${{ env.RISC0_MONOREPO_REF }}
toolchain-version: ${{ env.RISC0_TOOLCHAIN_VERSION }}
features: cuda
- run: cargo build
- run: forge test -vvv
- name: Run E2E test
run: ./e2e-test.sh
- run: sccache --show-stats

examples:
runs-on: [self-hosted, prod, "${{ matrix.os }}", "${{ matrix.device }}"]
strategy:
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@
[submodule "lib/openzeppelin-contracts"]
path = lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
[submodule "examples/message-passing/optimism"]
path = examples/message-passing/optimism
url = https://github.com/ethereum-optimism/optimism.git
7 changes: 7 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ Explore a more advanced interaction between [Steel] and a custom Ethereum smart
This example shows how the [Steel] library can be used to call multiple view functions of a contract.
This example generates a proof of a [Compound] cToken's APR (Annual Percentage Rate), showcasing the potential for on-chain verification of complex financial metrics.

## [Cross-Domain Message Passing](./message-passing/README.md)

In order for Smart contracts on L1 to interact with smart contracts on L2, Optimism is using a process called "bridging".
This example showcases an alternative using the [Steel] library to do secure and efficient OP-compatible message passing,
significantly reducing L1 gas costs. For more information on Optimism's bridging process, refer to their [documentation].

[coprocessor]: https://www.risczero.com/news/a-guide-to-zk-coprocessors-for-scalability
[Steel]: ../steel
[Compound]: https://compound.finance/
[documentation]: https://docs.optimism.io/builders/app-developers/bridging/messaging
13 changes: 8 additions & 5 deletions examples/erc20-counter/test-local-deployment.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@ cleanup() {
# This ensures cleanup is performed on script exit or error
trap cleanup EXIT ERR

# Get a random port number between 49152 and 65535
ANVIL_PORT=$(( RANDOM % 16384 + 49152 ))

# Start Anvil and capture its output temporarily
anvil > anvil_logs.txt 2>&1 &
anvil -p $ANVIL_PORT > anvil_logs.txt 2>&1 &
ANVIL_PID=$!
echo "Anvil started with PID $ANVIL_PID"
echo "Anvil started with on port $ANVIL_PORT with PID $ANVIL_PID"

# Wait a few seconds to ensure Anvil has started and output private keys
sleep 5
Expand All @@ -32,7 +35,7 @@ cargo build

# Deploy the Counter contract
echo "Deploying the Counter contract..."
forge script --rpc-url http://localhost:8545 --broadcast DeployCounter
forge script --rpc-url http://localhost:$ANVIL_PORT --broadcast DeployCounter

# Extract the Toyken address
export TOYKEN_ADDRESS=$(jq -re '.transactions[] | select(.contractName == "ERC20FixedSupply") | .contractAddress' ./broadcast/DeployCounter.s.sol/31337/run-latest.json)
Expand All @@ -45,14 +48,14 @@ echo "Counter Address: $COUNTER_ADDRESS"
# Publish a new state
echo "Publishing a new state..."
cargo run --bin publisher -- \
--eth-rpc-url=http://localhost:8545 \
--eth-rpc-url=http://localhost:$ANVIL_PORT \
--counter=${COUNTER_ADDRESS:?} \
--token-contract=${TOYKEN_ADDRESS:?} \
--account=${TOKEN_OWNER:?}

# Attempt to verify counter value as part of the script logic
echo "Verifying state..."
COUNTER_VALUE=$(cast call --rpc-url http://localhost:8545 ${COUNTER_ADDRESS:?} 'get()(uint256)')
COUNTER_VALUE=$(cast call --rpc-url http://localhost:$ANVIL_PORT ${COUNTER_ADDRESS:?} 'get()(uint256)')
if [ "$COUNTER_VALUE" != "1" ]; then
echo "Counter value is not 1 as expected, but $COUNTER_VALUE."
exit 1
Expand Down
18 changes: 18 additions & 0 deletions examples/message-passing/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
## L1 Ethereum Config

L1_RPC_URL="http://localhost:8545/"
BEACON_API_URL="http://localhost:5052"

L1_ADMIN_PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"

L1_WALLET_ADDRESS="0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65"
L1_WALLET_PRIVATE_KEY="0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a"

## L2 Optimism config

L2_RPC_URL="http://localhost:9545/"

L2_ADMIN_PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"

L2_WALLET_ADDRESS="0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65"
L2_WALLET_PRIVATE_KEY="0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a"
27 changes: 27 additions & 0 deletions examples/message-passing/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Compiler files
cache/
out/

# Ignores development broadcast logs
!/broadcast
/broadcast/*/31337/
/broadcast/*/11155111/
/broadcast/**/dry-run/

# Ignores anvil logs
anvil_logs.txt

# Autogenerated contracts
contracts/src/ImageID.sol
contracts/src/Elf.sol

# Dotenv file
.env

# Cargo
target/

# Misc
.DS_Store
.idea
proof.json
3 changes: 3 additions & 0 deletions examples/message-passing/.solhint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "solhint:default"
}
43 changes: 43 additions & 0 deletions examples/message-passing/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
[workspace]
resolver = "2"
members = ["crates/*"]

[workspace.package]
version = "0.1.0"
edition = "2021"

[workspace.dependencies]
# Intra-workspace dependencies
risc0-build-ethereum = { path = "../../build" }
risc0-ethereum-contracts = { path = "../../contracts" }
risc0-steel = { path = "../../steel" }

cross-domain-messenger-methods = { path = "./crates/methods" }
cross-domain-messenger-core = { path = "./crates/core" }

# risc0 monorepo dependencies.
risc0-build = { git = "https://github.com/risc0/risc0", branch = "main", features = ["docker"] }
risc0-zkvm = { git = "https://github.com/risc0/risc0", branch = "main", default-features = false }
risc0-zkp = { git = "https://github.com/risc0/risc0", branch = "main", default-features = false }

alloy = { version = "0.3", features = ["full"] }
alloy-primitives = { version = "0.8", features = ["rlp", "serde", "std"] }
alloy-sol-types = { version = "0.8" }
anyhow = { version = "1.0.75" }
bincode = { version = "1.3" }
bytemuck = { version = "1.14" }
clap = { version = "4.5" }
dotenvy = { version = "0.15" }
ethers = { version = "2.0" }
hex = { version = "0.4" }
log = { version = "0.4" }
serde = { version = "1.0", features = ["derive", "std"] }
serde_json = { version = "1.0" }
tokio = { version = "1.39", features = ["full"] }
tracing = { version = "0.1" }
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
url = { version = "2.5" }

[profile.release]
debug = 1
lto = true
33 changes: 33 additions & 0 deletions examples/message-passing/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Cross-Domain Messaging with Steel
In order for Smart contracts on L1 to interact with smart contracts on L2, Optimism is using a process called "bridging". For more information on Optimism's bridging process, refer to their [documentation](https://docs.optimism.io/builders/app-developers/bridging/messaging).

This example showcases an alternative using the Steel library to do secure and efficient OP-compatible message passing.
It also showcases a *bookmarking block commitment validation* technique, by saving the target block hash to the contract state before generating a Steel proof that targets that specific block. Once the block hash is bookmarked, it can be used later for validation, ensuring that the proof corresponds to the correct blockchain state.

> **Note:** Even though the example specifically targets OP-compatible message passing, most of the code is chain agnostic and can be easily adapted to any EVM-based chain.

## Key Steps
1. **Send Message from L1:**<br>
Call `L1CrossDomainMessenger::sendMessage(address,bytes)` with the target and the data of message you want to relay to L2.
2. **Bookmark the block hash**<br>
Call `L2CrossDomainMessenger::bookmarkL1Block()` to save the current L1 block hash to the contract state before generating the Steel proof.
3. **Generate Steel Proof:**<br>
Generate a Steel proof verifying the message's inclusion in the L1 state, targeting the bookmarked block.
4. **Relay Message on L2:**<br>
On the L2, call `L2CrossDomainMessenger::relayMessage(bytes,bytes)` with the journal and seal of the Steel proof. This will verify the seal, check the Steel commitment against the bookmarked blocks, and finally relay the message to the target contract.

## Advantages
This method eliminates unnecessary bridging operations, significantly reducing L1 gas costs. The Steel approach also avoids the `OptimismPortal` L1 gas burn, which can vary depending on the usage.

## How to run

- Assure that the `.env` file contains the correct information and potentially deploy the contracts using `forge script --broadcast Deploy`.
- Set `TARGET` and `CALL_DATA` according to the message that you want to pass to the L2.
- Run `cast send $L1_CROSS_DOMAIN_MESSENGER_ADDRESS 'sendMessage(address, bytes)' $TARGET $CALL_DATA)` to submit the message on the L1.
- Create the proof using the `prover` app with the hash of the resulting transaction:
```bash
RUST_LOG=info cargo run -- --tx-hash $SEND_MESSAGE_TX_HASH
```
- Run `cast send $L2_CROSS_DOMAIN_MESSENGER_ADDRESS 'relayMessage(bytes, bytes)' $TARGET $CALL_DATA)` to relay the message on the L2.

The file `e2e-test.sh` contains an examples that performs all those steps in one script.
67 changes: 67 additions & 0 deletions examples/message-passing/contracts/script/Deploy.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright 2024 RISC Zero, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// SPDX-License-Identifier: Apache-2.0

pragma solidity ^0.8.20;

import {Script, console2} from "forge-std/Script.sol";
import {RiscZeroCheats} from "risc0/test/RiscZeroCheats.sol";
import {IRiscZeroVerifier} from "risc0/IRiscZeroVerifier.sol";
import {RiscZeroMockVerifier} from "risc0/test/RiscZeroMockVerifier.sol";
import {IL1Block} from "../src/IL1Block.sol";
import {L1CrossDomainMessenger} from "../src/L1CrossDomainMessenger.sol";
import {L2CrossDomainMessenger} from "../src/L2CrossDomainMessenger.sol";
import {ImageID} from "../src/ImageID.sol";
import {Counter} from "../src/Counter.sol";

contract Deploy is Script, RiscZeroCheats {
// Address of the L1Block contract.
address private L1_BLOCK_ADDRESS = 0x4200000000000000000000000000000000000015;

function run() external {
// load ENV variables first
uint256 key1 = vm.envUint("L1_ADMIN_PRIVATE_KEY");
uint256 key2 = vm.envUint("L2_ADMIN_PRIVATE_KEY");
uint256 l1 = vm.createFork(vm.envString("L1_RPC_URL"));
uint256 l2 = vm.createFork(vm.envString("L2_RPC_URL"));
address l1Sender = address(0x0);
try vm.envAddress("L1_WALLET_ADDRESS") returns (address val) {
l1Sender = val;
} catch {}

vm.selectFork(l1);
vm.startBroadcast(key1);

L1CrossDomainMessenger l1CrossDomainMessenger = new L1CrossDomainMessenger();
console2.log("Deployed L1 IL1CrossDomainMessenger to", address(l1CrossDomainMessenger));

vm.stopBroadcast();

vm.selectFork(l2);
vm.startBroadcast(key2);

IRiscZeroVerifier verifier = deployRiscZeroVerifier();

L2CrossDomainMessenger l2CrossDomainMessenger = new L2CrossDomainMessenger(
verifier, ImageID.CROSS_DOMAIN_MESSENGER_ID, address(l1CrossDomainMessenger), IL1Block(L1_BLOCK_ADDRESS)
);
console2.log("Deployed L2 L2CrossDomainMessenger to", address(l2CrossDomainMessenger));

Counter counter = new Counter(l2CrossDomainMessenger, l1Sender);
console2.log("Deployed L2 Counter to", address(counter));

vm.stopBroadcast();
}
}
45 changes: 45 additions & 0 deletions examples/message-passing/contracts/src/Bookmark.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright 2024 RISC Zero, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// SPDX-License-Identifier: Apache-2.0

pragma solidity ^0.8.20;

import {IBookmark} from "./IBookmark.sol";
import {IL1Block} from "./IL1Block.sol";

contract Bookmark is IBookmark {
/// @notice Address of the L1Block contract.
IL1Block private immutable L1_BLOCK;

mapping(uint64 blockNumber => bytes32 blockHash) internal blocks;

constructor(IL1Block l1Block) {
L1_BLOCK = l1Block;
}

function bookmarkL1Block() external returns (uint64) {
uint64 blockNumber = L1_BLOCK.number();
bytes32 blockHash = L1_BLOCK.hash();

blocks[blockNumber] = blockHash;
emit BookmarkedL1Block(blockNumber, blockHash);

return blockNumber;
}

function getBookmark(uint64 blockNumber) external view returns (bytes32) {
return blocks[blockNumber];
}
}
Loading
Loading