From 7e4b8c6ccff83c96195f6b3273c05402dd6f32d8 Mon Sep 17 00:00:00 2001 From: capossele Date: Mon, 12 Aug 2024 10:34:43 -0700 Subject: [PATCH 01/44] add message-passing example --- examples/message-passing/.gitignore | 26 +++ examples/message-passing/.solhint.json | 3 + examples/message-passing/Cargo.toml | 41 ++++ examples/message-passing/apps/Cargo.toml | 18 ++ examples/message-passing/apps/README.md | 38 ++++ .../message-passing/apps/src/bin/publisher.rs | 170 ++++++++++++++ .../contracts/src/Bookmark.sol | 45 ++++ .../message-passing/contracts/src/Counter.sol | 47 ++++ .../contracts/src/IBookmark.sol | 29 +++ .../contracts/src/ICounter.sol | 25 +++ .../contracts/src/IL1Block.sol | 26 +++ .../contracts/src/IL1CrossDomainMessenger.sol | 35 +++ .../contracts/src/IL2CrossDomainMessenger.sol | 35 +++ .../contracts/src/L1CrossDomainMessenger.sol | 50 +++++ .../contracts/src/L2CrossDomainMessenger.sol | 97 ++++++++ .../message-passing/contracts/src/Structs.sol | 51 +++++ .../message-passing/contracts/test/E2E.t.sol | 109 +++++++++ .../contracts/test/L1BlockMock.sol | 29 +++ .../contracts/test/Structs.t.sol | 35 +++ examples/message-passing/core/Cargo.toml | 17 ++ .../message-passing/core/src/contracts.rs | 207 ++++++++++++++++++ examples/message-passing/core/src/lib.rs | 75 +++++++ examples/message-passing/foundry.toml | 8 + examples/message-passing/methods/Cargo.toml | 19 ++ examples/message-passing/methods/README.md | 31 +++ examples/message-passing/methods/build.rs | 46 ++++ .../message-passing/methods/guest/Cargo.toml | 25 +++ .../guest/src/bin/cross_domain_messenger.rs | 48 ++++ examples/message-passing/methods/src/lib.rs | 16 ++ examples/message-passing/remappings.txt | 3 + examples/message-passing/rust-toolchain.toml | 4 + examples/message-passing/scripts/Deploy.s.sol | 51 +++++ 32 files changed, 1459 insertions(+) create mode 100644 examples/message-passing/.gitignore create mode 100644 examples/message-passing/.solhint.json create mode 100644 examples/message-passing/Cargo.toml create mode 100644 examples/message-passing/apps/Cargo.toml create mode 100644 examples/message-passing/apps/README.md create mode 100644 examples/message-passing/apps/src/bin/publisher.rs create mode 100644 examples/message-passing/contracts/src/Bookmark.sol create mode 100644 examples/message-passing/contracts/src/Counter.sol create mode 100644 examples/message-passing/contracts/src/IBookmark.sol create mode 100644 examples/message-passing/contracts/src/ICounter.sol create mode 100644 examples/message-passing/contracts/src/IL1Block.sol create mode 100644 examples/message-passing/contracts/src/IL1CrossDomainMessenger.sol create mode 100644 examples/message-passing/contracts/src/IL2CrossDomainMessenger.sol create mode 100644 examples/message-passing/contracts/src/L1CrossDomainMessenger.sol create mode 100644 examples/message-passing/contracts/src/L2CrossDomainMessenger.sol create mode 100644 examples/message-passing/contracts/src/Structs.sol create mode 100644 examples/message-passing/contracts/test/E2E.t.sol create mode 100644 examples/message-passing/contracts/test/L1BlockMock.sol create mode 100644 examples/message-passing/contracts/test/Structs.t.sol create mode 100644 examples/message-passing/core/Cargo.toml create mode 100644 examples/message-passing/core/src/contracts.rs create mode 100644 examples/message-passing/core/src/lib.rs create mode 100644 examples/message-passing/foundry.toml create mode 100644 examples/message-passing/methods/Cargo.toml create mode 100644 examples/message-passing/methods/README.md create mode 100644 examples/message-passing/methods/build.rs create mode 100644 examples/message-passing/methods/guest/Cargo.toml create mode 100644 examples/message-passing/methods/guest/src/bin/cross_domain_messenger.rs create mode 100644 examples/message-passing/methods/src/lib.rs create mode 100644 examples/message-passing/remappings.txt create mode 100644 examples/message-passing/rust-toolchain.toml create mode 100644 examples/message-passing/scripts/Deploy.s.sol diff --git a/examples/message-passing/.gitignore b/examples/message-passing/.gitignore new file mode 100644 index 00000000..7c2e0811 --- /dev/null +++ b/examples/message-passing/.gitignore @@ -0,0 +1,26 @@ +# 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 diff --git a/examples/message-passing/.solhint.json b/examples/message-passing/.solhint.json new file mode 100644 index 00000000..d7c3de98 --- /dev/null +++ b/examples/message-passing/.solhint.json @@ -0,0 +1,3 @@ +{ + "extends": "solhint:default" +} diff --git a/examples/message-passing/Cargo.toml b/examples/message-passing/Cargo.toml new file mode 100644 index 00000000..3ce8c1b9 --- /dev/null +++ b/examples/message-passing/Cargo.toml @@ -0,0 +1,41 @@ +[workspace] +resolver = "2" +members = ["apps", "core", "methods"] +exclude = ["lib"] + +[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" } + +# 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.2.1", features = ["full"] } +alloy-primitives = { version = "0.7", features = ["rlp", "serde", "std"] } +alloy-sol-types = { version = "0.7" } +anyhow = { version = "1.0.75" } +bincode = { version = "1.3" } +bytemuck = { version = "1.14" } +clap = { version = "4.5" } +ethers = { version = "2.0" } +hex = { version = "0.4" } +log = { version = "0.4" } +cross-domain-messenger-methods = { path = "./methods" } +cross-domain-messenger-core = { path = "./core" } +serde = { version = "1.0", features = ["derive", "std"] } +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 diff --git a/examples/message-passing/apps/Cargo.toml b/examples/message-passing/apps/Cargo.toml new file mode 100644 index 00000000..eb7a3cee --- /dev/null +++ b/examples/message-passing/apps/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "apps" +version = { workspace = true } +edition = { workspace = true } + +[dependencies] +alloy = { workspace = true } +alloy-primitives = { workspace = true } +anyhow = { workspace = true } +clap = { workspace = true, features = ["derive", "env"] } +cross-domain-messenger-core = { workspace = true } +cross-domain-messenger-methods = { workspace = true } +risc0-ethereum-contracts = { workspace = true } +risc0-steel = { workspace = true, features = ["host"] } +risc0-zkvm = { workspace = true, features = ["client"] } +tokio = { workspace = true } +tracing-subscriber = { workspace = true } +url = { workspace = true } diff --git a/examples/message-passing/apps/README.md b/examples/message-passing/apps/README.md new file mode 100644 index 00000000..08f8c22c --- /dev/null +++ b/examples/message-passing/apps/README.md @@ -0,0 +1,38 @@ +# Apps + +## Publisher + +The [`publisher` CLI][publisher], is an example application that sends an off-chain proof request to the RISC Zero zkVM, and publishes the received proofs to your deployed [Counter] contract. + +### Usage + +Run the `publisher` with: + +```sh +cargo run --bin publisher +``` + +```text +$ cargo run --bin publisher -- --help + +Usage: publisher --eth-wallet-private-key --rpc-url --contract --token --account + +Options: + --eth-wallet-private-key + Ethereum Node endpoint [env: ETH_WALLET_PRIVATE_KEY=] + --rpc-url + Ethereum Node endpoint [env: RPC_URL=] + --contract + Counter's contract address on Ethereum + --token + ERC20 contract address on Ethereum + --account + Account address to read the balance_of on Ethereum + -h, --help + Print help + -V, --version + Print version +``` + +[publisher]: ./src/bin/publisher.rs +[Counter]: ../contracts/Counter.sol diff --git a/examples/message-passing/apps/src/bin/publisher.rs b/examples/message-passing/apps/src/bin/publisher.rs new file mode 100644 index 00000000..639ddd38 --- /dev/null +++ b/examples/message-passing/apps/src/bin/publisher.rs @@ -0,0 +1,170 @@ +// 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. + +// This application demonstrates how to send an off-chain proof request +// to the Bonsai proving service and publish the received proofs directly +// to your deployed app contract. + +use alloy::{ + network::EthereumWallet, providers::ProviderBuilder, signers::local::PrivateKeySigner, sol, + sol_types::SolCall, +}; +use alloy_primitives::Address; +use anyhow::{ensure, Context, Result}; +use clap::Parser; +use cross_domain_messenger_core::{ + contracts::{ + IBookmarkService, IL1CrossDomainMessenger, IL1CrossDomainMessengerService, + IL2CrossDomainMessengerService, + }, + CrossDomainMessengerInput, +}; +use cross_domain_messenger_methods::CROSS_DOMAIN_MESSENGER_ELF; +use risc0_ethereum_contracts::encode_seal; +use risc0_steel::{ethereum::EthEvmEnv, Contract}; +use risc0_zkvm::{default_prover, ExecutorEnv, ProverOpts, VerifierContext}; +use tokio::task; +use tracing_subscriber::EnvFilter; +use url::Url; + +// Contract to call via L1. +sol!("../contracts/src/ICounter.sol"); + +/// Arguments of the publisher CLI. +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +struct Args { + /// Ethereum Node endpoint. + #[clap(long, env)] + eth_wallet_private_key: PrivateKeySigner, + + /// Ethereum Node endpoint. + #[clap(long, env)] + l1_rpc_url: Url, + + /// Ethereum Node endpoint. + #[clap(long, env)] + l2_rpc_url: Url, + + /// Target's contract address on L2 + #[clap(long, env)] + target_address: Address, + + /// l1_cross_domain_messenger_address's contract address on L1 + #[clap(long, env)] + l1_cross_domain_messenger_address: Address, + + /// l2_cross_domain_messenger_address's contract address on L2 + #[clap(long, env)] + l2_cross_domain_messenger_address: Address, +} + +#[tokio::main] +async fn main() -> Result<()> { + // Initialize tracing. In order to view logs, run `RUST_LOG=info cargo run` + tracing_subscriber::fmt() + .with_env_filter(EnvFilter::from_default_env()) + .init(); + // Parse the command line arguments. + let args = Args::parse(); + + // Create an alloy provider for that private key and URL. + let wallet = EthereumWallet::from(args.eth_wallet_private_key); + let l1_provider = ProviderBuilder::new() + .with_recommended_fillers() + .wallet(wallet.clone()) + .on_http(args.l1_rpc_url); + + let l2_provider = ProviderBuilder::new() + .with_recommended_fillers() + .wallet(wallet.clone()) + .on_http(args.l2_rpc_url); + + // Instantiate all the contracts we want to call. + let l1_messenger_contract = IL1CrossDomainMessengerService::new( + args.l1_cross_domain_messenger_address, + l1_provider.clone(), + ); + let l2_messenger_contract = IL2CrossDomainMessengerService::new( + args.l2_cross_domain_messenger_address, + l2_provider.clone(), + ); + let bookmark_contract = + IBookmarkService::new(args.l2_cross_domain_messenger_address, l2_provider.clone()); + + // Prepare the message to be passed from L1 to L2 + let target = args.target_address; + let data = ICounter::incrementCall {}.abi_encode(); + + // Send a transaction calling IL1CrossDomainMessenger.sendMessage + let (message, message_block_number) = l1_messenger_contract + .send_message(target, data.into()) + .await?; + + // Bookmark the block number of the message + let bookmark_block_number = bookmark_contract.bookmark(message_block_number).await?; + + // Run Steel: + // Create an EVM environment from that provider and a block number. + let mut env = + EthEvmEnv::from_provider(l1_provider.clone(), bookmark_block_number.into()).await?; + // Prepare the function call to be called inside steal + let call = IL1CrossDomainMessenger::containsCall { + digest: message.digest(), + }; + // Preflight the call to prepare the input for the guest. + let mut contract = Contract::preflight(args.l1_cross_domain_messenger_address, &mut env); + let success = contract.call_builder(&call).call().await?._0; + ensure!(success, "message {} not found", call.digest); + // Finally, construct the input for the guest. + let evm_input = env.into_input().await?; + let cross_domain_messenger_input = CrossDomainMessengerInput { + l1_cross_domain_messenger: args.l1_cross_domain_messenger_address, + message, + }; + + println!("Creating proof for the constructed input..."); + let prove_info = task::spawn_blocking(move || { + let env = ExecutorEnv::builder() + .write(&evm_input)? + .write(&cross_domain_messenger_input)? + .build() + .unwrap(); + + default_prover().prove_with_ctx( + env, + &VerifierContext::default(), + CROSS_DOMAIN_MESSENGER_ELF, + &ProverOpts::groth16(), + ) + }) + .await? + .context("failed to create proof")?; + println!( + "Proving finished in {} cycles", + prove_info.stats.total_cycles + ); + let receipt = prove_info.receipt; + + // Encode the groth16 seal with the selector. + let seal = encode_seal(&receipt)?; + + // Call the increment function of the contract and wait for confirmation. + let msg_hash = l2_messenger_contract + .relay_message(receipt.journal.bytes.into(), seal.into()) + .await?; + println!("Message relayed {:?}", msg_hash); + + Ok(()) +} diff --git a/examples/message-passing/contracts/src/Bookmark.sol b/examples/message-passing/contracts/src/Bookmark.sol new file mode 100644 index 00000000..c09beb06 --- /dev/null +++ b/examples/message-passing/contracts/src/Bookmark.sol @@ -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]; + } +} diff --git a/examples/message-passing/contracts/src/Counter.sol b/examples/message-passing/contracts/src/Counter.sol new file mode 100644 index 00000000..045e2309 --- /dev/null +++ b/examples/message-passing/contracts/src/Counter.sol @@ -0,0 +1,47 @@ +// 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 "./IL2CrossDomainMessenger.sol"; +import "./ICounter.sol"; + +contract Counter is ICounter { + IL2CrossDomainMessenger private immutable L2_CROSS_DOMAIN_MESSENGER; + address private immutable L1_SENDER; + + uint256 private count; + + constructor(IL2CrossDomainMessenger l2CrossDomainMessenger, address l1Sender) { + L2_CROSS_DOMAIN_MESSENGER = l2CrossDomainMessenger; + L1_SENDER = l1Sender; + count = 0; + } + + function increment() external { + require( + msg.sender == address(L2_CROSS_DOMAIN_MESSENGER), + "Counter: Only L2CrossDomainMessenger can increment the counter" + ); + require(L2_CROSS_DOMAIN_MESSENGER.xDomainMessageSender() == L1_SENDER, "Counter: Invalid L1 sender"); + + count++; + } + + function get() external view returns (uint256) { + return count; + } +} diff --git a/examples/message-passing/contracts/src/IBookmark.sol b/examples/message-passing/contracts/src/IBookmark.sol new file mode 100644 index 00000000..6e31f28d --- /dev/null +++ b/examples/message-passing/contracts/src/IBookmark.sol @@ -0,0 +1,29 @@ +// 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; + +/// @notice Interface to bookmark L1 blocks. +interface IBookmark { + /// @notice A new L1 block has been bookmarked. + event BookmarkedL1Block(uint64 number, bytes32 hash); + + /// @notice Bookmarks the current L1 block. + function bookmarkL1Block() external returns (uint64); + + /// @notice Returns the bookmarked hash of the block with the given number. + function getBookmark(uint64 blockNumber) external view returns (bytes32); +} diff --git a/examples/message-passing/contracts/src/ICounter.sol b/examples/message-passing/contracts/src/ICounter.sol new file mode 100644 index 00000000..9a6c9826 --- /dev/null +++ b/examples/message-passing/contracts/src/ICounter.sol @@ -0,0 +1,25 @@ +// 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; + +interface ICounter { + /// @notice Increments the counter. + function increment() external; + + /// @notice Returns the value of the counter. + function get() external view returns (uint256); +} diff --git a/examples/message-passing/contracts/src/IL1Block.sol b/examples/message-passing/contracts/src/IL1Block.sol new file mode 100644 index 00000000..33bd8a91 --- /dev/null +++ b/examples/message-passing/contracts/src/IL1Block.sol @@ -0,0 +1,26 @@ +// 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; + +/// @notice The L1Block predeploy gives users access to information about the last known L1 block. +interface IL1Block { + /// @notice The latest L1 block number known by the L2 system. + function number() external view returns (uint64); + + /// @notice The latest L1 blockhash. + function hash() external view returns (bytes32); +} diff --git a/examples/message-passing/contracts/src/IL1CrossDomainMessenger.sol b/examples/message-passing/contracts/src/IL1CrossDomainMessenger.sol new file mode 100644 index 00000000..9a1c1a4f --- /dev/null +++ b/examples/message-passing/contracts/src/IL1CrossDomainMessenger.sol @@ -0,0 +1,35 @@ +// 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; + +interface IL1CrossDomainMessenger { + /// @notice Emitted whenever a message is sent to the other chain. + /// @param target Address of the recipient of the message. + /// @param sender Address of the sender of the message. + /// @param data Message to trigger the recipient address with. + /// @param messageNonce Unique nonce attached to the message. + event SentMessage(address indexed target, address sender, bytes data, uint256 messageNonce); + + /// @notice Returns whether the digest of the message has been committed to be relayed. + function contains(bytes32 digest) external view returns (bool); + + /// @notice Sends a new message by commiting to its digest. + function sendMessage(address target, bytes calldata data) external; + + /// @notice Retrieves the next message nonce. + function messageNonce() external view returns (uint256); +} diff --git a/examples/message-passing/contracts/src/IL2CrossDomainMessenger.sol b/examples/message-passing/contracts/src/IL2CrossDomainMessenger.sol new file mode 100644 index 00000000..22a00c23 --- /dev/null +++ b/examples/message-passing/contracts/src/IL2CrossDomainMessenger.sol @@ -0,0 +1,35 @@ +// 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"; + +interface IL2CrossDomainMessenger is IBookmark { + /// @notice Emitted whenever a message is successfully relayed on this chain. + /// @param msgHash Hash of the message that was relayed. + event RelayedMessage(bytes32 indexed msgHash); + + /// @notice Relays a message that was sent to the other CrossDomainMessenger contract. + /// @param journal The full journal. + /// @param seal The encoded cryptographic proof (i.e. SNARK). + function relayMessage(bytes calldata journal, bytes calldata seal) external; + + /// @notice Retrieves the address of the contract or wallet that initiated the currently + /// executing message on the other chain. Will throw an error if there is no message + /// currently being executed. Allows the recipient of a call to see who triggered it. + function xDomainMessageSender() external view returns (address); +} diff --git a/examples/message-passing/contracts/src/L1CrossDomainMessenger.sol b/examples/message-passing/contracts/src/L1CrossDomainMessenger.sol new file mode 100644 index 00000000..d8eedbb5 --- /dev/null +++ b/examples/message-passing/contracts/src/L1CrossDomainMessenger.sol @@ -0,0 +1,50 @@ +// 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 {Message, Digest} from "./Structs.sol"; +import {IL1CrossDomainMessenger} from "./IL1CrossDomainMessenger.sol"; + +contract L1CrossDomainMessenger is IL1CrossDomainMessenger { + using Digest for Message; + + mapping(bytes32 => bool) private messages; + uint256 private msgNonce; + + constructor() { + msgNonce = 0; + } + + function contains(bytes32 digest) external view returns (bool) { + return messages[digest]; + } + + function sendMessage(address target, bytes calldata data) external { + Message memory message = Message(target, msg.sender, data, messageNonce()); + messages[message.digest()] = true; + + emit SentMessage(message.target, message.sender, message.data, message.nonce); + + unchecked { + ++msgNonce; + } + } + + function messageNonce() public view returns (uint256) { + return msgNonce; + } +} diff --git a/examples/message-passing/contracts/src/L2CrossDomainMessenger.sol b/examples/message-passing/contracts/src/L2CrossDomainMessenger.sol new file mode 100644 index 00000000..37c1b115 --- /dev/null +++ b/examples/message-passing/contracts/src/L2CrossDomainMessenger.sol @@ -0,0 +1,97 @@ +// 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 {Address} from "openzeppelin/contracts/utils/Address.sol"; +import {SafeCast} from "openzeppelin/contracts/utils/math/SafeCast.sol"; +import {IRiscZeroVerifier} from "risc0/IRiscZeroVerifier.sol"; +import {Steel} from "risc0/steel/Steel.sol"; +import {IL2CrossDomainMessenger} from "./IL2CrossDomainMessenger.sol"; +import {IL1Block} from "./IL1Block.sol"; +import {Bookmark} from "./Bookmark.sol"; +import {Journal, Message, Digest} from "./Structs.sol"; + +/// @notice L1Bridging verifier contract for RISC Zero receipts of execution. +contract L2CrossDomainMessenger is IL2CrossDomainMessenger, Bookmark { + using Address for address; + using SafeCast for uint256; + using Digest for Journal; + + /// @notice Value used for the L1 sender storage slot before an actual sender is set. This value is non-zero to + /// reduce the gas cost of message passing transactions. + address internal constant DEFAULT_L1_SENDER = 0x000000000000000000000000000000000000dEaD; + + /// @notice CrossDomainMessenger contract on the other chain. + address private immutable L1_CROSS_DOMAIN_MESSENGER; + + /// @notice RiscZero verifier contract. + IRiscZeroVerifier private immutable VERIFIER; + + /// @notice ID of guest. + bytes32 private immutable IMAGE_ID; + + /// @notice Address of the sender of the currently executing message on the other chain. + address internal xDomainMsgSender; + + /// @notice Mapping of message hashes to boolean receipt values. A message will only be present in this mapping if + // it has successfully been relayed, and can therefore not be relayed again. + mapping(bytes32 => bool) private relayedMessages; + + constructor(IRiscZeroVerifier verifier, bytes32 imageId, address l1CrossDomainMessenger, IL1Block l1Block) + Bookmark(l1Block) + { + VERIFIER = verifier; + IMAGE_ID = imageId; + L1_CROSS_DOMAIN_MESSENGER = l1CrossDomainMessenger; + + xDomainMsgSender = DEFAULT_L1_SENDER; + } + + function relayMessage(bytes calldata journalData, bytes calldata seal) external { + VERIFIER.verify(seal, IMAGE_ID, sha256(journalData)); + + Journal memory journal = abi.decode(journalData, (Journal)); + require(journal.l1CrossDomainMessenger == L1_CROSS_DOMAIN_MESSENGER, "invalid l1CrossDomainMessenger"); + require(validateCommitment(journal.commitment), "commitment verification failed"); + + relayVerifiedMessage(journal.message, journal.messageDigest); + } + + function xDomainMessageSender() external view returns (address) { + require(xDomainMsgSender != DEFAULT_L1_SENDER, "L2CrossDomainMessenger: xDomainMsgSender is not set"); + + return xDomainMsgSender; + } + + function validateCommitment(Steel.Commitment memory commitment) internal view returns (bool) { + return commitment.blockHash == Bookmark.blocks[commitment.blockNumber.toUint64()]; + } + + function relayVerifiedMessage(Message memory message, bytes32 digest) internal { + require(xDomainMsgSender == DEFAULT_L1_SENDER, "L2CrossDomainMessenger: reentrant call"); + require(!relayedMessages[digest], "L2CrossDomainMessenger: message already relayed"); + + xDomainMsgSender = message.sender; + (bool success, bytes memory returndata) = message.target.call(message.data); + xDomainMsgSender = DEFAULT_L1_SENDER; + + message.target.verifyCallResultFromTarget(success, returndata); + relayedMessages[digest] = true; + + emit RelayedMessage(digest); + } +} diff --git a/examples/message-passing/contracts/src/Structs.sol b/examples/message-passing/contracts/src/Structs.sol new file mode 100644 index 00000000..2df49051 --- /dev/null +++ b/examples/message-passing/contracts/src/Structs.sol @@ -0,0 +1,51 @@ +// 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 {Steel} from "risc0/steel/Steel.sol"; + +/// @notice A Message to be relayed. +struct Message { + address target; + address sender; + bytes data; + uint256 nonce; +} + +/// @notice The Journal returned by the guest. +struct Journal { + /// @notice The actual Steel commitment. + Steel.Commitment commitment; + /// @notice Address of the L1 Messenger used to verify that the message was sent. + address l1CrossDomainMessenger; + /// @notice The actual message to be relayed. + Message message; + /// @notice Precomputed digest of the message. + bytes32 messageDigest; +} + +library Digest { + bytes32 internal constant MESSAGE_TYPEHASH = + keccak256("Message(address target,address sender,bytes data,uint256 nonce)"); + + /// @notice Returns the `hashStruct` of the message as defined in EIP-712. + function digest(Message memory message) internal pure returns (bytes32) { + return keccak256( + abi.encode(MESSAGE_TYPEHASH, message.target, message.sender, keccak256(message.data), message.nonce) + ); + } +} diff --git a/examples/message-passing/contracts/test/E2E.t.sol b/examples/message-passing/contracts/test/E2E.t.sol new file mode 100644 index 00000000..32cb837a --- /dev/null +++ b/examples/message-passing/contracts/test/E2E.t.sol @@ -0,0 +1,109 @@ +// 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 "forge-std/Test.sol"; +import "forge-std/console.sol"; +import {Receipt as RiscZeroReceipt} from "risc0/IRiscZeroVerifier.sol"; +import {RiscZeroMockVerifier} from "risc0/test/RiscZeroMockVerifier.sol"; +import {Journal, Message, Digest} from "../src/Structs.sol"; +import {IL1CrossDomainMessenger} from "../src/IL1CrossDomainMessenger.sol"; +import {L1CrossDomainMessenger} from "../src/L1CrossDomainMessenger.sol"; +import {IL1Block} from "../src/IL1Block.sol"; +import {Journal, L2CrossDomainMessenger} from "../src/L2CrossDomainMessenger.sol"; +import {L1BlockMock} from "./L1BlockMock.sol"; +import {Counter} from "../src/Counter.sol"; +import {Steel} from "risc0/steel/Steel.sol"; + +contract E2ETest is Test { + using Digest for Message; + using Digest for Journal; + + RiscZeroMockVerifier private verifier; + L1CrossDomainMessenger private l1CrossDomainMessenger; + L2CrossDomainMessenger private l2CrossDomainMessenger; + IL1Block private l1Block; + Counter private counter; + address private sender; + + bytes32 internal CROSS_DOMAIN_MESSENGER_IMAGE_ID = bytes32(uint256(0x03)); + + bytes4 MOCK_SELECTOR = bytes4(0); + + function setUp() public { + sender = address(1); + vm.startPrank(sender); + + l1CrossDomainMessenger = new L1CrossDomainMessenger(); + verifier = new RiscZeroMockVerifier(MOCK_SELECTOR); + l1Block = new L1BlockMock(); + l2CrossDomainMessenger = new L2CrossDomainMessenger( + verifier, CROSS_DOMAIN_MESSENGER_IMAGE_ID, address(l1CrossDomainMessenger), l1Block + ); + counter = new Counter(l2CrossDomainMessenger, address(sender)); + } + + function testCounterIncrement() public { + // pass Counter::increment() message + address target = address(counter); + bytes memory data = abi.encodeCall(Counter.increment, ()); + + uint256 previous_count = counter.get(); + + testCrossDomainMessenger(target, data); + + // check that the counter was incremented + assert(counter.get() == previous_count + 1); + } + + function testSHA256() public { + // sha256 hash + address target = address(0x02); + bytes memory data = unicode"こんにちは世界!"; + + testCrossDomainMessenger(target, data); + } + + function testCrossDomainMessenger(address target, bytes memory data) internal { + uint256 nonce = l1CrossDomainMessenger.messageNonce(); + + // send a message on L1 + vm.expectEmit(true, true, false, true); + emit IL1CrossDomainMessenger.SentMessage(target, sender, data, nonce); + l1CrossDomainMessenger.sendMessage(target, data); + + // bookmark the next block + vm.roll(1); + uint256 blockNumber = l2CrossDomainMessenger.bookmarkL1Block(); + bytes32 blockHash = l1Block.hash(); + + // mock the Journal + Message memory message = Message(target, sender, data, nonce); + Journal memory journal = Journal({ + commitment: Steel.Commitment(blockNumber, blockHash), + l1CrossDomainMessenger: address(l1CrossDomainMessenger), + message: message, + messageDigest: message.digest() + }); + // create a mock proof + RiscZeroReceipt memory receipt = + verifier.mockProve(CROSS_DOMAIN_MESSENGER_IMAGE_ID, sha256(abi.encode(journal))); + + // relay the message on L2 + l2CrossDomainMessenger.relayMessage(abi.encode(journal), receipt.seal); + } +} diff --git a/examples/message-passing/contracts/test/L1BlockMock.sol b/examples/message-passing/contracts/test/L1BlockMock.sol new file mode 100644 index 00000000..2fd29612 --- /dev/null +++ b/examples/message-passing/contracts/test/L1BlockMock.sol @@ -0,0 +1,29 @@ +// 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 "../src/IL1Block.sol"; + +contract L1BlockMock is IL1Block { + function number() external view returns (uint64) { + return uint64(block.number - 1); + } + + function hash() external view returns (bytes32) { + return blockhash(block.number - 1); + } +} diff --git a/examples/message-passing/contracts/test/Structs.t.sol b/examples/message-passing/contracts/test/Structs.t.sol new file mode 100644 index 00000000..3eada783 --- /dev/null +++ b/examples/message-passing/contracts/test/Structs.t.sol @@ -0,0 +1,35 @@ +// 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 "forge-std/Test.sol"; +import {Message, Digest} from "../src/Structs.sol"; + +contract DigestTest is Test { + using Digest for Message; + + function testMessageStructHash() public pure { + Message memory message = Message({ + target: 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9, + sender: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, + data: hex"d09de08a", + nonce: 0 + }); + + assertEq(message.digest(), 0x0c6548312532ea5e926eaf99520bb0bd62d4ca58ad70c07d31256331ba7dd4cb); + } +} diff --git a/examples/message-passing/core/Cargo.toml b/examples/message-passing/core/Cargo.toml new file mode 100644 index 00000000..ee4f4e07 --- /dev/null +++ b/examples/message-passing/core/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "cross-domain-messenger-core" +version = "0.1.0" +edition = "2021" + +[dependencies] +alloy-primitives = { workspace = true } +alloy-sol-types = { workspace = true } +risc0-steel = { workspace = true } +serde = { workspace = true } + +[target.'cfg(not(target_os = "zkvm"))'.dependencies] +alloy = { workspace = true } +anyhow = { workspace = true } +tokio = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } \ No newline at end of file diff --git a/examples/message-passing/core/src/contracts.rs b/examples/message-passing/core/src/contracts.rs new file mode 100644 index 00000000..6d84648d --- /dev/null +++ b/examples/message-passing/core/src/contracts.rs @@ -0,0 +1,207 @@ +// 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. + +use std::time::Duration; + +use alloy::{ + network::Ethereum, providers::Provider, rpc::types::TransactionReceipt, sol, + transports::Transport, +}; +use alloy_primitives::{Address, Bytes, B256}; +use alloy_sol_types::SolEvent; +use anyhow::{bail, ensure, Context, Result}; +use tokio::time; + +use crate::{ + contracts::{ + IBookmark::IBookmarkInstance, IL1CrossDomainMessenger::IL1CrossDomainMessengerInstance, + IL2CrossDomainMessenger::IL2CrossDomainMessengerInstance, + }, + Message, +}; + +sol!( + #[sol(rpc, all_derives)] + "../contracts/src/IL1CrossDomainMessenger.sol" +); + +sol!( + #[sol(rpc, all_derives)] + "../contracts/src/IL2CrossDomainMessenger.sol" +); + +// Contract to bookmark L1 blocks for later verification. +sol!( + #[sol(rpc, all_derives)] + "../contracts/src/IBookmark.sol" +); + +#[derive(Clone)] +pub struct IL1CrossDomainMessengerService { + instance: IL1CrossDomainMessengerInstance, +} + +impl IL1CrossDomainMessengerService +where + T: Transport + Clone, + P: Provider + 'static, +{ + pub const TX_TIMEOUT: Duration = Duration::from_secs(30); + + pub fn new(address: Address, provider: P) -> Self { + let instance = IL1CrossDomainMessenger::new(address, provider); + + IL1CrossDomainMessengerService { instance } + } + + pub fn instance(&self) -> &IL1CrossDomainMessengerInstance { + &self.instance + } + + pub async fn contains(&self, digest: B256) -> Result { + tracing::debug!("Calling contains({:?})", digest); + let call = self.instance.contains(digest); + let result = call.call().await?; + Ok(result._0) + } + + pub async fn send_message(&self, target: Address, data: Bytes) -> Result<(Message, u64)> { + tracing::debug!("Calling sendMessage({:?},{:?})", target, data); + let call = self.instance.sendMessage(target, data); + let pending_tx = call.send().await?; + tracing::debug!("Broadcasting tx {}", pending_tx.tx_hash()); + let receipt = pending_tx + .with_timeout(Some(Self::TX_TIMEOUT)) + .get_receipt() + .await?; + + // Process the transaction result + ensure!(receipt.status(), "transaction failed"); + let message_block_number = receipt.block_number.unwrap(); + let event: IL1CrossDomainMessenger::SentMessage = into_event(receipt)?; + println!("Message submitted on L1: {:?}", event); + let message = Message { + target: event.target, + sender: event.sender, + data: event.data, + nonce: event.messageNonce, + }; + + Ok((message, message_block_number)) + } +} + +#[derive(Clone)] +pub struct IL2CrossDomainMessengerService { + instance: IL2CrossDomainMessengerInstance, +} + +impl IL2CrossDomainMessengerService +where + T: Transport + Clone, + P: Provider + 'static, +{ + pub const TX_TIMEOUT: Duration = Duration::from_secs(30); + + pub fn new(address: Address, provider: P) -> Self { + let instance = IL2CrossDomainMessenger::new(address, provider); + + IL2CrossDomainMessengerService { instance } + } + + pub fn instance(&self) -> &IL2CrossDomainMessengerInstance { + &self.instance + } + + pub async fn relay_message(&self, journal: Bytes, seal: Bytes) -> Result { + tracing::debug!("Calling relayMessage({:?},{:?})", journal, seal); + let call = self.instance.relayMessage(journal, seal); + let pending_tx = call.send().await?; + tracing::debug!("Broadcasting tx {}", pending_tx.tx_hash()); + let receipt = pending_tx + .with_timeout(Some(Self::TX_TIMEOUT)) + .get_receipt() + .await?; + + let event = into_event::(receipt)?; + Ok(event.msgHash) + } +} + +#[derive(Clone)] +pub struct IBookmarkService { + instance: IBookmarkInstance, +} + +impl IBookmarkService +where + T: Transport + Clone, + P: Provider + 'static, +{ + pub const TX_TIMEOUT: Duration = Duration::from_secs(30); + + pub fn new(address: Address, provider: P) -> Self { + let instance = IBookmark::new(address, provider); + + IBookmarkService { instance } + } + + pub fn instance(&self) -> &IBookmarkInstance { + &self.instance + } + + pub async fn bookmark(&self, message_block_number: u64) -> Result { + // Call IBookmark.bookmarkL1Block until we can bookmark a block that contains the sent message. + let bookmark_call = self.instance.bookmarkL1Block(); + loop { + let current_block_number = bookmark_call.call().await?._0; + if current_block_number >= message_block_number { + break; + } + println!( + "Waiting for L1 block to catch up: {} < {}", + current_block_number, message_block_number + ); + time::sleep(Duration::from_secs(5)).await; + } + + // Send a transaction calling IBookmark.bookmarkL1Block to create an on-chain bookmark. + let pending_tx = bookmark_call + .send() + .await + .context("failed to send bookmarkL1Block")?; + let receipt = pending_tx + .with_timeout(Some(Duration::from_secs(60))) + .get_receipt() + .await + .context("failed to confirm tx")?; + + // Get the number of the actual bookmarked block. + let event: IBookmark::BookmarkedL1Block = into_event(receipt)?; + let bookmark_block_number = event.number; + + Ok(bookmark_block_number) + } +} + +fn into_event(receipt: TransactionReceipt) -> Result { + ensure!(receipt.status(), "transaction failed"); + for log in receipt.inner.logs() { + match log.log_decode::() { + Ok(decoded_log) => return Ok(decoded_log.inner.data), + Err(_) => {} + } + } + bail!("invalid events emitted") +} diff --git a/examples/message-passing/core/src/lib.rs b/examples/message-passing/core/src/lib.rs new file mode 100644 index 00000000..f016b3b9 --- /dev/null +++ b/examples/message-passing/core/src/lib.rs @@ -0,0 +1,75 @@ +// 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. + +use alloy_primitives::{Address, B256}; +use alloy_sol_types::{sol, SolStruct}; +use risc0_steel::SolCommitment; +use serde::{Deserialize, Serialize}; + +#[cfg(not(target_os = "zkvm"))] +pub mod contracts; + +sol! { + interface IL1CrossDomainMessenger { + /// Returns whether the digest of the message has been committed to be relayed. + function contains(bytes32 digest) external view returns (bool); + } +} + +sol! { + /// A Message to be relayed. + #[derive(Serialize, Deserialize)] + struct Message { + address target; + address sender; + bytes data; + uint256 nonce; + } + + /// Journal returned by the guest. + struct Journal { + SolCommitment commitment; + address l1CrossDomainMessenger; + Message message; + bytes32 messageDigest; + } +} + +impl Message { + #[inline] + pub fn digest(&self) -> B256 { + return self.eip712_hash_struct(); + } +} + +#[derive(Serialize, Deserialize)] +pub struct CrossDomainMessengerInput { + pub l1_cross_domain_messenger: Address, + pub message: Message, +} + +impl CrossDomainMessengerInput { + /// Converts the input into the corresponding [Journal], leaving the commitment empty. + #[inline] + pub fn into_journal(self) -> Journal { + let digest = self.message.digest(); + + Journal { + commitment: SolCommitment::default(), + l1CrossDomainMessenger: self.l1_cross_domain_messenger, + message: self.message, + messageDigest: digest, + } + } +} diff --git a/examples/message-passing/foundry.toml b/examples/message-passing/foundry.toml new file mode 100644 index 00000000..0d3ed72f --- /dev/null +++ b/examples/message-passing/foundry.toml @@ -0,0 +1,8 @@ +[profile.default] +src = "contracts" +out = "out" +libs = ["../../lib", "../../contracts/src"] +test = "tests" +ffi = true + +# See more config options https://github.com/foundry-rs/foundry/tree/master/config diff --git a/examples/message-passing/methods/Cargo.toml b/examples/message-passing/methods/Cargo.toml new file mode 100644 index 00000000..c3a3c24e --- /dev/null +++ b/examples/message-passing/methods/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "cross-domain-messenger-methods" +version = { workspace = true } +edition = { workspace = true } + +[package.metadata.risc0] +methods = ["guest"] + +[build-dependencies] +cross-domain-messenger-core = { workspace = true } +hex = { workspace = true } +risc0-build = { workspace = true } +risc0-build-ethereum = { workspace = true } +risc0-zkp = { workspace = true } + +[dev-dependencies] +alloy-primitives = { workspace = true } +alloy-sol-types = { workspace = true } +risc0-zkvm = { workspace = true, features = ["client"] } diff --git a/examples/message-passing/methods/README.md b/examples/message-passing/methods/README.md new file mode 100644 index 00000000..98e32535 --- /dev/null +++ b/examples/message-passing/methods/README.md @@ -0,0 +1,31 @@ +# zkVM Methods + +This directory contains the [zkVM] portion of your [RISC Zero] application. +This is where you will define one or more [guest programs] to act as a coprocessor to your [on-chain logic]. + +> In typical use cases, the only code in this directory that you will need to edit is inside [guest/src/bin]. + + +### Writing Guest Code + +To learn to write code for the zkVM, we recommend [Guest Code 101]. + +Examples of what you can do in the guest can be found in the [RISC Zero examples]. + + +### From Guest Code to Binary File + +Code in the `methods/guest` directory will be compiled into one or more binaries. + +Build configuration for the methods is included in `methods/build.rs`. + +Each will have a corresponding image ID, which is a hash identifying the program. + + +[zkVM]: https://dev.risczero.com/zkvm +[RISC Zero]: https://www.risczero.com/ +[guest programs]: https://dev.risczero.com/terminology#guest-program +[on-chain logic]: ../contracts/ +[guest/src/bin]: ./guest/src/bin/ +[Guest Code 101]: https://dev.risczero.com/zkvm/developer-guide/guest-code-101 +[RISC Zero examples]: https://github.com/risc0/tree/v0.18.0/examples \ No newline at end of file diff --git a/examples/message-passing/methods/build.rs b/examples/message-passing/methods/build.rs new file mode 100644 index 00000000..4d704506 --- /dev/null +++ b/examples/message-passing/methods/build.rs @@ -0,0 +1,46 @@ +// 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. + +use std::{collections::HashMap, env}; + +use risc0_build::{embed_methods_with_options, DockerOptions, GuestOptions}; +use risc0_build_ethereum::generate_solidity_files; + +// Paths where the generated Solidity files will be written. +const SOLIDITY_IMAGE_ID_PATH: &str = "../contracts/src/ImageID.sol"; +const SOLIDITY_ELF_PATH: &str = "../contracts/src/Elf.sol"; + +fn main() { + // Builds can be made deterministic, and thereby reproducible, by using Docker to build the + // guest. Check the RISC0_USE_DOCKER variable and use Docker to build the guest if set. + let use_docker = env::var("RISC0_USE_DOCKER").ok().map(|_| DockerOptions { + root_dir: Some("../".into()), + }); + + // Generate Rust source files for the methods crate. + let guests = embed_methods_with_options(HashMap::from([( + "cross-domain-messenger-guests", + GuestOptions { + features: Vec::new(), + use_docker, + }, + )])); + + // Generate Solidity source files for use with Forge. + let solidity_opts = risc0_build_ethereum::Options::default() + .with_image_id_sol_path(SOLIDITY_IMAGE_ID_PATH) + .with_elf_sol_path(SOLIDITY_ELF_PATH); + + generate_solidity_files(guests.as_slice(), &solidity_opts).unwrap(); +} diff --git a/examples/message-passing/methods/guest/Cargo.toml b/examples/message-passing/methods/guest/Cargo.toml new file mode 100644 index 00000000..b66f68b2 --- /dev/null +++ b/examples/message-passing/methods/guest/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "cross-domain-messenger-guests" +version = "0.1.0" +edition = "2021" + +[[bin]] +name = "cross_domain_messenger" +path = "src/bin/cross_domain_messenger.rs" + +[workspace] + +[dependencies] + +# Force the `compile-time-rng` feature, to prevent `getrandom` from being used. +ahash = { version = "0.8", default-features = false, features = ["compile-time-rng"] } +alloy-sol-types = { version = "0.7" } +risc0-steel = { path = "../../../../steel" } +cross-domain-messenger-core = { path = "../../core" } +risc0-zkvm = { git = "https://github.com/risc0/risc0", branch = "main", default-features = false, features = ["std"] } + +[patch.crates-io] +# use optimized risc0 circuit +crypto-bigint = { git = "https://github.com/risc0/RustCrypto-crypto-bigint", tag = "v0.5.5-risczero.0" } +k256 = { git = "https://github.com/risc0/RustCrypto-elliptic-curves", tag = "k256/v0.13.3-risczero.0" } +sha2 = { git = "https://github.com/risc0/RustCrypto-hashes", tag = "sha2-v0.10.8-risczero.0" } diff --git a/examples/message-passing/methods/guest/src/bin/cross_domain_messenger.rs b/examples/message-passing/methods/guest/src/bin/cross_domain_messenger.rs new file mode 100644 index 00000000..fd507d42 --- /dev/null +++ b/examples/message-passing/methods/guest/src/bin/cross_domain_messenger.rs @@ -0,0 +1,48 @@ +// 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. + +#![no_main] + +use alloy_sol_types::SolValue; +use cross_domain_messenger_core::{CrossDomainMessengerInput, IL1CrossDomainMessenger}; +use risc0_steel::{ethereum::EthEvmInput, Contract}; +use risc0_zkvm::guest::env; + +risc0_zkvm::guest::entry!(main); + +fn main() { + // Read the input from the guest environment. + let input: EthEvmInput = env::read(); + // Converts the input into a `EvmEnv` for execution. + let env = input.into_env(); + + // Read the remaining input and create the journal from it + let cross_domain_messenger_input: CrossDomainMessengerInput = env::read(); + let mut journal = cross_domain_messenger_input.into_journal(); + + // Execute the view call; it returns the result in the type generated by the `sol!` macro. + let call = IL1CrossDomainMessenger::containsCall { + digest: journal.messageDigest, + }; + let returns = Contract::new(journal.l1CrossDomainMessenger, &env) + .call_builder(&call) + .call(); + + // Check that the message exists. + assert!(returns._0, "message does not exist"); + + // Commit the block hash and number used when deriving `view_call_env` to the journal. + journal.commitment = env.into_commitment(); + env::commit_slice(&journal.abi_encode()); +} diff --git a/examples/message-passing/methods/src/lib.rs b/examples/message-passing/methods/src/lib.rs new file mode 100644 index 00000000..67e83b84 --- /dev/null +++ b/examples/message-passing/methods/src/lib.rs @@ -0,0 +1,16 @@ +// 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. + +//! Generated crate containing the image ID and ELF binary of the build guest. +include!(concat!(env!("OUT_DIR"), "/methods.rs")); diff --git a/examples/message-passing/remappings.txt b/examples/message-passing/remappings.txt new file mode 100644 index 00000000..515a0e6f --- /dev/null +++ b/examples/message-passing/remappings.txt @@ -0,0 +1,3 @@ +forge-std/=../../lib/forge-std/src/ +openzeppelin/=../../lib/openzeppelin-contracts/ +risc0/=../../contracts/src/ diff --git a/examples/message-passing/rust-toolchain.toml b/examples/message-passing/rust-toolchain.toml new file mode 100644 index 00000000..6c18dfcd --- /dev/null +++ b/examples/message-passing/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "stable" +components = ["rustfmt", "rust-src"] +profile = "minimal" \ No newline at end of file diff --git a/examples/message-passing/scripts/Deploy.s.sol b/examples/message-passing/scripts/Deploy.s.sol new file mode 100644 index 00000000..1979a09c --- /dev/null +++ b/examples/message-passing/scripts/Deploy.s.sol @@ -0,0 +1,51 @@ +// Copyright (c) 2024 RISC Zero, Inc. +// +// All rights reserved. + +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 {ControlID, RiscZeroGroth16Verifier} from "risc0/groth16/RiscZeroGroth16Verifier.sol"; +import {RiscZeroMockVerifier} from "risc0/test/RiscZeroMockVerifier.sol"; +import {IL1CrossDomainMessenger} from "../contracts/src/IL1CrossDomainMessenger.sol"; +import {L1CrossDomainMessenger} from "../contracts/src/L1CrossDomainMessenger.sol"; +import {IL2CrossDomainMessenger} from "../contracts/src/IL2CrossDomainMessenger.sol"; +import {L2CrossDomainMessenger} from "../contracts/src/L2CrossDomainMessenger.sol"; +import {IL1Block} from "../contracts/src/IL1Block.sol"; +import {L1BlockMock} from "../contracts/test/L1BlockMock.sol"; +import {ImageID} from "../contracts/src/ImageID.sol"; +import {Counter} from "../contracts/src/Counter.sol"; + +contract Deploy is Script, RiscZeroCheats { + function run() external { + // Read and log the chainID + uint256 chainId = block.chainid; + console2.log("You are deploying on ChainID %d", chainId); + + deployAnvil(); + } + + function deployAnvil() internal { + // load ENV variables first + uint256 key = vm.envUint("L1_ADMIN_PRIVATE_KEY"); + + vm.startBroadcast(key); + + IL1CrossDomainMessenger l1CrossDomainMessenger = new L1CrossDomainMessenger(); + console2.log("Deployed L1 IL1CrossDomainMessenger to", address(l1CrossDomainMessenger)); + + IRiscZeroVerifier verifier = deployRiscZeroVerifier(); + + IL1Block l1Block = new L1BlockMock(); + + IL2CrossDomainMessenger l2CrossDomainMessenger = new L2CrossDomainMessenger(verifier, ImageID.CROSS_DOMAIN_MESSENGER_ID, address(l1CrossDomainMessenger), l1Block); + console2.log("Deployed L2 L2CrossDomainMessenger to", address(l2CrossDomainMessenger)); + + Counter counter = new Counter(l2CrossDomainMessenger, address(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266)); + console2.log("Deployed L1 Counter to", address(counter)); + + vm.stopBroadcast(); + } +} From c7c739301b6b3443ddcdfbea4fcdccdfb9fd2553 Mon Sep 17 00:00:00 2001 From: capossele Date: Mon, 12 Aug 2024 10:57:24 -0700 Subject: [PATCH 02/44] fix copyright --- examples/message-passing/scripts/Deploy.s.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/message-passing/scripts/Deploy.s.sol b/examples/message-passing/scripts/Deploy.s.sol index 1979a09c..9ba500b0 100644 --- a/examples/message-passing/scripts/Deploy.s.sol +++ b/examples/message-passing/scripts/Deploy.s.sol @@ -1,4 +1,4 @@ -// Copyright (c) 2024 RISC Zero, Inc. +// Copyright 2024 RISC Zero, Inc. // // All rights reserved. From 551851130dda14868cf697b5880569af331d132d Mon Sep 17 00:00:00 2001 From: capossele Date: Mon, 12 Aug 2024 11:21:21 -0700 Subject: [PATCH 03/44] fix license --- examples/message-passing/scripts/Deploy.s.sol | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/examples/message-passing/scripts/Deploy.s.sol b/examples/message-passing/scripts/Deploy.s.sol index 9ba500b0..f018286d 100644 --- a/examples/message-passing/scripts/Deploy.s.sol +++ b/examples/message-passing/scripts/Deploy.s.sol @@ -1,6 +1,18 @@ // Copyright 2024 RISC Zero, Inc. // -// All rights reserved. +// 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; From d9be70e9779ce30220d673878612fd03d2ba9b01 Mon Sep 17 00:00:00 2001 From: Wolfgang Welz Date: Mon, 12 Aug 2024 19:38:51 -0700 Subject: [PATCH 04/44] update the README --- examples/message-passing/README.md | 13 ++++++++ examples/message-passing/apps/README.md | 38 ---------------------- examples/message-passing/methods/README.md | 31 ------------------ 3 files changed, 13 insertions(+), 69 deletions(-) create mode 100644 examples/message-passing/README.md delete mode 100644 examples/message-passing/apps/README.md delete mode 100644 examples/message-passing/methods/README.md diff --git a/examples/message-passing/README.md b/examples/message-passing/README.md new file mode 100644 index 00000000..abb322db --- /dev/null +++ b/examples/message-passing/README.md @@ -0,0 +1,13 @@ +# 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 examples showcases an alternative using the Steel library to do secure and efficient OP-compatible message passing. + +## Key Steps +1. **Send Message from L1:**
+Call `L1CrossDomainMessenger.sendMessage` with the message you want to relay to L2. +2. **Generate Steel Proof:**
Generate a Steel proof verifying the message's inclusion in the L1 state. +3. **Relay Message on L2:**
On L2, use `L2CrossDomainMessenger.relayMessage` with the message and Steel proof. Upon successful verification, the message will be relayed to the target contract, ensuring its validity as an L1 message. + +## 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. diff --git a/examples/message-passing/apps/README.md b/examples/message-passing/apps/README.md deleted file mode 100644 index 08f8c22c..00000000 --- a/examples/message-passing/apps/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# Apps - -## Publisher - -The [`publisher` CLI][publisher], is an example application that sends an off-chain proof request to the RISC Zero zkVM, and publishes the received proofs to your deployed [Counter] contract. - -### Usage - -Run the `publisher` with: - -```sh -cargo run --bin publisher -``` - -```text -$ cargo run --bin publisher -- --help - -Usage: publisher --eth-wallet-private-key --rpc-url --contract --token --account - -Options: - --eth-wallet-private-key - Ethereum Node endpoint [env: ETH_WALLET_PRIVATE_KEY=] - --rpc-url - Ethereum Node endpoint [env: RPC_URL=] - --contract - Counter's contract address on Ethereum - --token - ERC20 contract address on Ethereum - --account - Account address to read the balance_of on Ethereum - -h, --help - Print help - -V, --version - Print version -``` - -[publisher]: ./src/bin/publisher.rs -[Counter]: ../contracts/Counter.sol diff --git a/examples/message-passing/methods/README.md b/examples/message-passing/methods/README.md deleted file mode 100644 index 98e32535..00000000 --- a/examples/message-passing/methods/README.md +++ /dev/null @@ -1,31 +0,0 @@ -# zkVM Methods - -This directory contains the [zkVM] portion of your [RISC Zero] application. -This is where you will define one or more [guest programs] to act as a coprocessor to your [on-chain logic]. - -> In typical use cases, the only code in this directory that you will need to edit is inside [guest/src/bin]. - - -### Writing Guest Code - -To learn to write code for the zkVM, we recommend [Guest Code 101]. - -Examples of what you can do in the guest can be found in the [RISC Zero examples]. - - -### From Guest Code to Binary File - -Code in the `methods/guest` directory will be compiled into one or more binaries. - -Build configuration for the methods is included in `methods/build.rs`. - -Each will have a corresponding image ID, which is a hash identifying the program. - - -[zkVM]: https://dev.risczero.com/zkvm -[RISC Zero]: https://www.risczero.com/ -[guest programs]: https://dev.risczero.com/terminology#guest-program -[on-chain logic]: ../contracts/ -[guest/src/bin]: ./guest/src/bin/ -[Guest Code 101]: https://dev.risczero.com/zkvm/developer-guide/guest-code-101 -[RISC Zero examples]: https://github.com/risc0/tree/v0.18.0/examples \ No newline at end of file From ece43d5bb2440fcfac2335bcf944db1f21dec1c2 Mon Sep 17 00:00:00 2001 From: Wolfgang Welz Date: Mon, 12 Aug 2024 20:26:29 -0700 Subject: [PATCH 05/44] add .env --- examples/message-passing/.env | 14 ++++++++ examples/message-passing/Cargo.toml | 1 + examples/message-passing/README.md | 2 ++ examples/message-passing/apps/Cargo.toml | 1 + .../message-passing/apps/src/bin/publisher.rs | 22 +++++++----- examples/message-passing/foundry.toml | 1 + examples/message-passing/scripts/Deploy.s.sol | 35 ++++++++++--------- 7 files changed, 52 insertions(+), 24 deletions(-) create mode 100644 examples/message-passing/.env diff --git a/examples/message-passing/.env b/examples/message-passing/.env new file mode 100644 index 00000000..585d6b1b --- /dev/null +++ b/examples/message-passing/.env @@ -0,0 +1,14 @@ +L1_ADMIN_PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" +L2_ADMIN_PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" + +L1_RPC_URL="http://localhost:8545" +L2_RPC_URL="http://localhost:9545" + +### Publisher Config + +L1_WALLET_PRIVATE_KEY="0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6" +L2_WALLET_PRIVATE_KEY="0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6" + +COUNTER_ADDRESS="" +L1_CROSS_DOMAIN_MESSENGER_ADDRESS="" +L2_CROSS_DOMAIN_MESSENGER_ADDRESS="" diff --git a/examples/message-passing/Cargo.toml b/examples/message-passing/Cargo.toml index 3ce8c1b9..6cd11673 100644 --- a/examples/message-passing/Cargo.toml +++ b/examples/message-passing/Cargo.toml @@ -25,6 +25,7 @@ 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" } diff --git a/examples/message-passing/README.md b/examples/message-passing/README.md index abb322db..c3428101 100644 --- a/examples/message-passing/README.md +++ b/examples/message-passing/README.md @@ -11,3 +11,5 @@ Call `L1CrossDomainMessenger.sendMessage` with the message you want to relay to ## 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. + +## diff --git a/examples/message-passing/apps/Cargo.toml b/examples/message-passing/apps/Cargo.toml index eb7a3cee..fa4e2efd 100644 --- a/examples/message-passing/apps/Cargo.toml +++ b/examples/message-passing/apps/Cargo.toml @@ -16,3 +16,4 @@ risc0-zkvm = { workspace = true, features = ["client"] } tokio = { workspace = true } tracing-subscriber = { workspace = true } url = { workspace = true } +dotenvy = { workspace = true } \ No newline at end of file diff --git a/examples/message-passing/apps/src/bin/publisher.rs b/examples/message-passing/apps/src/bin/publisher.rs index 639ddd38..c3187637 100644 --- a/examples/message-passing/apps/src/bin/publisher.rs +++ b/examples/message-passing/apps/src/bin/publisher.rs @@ -45,9 +45,13 @@ sol!("../contracts/src/ICounter.sol"); #[derive(Parser, Debug)] #[clap(author, version, about, long_about = None)] struct Args { - /// Ethereum Node endpoint. + /// L1 private key. + #[clap(long, env)] + l1_wallet_private_key: PrivateKeySigner, + + /// L2 private key. #[clap(long, env)] - eth_wallet_private_key: PrivateKeySigner, + l2_wallet_private_key: PrivateKeySigner, /// Ethereum Node endpoint. #[clap(long, env)] @@ -59,7 +63,7 @@ struct Args { /// Target's contract address on L2 #[clap(long, env)] - target_address: Address, + counter_address: Address, /// l1_cross_domain_messenger_address's contract address on L1 #[clap(long, env)] @@ -77,18 +81,20 @@ async fn main() -> Result<()> { .with_env_filter(EnvFilter::from_default_env()) .init(); // Parse the command line arguments. - let args = Args::parse(); + dotenvy::dotenv()?; + let args = Args::try_parse()?; // Create an alloy provider for that private key and URL. - let wallet = EthereumWallet::from(args.eth_wallet_private_key); + let wallet = EthereumWallet::from(args.l1_wallet_private_key); let l1_provider = ProviderBuilder::new() .with_recommended_fillers() - .wallet(wallet.clone()) + .wallet(wallet) .on_http(args.l1_rpc_url); + let wallet = EthereumWallet::from(args.l2_wallet_private_key); let l2_provider = ProviderBuilder::new() .with_recommended_fillers() - .wallet(wallet.clone()) + .wallet(wallet) .on_http(args.l2_rpc_url); // Instantiate all the contracts we want to call. @@ -104,7 +110,7 @@ async fn main() -> Result<()> { IBookmarkService::new(args.l2_cross_domain_messenger_address, l2_provider.clone()); // Prepare the message to be passed from L1 to L2 - let target = args.target_address; + let target = args.counter_address; let data = ICounter::incrementCall {}.abi_encode(); // Send a transaction calling IL1CrossDomainMessenger.sendMessage diff --git a/examples/message-passing/foundry.toml b/examples/message-passing/foundry.toml index 0d3ed72f..a553c56a 100644 --- a/examples/message-passing/foundry.toml +++ b/examples/message-passing/foundry.toml @@ -3,6 +3,7 @@ src = "contracts" out = "out" libs = ["../../lib", "../../contracts/src"] test = "tests" +script = "scripts" ffi = true # See more config options https://github.com/foundry-rs/foundry/tree/master/config diff --git a/examples/message-passing/scripts/Deploy.s.sol b/examples/message-passing/scripts/Deploy.s.sol index f018286d..2acd6754 100644 --- a/examples/message-passing/scripts/Deploy.s.sol +++ b/examples/message-passing/scripts/Deploy.s.sol @@ -26,38 +26,41 @@ import {L1CrossDomainMessenger} from "../contracts/src/L1CrossDomainMessenger.so import {IL2CrossDomainMessenger} from "../contracts/src/IL2CrossDomainMessenger.sol"; import {L2CrossDomainMessenger} from "../contracts/src/L2CrossDomainMessenger.sol"; import {IL1Block} from "../contracts/src/IL1Block.sol"; -import {L1BlockMock} from "../contracts/test/L1BlockMock.sol"; import {ImageID} from "../contracts/src/ImageID.sol"; import {Counter} from "../contracts/src/Counter.sol"; contract Deploy is Script, RiscZeroCheats { - function run() external { - // Read and log the chainID - uint256 chainId = block.chainid; - console2.log("You are deploying on ChainID %d", chainId); - - deployAnvil(); - } + // In OP the L1Block contract on L2 is always at the same predeployed address + IL1Block constant L1_BLOCK = IL1Block(0x4200000000000000000000000000000000000015); - function deployAnvil() internal { + function run() external { // load ENV variables first - uint256 key = vm.envUint("L1_ADMIN_PRIVATE_KEY"); - - vm.startBroadcast(key); + 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")); + + vm.selectFork(l1); + vm.startBroadcast(key1); IL1CrossDomainMessenger l1CrossDomainMessenger = new L1CrossDomainMessenger(); console2.log("Deployed L1 IL1CrossDomainMessenger to", address(l1CrossDomainMessenger)); + vm.stopBroadcast(); + + vm.selectFork(l2); + vm.startBroadcast(key2); + IRiscZeroVerifier verifier = deployRiscZeroVerifier(); - IL1Block l1Block = new L1BlockMock(); - - IL2CrossDomainMessenger l2CrossDomainMessenger = new L2CrossDomainMessenger(verifier, ImageID.CROSS_DOMAIN_MESSENGER_ID, address(l1CrossDomainMessenger), l1Block); + IL2CrossDomainMessenger l2CrossDomainMessenger = new L2CrossDomainMessenger( + verifier, ImageID.CROSS_DOMAIN_MESSENGER_ID, address(l1CrossDomainMessenger), L1_BLOCK + ); console2.log("Deployed L2 L2CrossDomainMessenger to", address(l2CrossDomainMessenger)); Counter counter = new Counter(l2CrossDomainMessenger, address(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266)); console2.log("Deployed L1 Counter to", address(counter)); vm.stopBroadcast(); - } + } } From 3e20bd199b89a332124f201407901cc982f0fed2 Mon Sep 17 00:00:00 2001 From: capossele Date: Mon, 12 Aug 2024 21:02:50 -0700 Subject: [PATCH 06/44] simplify counter --- examples/message-passing/.env | 16 +++++----------- examples/message-passing/README.md | 14 +++++++++++++- .../message-passing/contracts/src/Counter.sol | 6 ++---- .../message-passing/contracts/test/E2E.t.sol | 2 +- examples/message-passing/scripts/Deploy.s.sol | 2 +- 5 files changed, 22 insertions(+), 18 deletions(-) diff --git a/examples/message-passing/.env b/examples/message-passing/.env index 585d6b1b..499aad52 100644 --- a/examples/message-passing/.env +++ b/examples/message-passing/.env @@ -1,14 +1,8 @@ -L1_ADMIN_PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" -L2_ADMIN_PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" - -L1_RPC_URL="http://localhost:8545" -L2_RPC_URL="http://localhost:9545" +L1_RPC_URL="https://ethereum-sepolia-rpc.publicnode.com" +L2_RPC_URL="https://optimism-sepolia-rpc.publicnode.com" ### Publisher Config -L1_WALLET_PRIVATE_KEY="0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6" -L2_WALLET_PRIVATE_KEY="0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6" - -COUNTER_ADDRESS="" -L1_CROSS_DOMAIN_MESSENGER_ADDRESS="" -L2_CROSS_DOMAIN_MESSENGER_ADDRESS="" +COUNTER_ADDRESS="0xb0916431C28203c4426955CE72E93440211FA4E2" +L1_CROSS_DOMAIN_MESSENGER_ADDRESS="0x794D2335A98693d05f7cbc18601f997702C77FA1" +L2_CROSS_DOMAIN_MESSENGER_ADDRESS="0x11052f9b1484ED5d24e37d5B27Ac9ef56eE0449d" \ No newline at end of file diff --git a/examples/message-passing/README.md b/examples/message-passing/README.md index c3428101..f592e0d6 100644 --- a/examples/message-passing/README.md +++ b/examples/message-passing/README.md @@ -12,4 +12,16 @@ Call `L1CrossDomainMessenger.sendMessage` with the message you want to relay to ## 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 +We deployed this example on Sepolia and OP-Sepolia. You can export: + +```bash +L1_WALLET_PRIVATE_KEY="YOUR_SEPOLIA_WALLET_PRIVATE_KEY" +L2_WALLET_PRIVATE_KEY="YOUR_OP_SEPOLIA_WALLET_PRIVATE_KEY" +``` + +and finally run + +```bash +RISC0_DEV_MODE=1 RUST_LOG=info cargo run +``` \ No newline at end of file diff --git a/examples/message-passing/contracts/src/Counter.sol b/examples/message-passing/contracts/src/Counter.sol index 045e2309..5c1b7bc8 100644 --- a/examples/message-passing/contracts/src/Counter.sol +++ b/examples/message-passing/contracts/src/Counter.sol @@ -21,13 +21,11 @@ import "./ICounter.sol"; contract Counter is ICounter { IL2CrossDomainMessenger private immutable L2_CROSS_DOMAIN_MESSENGER; - address private immutable L1_SENDER; uint256 private count; - constructor(IL2CrossDomainMessenger l2CrossDomainMessenger, address l1Sender) { + constructor(IL2CrossDomainMessenger l2CrossDomainMessenger) { L2_CROSS_DOMAIN_MESSENGER = l2CrossDomainMessenger; - L1_SENDER = l1Sender; count = 0; } @@ -36,7 +34,7 @@ contract Counter is ICounter { msg.sender == address(L2_CROSS_DOMAIN_MESSENGER), "Counter: Only L2CrossDomainMessenger can increment the counter" ); - require(L2_CROSS_DOMAIN_MESSENGER.xDomainMessageSender() == L1_SENDER, "Counter: Invalid L1 sender"); + // require(L2_CROSS_DOMAIN_MESSENGER.xDomainMessageSender() == L1_SENDER, "Counter: Invalid L1 sender"); count++; } diff --git a/examples/message-passing/contracts/test/E2E.t.sol b/examples/message-passing/contracts/test/E2E.t.sol index 32cb837a..906951b5 100644 --- a/examples/message-passing/contracts/test/E2E.t.sol +++ b/examples/message-passing/contracts/test/E2E.t.sol @@ -54,7 +54,7 @@ contract E2ETest is Test { l2CrossDomainMessenger = new L2CrossDomainMessenger( verifier, CROSS_DOMAIN_MESSENGER_IMAGE_ID, address(l1CrossDomainMessenger), l1Block ); - counter = new Counter(l2CrossDomainMessenger, address(sender)); + counter = new Counter(l2CrossDomainMessenger); } function testCounterIncrement() public { diff --git a/examples/message-passing/scripts/Deploy.s.sol b/examples/message-passing/scripts/Deploy.s.sol index 2acd6754..65d10303 100644 --- a/examples/message-passing/scripts/Deploy.s.sol +++ b/examples/message-passing/scripts/Deploy.s.sol @@ -58,7 +58,7 @@ contract Deploy is Script, RiscZeroCheats { ); console2.log("Deployed L2 L2CrossDomainMessenger to", address(l2CrossDomainMessenger)); - Counter counter = new Counter(l2CrossDomainMessenger, address(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266)); + Counter counter = new Counter(l2CrossDomainMessenger); console2.log("Deployed L1 Counter to", address(counter)); vm.stopBroadcast(); From 329ddd48bbfe20379f81f0269a45dd1692b83a24 Mon Sep 17 00:00:00 2001 From: capossele Date: Fri, 23 Aug 2024 15:13:23 +0100 Subject: [PATCH 07/44] fix Commitment --- examples/message-passing/core/src/lib.rs | 6 +++--- examples/message-passing/rust-toolchain.toml | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/examples/message-passing/core/src/lib.rs b/examples/message-passing/core/src/lib.rs index f016b3b9..f3f5677c 100644 --- a/examples/message-passing/core/src/lib.rs +++ b/examples/message-passing/core/src/lib.rs @@ -14,7 +14,7 @@ use alloy_primitives::{Address, B256}; use alloy_sol_types::{sol, SolStruct}; -use risc0_steel::SolCommitment; +use risc0_steel::Commitment; use serde::{Deserialize, Serialize}; #[cfg(not(target_os = "zkvm"))] @@ -39,7 +39,7 @@ sol! { /// Journal returned by the guest. struct Journal { - SolCommitment commitment; + Commitment commitment; address l1CrossDomainMessenger; Message message; bytes32 messageDigest; @@ -66,7 +66,7 @@ impl CrossDomainMessengerInput { let digest = self.message.digest(); Journal { - commitment: SolCommitment::default(), + commitment: Commitment::default(), l1CrossDomainMessenger: self.l1_cross_domain_messenger, message: self.message, messageDigest: digest, diff --git a/examples/message-passing/rust-toolchain.toml b/examples/message-passing/rust-toolchain.toml index 6c18dfcd..cb78e29e 100644 --- a/examples/message-passing/rust-toolchain.toml +++ b/examples/message-passing/rust-toolchain.toml @@ -1,4 +1,5 @@ [toolchain] -channel = "stable" -components = ["rustfmt", "rust-src"] +channel = "1.79" +components = ["clippy", "rustfmt", "rust-src"] +targets = [] profile = "minimal" \ No newline at end of file From 151be94b94cc4e872034af7ea0526c3903d64136 Mon Sep 17 00:00:00 2001 From: capossele Date: Fri, 23 Aug 2024 16:09:11 +0100 Subject: [PATCH 08/44] update READMEs --- examples/README.md | 7 +++++++ examples/message-passing/README.md | 2 +- steel/README.md | 23 +++++++++++++++-------- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/examples/README.md b/examples/README.md index 18c0e779..9988327a 100644 --- a/examples/README.md +++ b/examples/README.md @@ -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 diff --git a/examples/message-passing/README.md b/examples/message-passing/README.md index f592e0d6..9f41bba8 100644 --- a/examples/message-passing/README.md +++ b/examples/message-passing/README.md @@ -1,7 +1,7 @@ # 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 examples showcases an alternative using the Steel library to do secure and efficient OP-compatible message passing. +This example showcases an alternative using the Steel library to do secure and efficient OP-compatible message passing. ## Key Steps 1. **Send Message from L1:**
diff --git a/steel/README.md b/steel/README.md index fcd5bfea..ce696eec 100644 --- a/steel/README.md +++ b/steel/README.md @@ -10,7 +10,7 @@ In the realm of Ethereum and smart contracts, obtaining data directly from the b Traditionally, these operations, especially when it comes to proving and verifying off-chain computations, involve a degree of complexity: either via proof of storage mechanisms requiring detailed knowledge of slot indexes, or via query-specific circuit development. In contrast, this library abstracts away these complexities, allowing developers to query Ethereum's state by just defining the Solidity method they wish to call. -To demonstrate a simple instance of using the view call library, let's consider possibly the most common view call: querying the balance of an ERC-20 token for a specific address. +To demonstrate a simple instance of using this library, let's consider possibly the most common view call: querying the balance of an ERC-20 token for a specific address. You can find the full example [here](../examples/erc20/README.md). ## Guest code @@ -114,19 +114,26 @@ let journal = Journal { env::commit_slice(&journal.abi_encode()); ``` -We also provide an example, [erc20-counter], showcasing such integration. +We provide several examples showcasing such integration, including [erc20-counter], [token-stats], and [message-passing]. ### Block hash validation -Since internally the `blockhash` opcode is used for validation, the commitment must not be older than 256 blocks. -Given a block time of 12 seconds, this allows just over 50 minutes to create the proof and ensure that the validating transaction is included in a block. -In many cases, this will work just fine: even very large computations such as proving an entire Ethereum block can be done in well under 50 minutes with sufficient resources. +Validating the block hash committed by a Steel proof is essential to ensure that the proof accurately reflects the correct blockchain state. +Steel supports three methods for block hash validation, each suited to different use cases. -For scenarios needing a verified block hash older than 256 blocks: +#### 1. Blockhash Opcode Commitment +This method uses the `blockhash` opcode to commit to a block hash that is no more than 256 blocks old. With Ethereum's 12-second block time, this provides a window of approximately 50 minutes to generate the proof and ensure the validating transaction is included in a block. This approach is ideal for most scenarios, including complex computations, as it typically allows sufficient time for proof generation. -* Save the required block hash to the contract state if known in advance (e.g., when initiating a governance proposal). -* Use RISC Zero to prove the hash chain from the queried block up to a block within the most recent 256. +#### 2. Beacon Block Root Commitment +The second method enables validation using the [EIP-4788] beacon roots contract. This technique extends the time available for proof generation to 24 hours, making it suitable for scenarios that require more extensive computation. It requires access to a beacon chain RPC node and can be activated via calling `EvmEnv::into_beacon_input`. +However, this approach is specific to Ethereum Steel proofs and depends on the implementation of [EIP-4788]. Note that EIP-4788 only provides access to the parent's beacon root, necessitating iterative queries in Solidity to retrieve the target beacon root for validation. This iterative process can lead to slightly higher gas costs when compared to using the `blockhash` opcode. Overall it is suited for environments where longer proof generation times are required. + +#### 3. Block Hash Bookmarking +The Bookmarking technique involves 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. This method is demonstrated in the [message-passing] example and offers flexibility by decoupling the proof generation from immediate block constraints. [erc20-counter]: ../examples/erc20-counter/README.md +[token-stats]: ../examples/token-stats/README.md +[message-passing]: ../examples/message-passing/README.md [Bonsai Foundry Template]: https://github.com/risc0/bonsai-foundry-template [Steel library]: ../contracts/src/steel/Steel.sol +[EIP-4788]: https://eips.ethereum.org/EIPS/eip-4788 From 3cef861bb16de37ee901f886eac6d8b7c831b766 Mon Sep 17 00:00:00 2001 From: capossele Date: Fri, 23 Aug 2024 16:22:39 +0100 Subject: [PATCH 09/44] fix test --- .../contracts/src/L2CrossDomainMessenger.sol | 2 +- steel/README.md | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/examples/message-passing/contracts/src/L2CrossDomainMessenger.sol b/examples/message-passing/contracts/src/L2CrossDomainMessenger.sol index 37c1b115..0836f3a1 100644 --- a/examples/message-passing/contracts/src/L2CrossDomainMessenger.sol +++ b/examples/message-passing/contracts/src/L2CrossDomainMessenger.sol @@ -78,7 +78,7 @@ contract L2CrossDomainMessenger is IL2CrossDomainMessenger, Bookmark { } function validateCommitment(Steel.Commitment memory commitment) internal view returns (bool) { - return commitment.blockHash == Bookmark.blocks[commitment.blockNumber.toUint64()]; + return commitment.blockDigest == Bookmark.blocks[commitment.blockID.toUint64()]; } function relayVerifiedMessage(Message memory message, bytes32 digest) internal { diff --git a/steel/README.md b/steel/README.md index ce696eec..6105493e 100644 --- a/steel/README.md +++ b/steel/README.md @@ -116,20 +116,19 @@ env::commit_slice(&journal.abi_encode()); We provide several examples showcasing such integration, including [erc20-counter], [token-stats], and [message-passing]. -### Block hash validation +### Block commitment validation -Validating the block hash committed by a Steel proof is essential to ensure that the proof accurately reflects the correct blockchain state. -Steel supports three methods for block hash validation, each suited to different use cases. +Validating the block committed by a Steel proof is essential to ensure that the proof accurately reflects the correct blockchain state. +Steel supports two methods for block validation (see the `validateCommitment` function in [Steel.sol](../contracts/src/steel/Steel.sol)). -#### 1. Blockhash Opcode Commitment +#### 1. Block hash Commitment This method uses the `blockhash` opcode to commit to a block hash that is no more than 256 blocks old. With Ethereum's 12-second block time, this provides a window of approximately 50 minutes to generate the proof and ensure the validating transaction is included in a block. This approach is ideal for most scenarios, including complex computations, as it typically allows sufficient time for proof generation. #### 2. Beacon Block Root Commitment The second method enables validation using the [EIP-4788] beacon roots contract. This technique extends the time available for proof generation to 24 hours, making it suitable for scenarios that require more extensive computation. It requires access to a beacon chain RPC node and can be activated via calling `EvmEnv::into_beacon_input`. However, this approach is specific to Ethereum Steel proofs and depends on the implementation of [EIP-4788]. Note that EIP-4788 only provides access to the parent's beacon root, necessitating iterative queries in Solidity to retrieve the target beacon root for validation. This iterative process can lead to slightly higher gas costs when compared to using the `blockhash` opcode. Overall it is suited for environments where longer proof generation times are required. -#### 3. Block Hash Bookmarking -The Bookmarking technique involves 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. This method is demonstrated in the [message-passing] example and offers flexibility by decoupling the proof generation from immediate block constraints. +A *bookmarking block commitment validation* technique can also be constructed on top of these. The idea is to save the target block hash to the contract state before generating a Steel proof that targets that specific block. Once the block hash (or beacon root) is bookmarked, it can be used later for validation, ensuring that the proof corresponds to the correct blockchain state. This method is demonstrated in the [message-passing] example and offers flexibility by decoupling the proof generation from immediate block constraints. [erc20-counter]: ../examples/erc20-counter/README.md [token-stats]: ../examples/token-stats/README.md From f8c919c1a3d04ade75843926247c736c3788c460 Mon Sep 17 00:00:00 2001 From: capossele Date: Fri, 23 Aug 2024 16:33:37 +0100 Subject: [PATCH 10/44] improve example README --- examples/message-passing/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/message-passing/README.md b/examples/message-passing/README.md index 9f41bba8..617dd992 100644 --- a/examples/message-passing/README.md +++ b/examples/message-passing/README.md @@ -2,6 +2,9 @@ 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:**
From f9a1ada96cb7641f2b5d04b6e7a951498d49406a Mon Sep 17 00:00:00 2001 From: capossele Date: Fri, 23 Aug 2024 16:35:51 +0100 Subject: [PATCH 11/44] add message-passing to CI --- .github/workflows/main.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5aa0b2e2..fcb39745 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -162,6 +162,12 @@ jobs: - name: build token-stats run: cargo build working-directory: examples/token-stats + - name: build message-passing + run: cargo build + working-directory: examples/message-passing + - name: forge test message-passing + run: forge test + working-directory: examples/message-passing - name: test erc20-Counter run: ./test-local-deployment.sh working-directory: examples/erc20-counter From 72859c8a1a66cfd507d6657aede32a4aefb38da8 Mon Sep 17 00:00:00 2001 From: capossele Date: Fri, 23 Aug 2024 16:47:41 +0100 Subject: [PATCH 12/44] fix comments --- examples/message-passing/apps/src/bin/publisher.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/message-passing/apps/src/bin/publisher.rs b/examples/message-passing/apps/src/bin/publisher.rs index c3187637..0626145a 100644 --- a/examples/message-passing/apps/src/bin/publisher.rs +++ b/examples/message-passing/apps/src/bin/publisher.rs @@ -53,11 +53,11 @@ struct Args { #[clap(long, env)] l2_wallet_private_key: PrivateKeySigner, - /// Ethereum Node endpoint. + /// L1 RPC node endpoint. #[clap(long, env)] l1_rpc_url: Url, - /// Ethereum Node endpoint. + /// L2 RPC node endpoint. #[clap(long, env)] l2_rpc_url: Url, From cd147fe2eff6549ac07ab05e0a131f3645559c53 Mon Sep 17 00:00:00 2001 From: Wolfgang Welz Date: Sat, 24 Aug 2024 16:03:22 +0200 Subject: [PATCH 13/44] improve README --- steel/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/steel/README.md b/steel/README.md index 6105493e..570c9137 100644 --- a/steel/README.md +++ b/steel/README.md @@ -125,8 +125,8 @@ Steel supports two methods for block validation (see the `validateCommitment` fu This method uses the `blockhash` opcode to commit to a block hash that is no more than 256 blocks old. With Ethereum's 12-second block time, this provides a window of approximately 50 minutes to generate the proof and ensure the validating transaction is included in a block. This approach is ideal for most scenarios, including complex computations, as it typically allows sufficient time for proof generation. #### 2. Beacon Block Root Commitment -The second method enables validation using the [EIP-4788] beacon roots contract. This technique extends the time available for proof generation to 24 hours, making it suitable for scenarios that require more extensive computation. It requires access to a beacon chain RPC node and can be activated via calling `EvmEnv::into_beacon_input`. -However, this approach is specific to Ethereum Steel proofs and depends on the implementation of [EIP-4788]. Note that EIP-4788 only provides access to the parent's beacon root, necessitating iterative queries in Solidity to retrieve the target beacon root for validation. This iterative process can lead to slightly higher gas costs when compared to using the `blockhash` opcode. Overall it is suited for environments where longer proof generation times are required. +The second method allows validation using the [EIP-4788] beacon roots contract. This technique extends the time window in which the proof can be validated on-chain to just over a day, making it suitable for scenarios requiring more extensive computation. It requires access to a beacon API endpoint and can be enabled by calling `EvmEnv::into_beacon_input`. +However, this approach is specific to Ethereum Steel proofs and depends on the implementation of EIP-4788. Note that EIP-4788 only provides access to the parent beacon root, requiring iterative queries in Solidity to retrieve the target beacon root for validation. This iterative process can result in slightly higher gas costs compared to using the `blockhash` opcode. Overall, it is suitable for environments where longer proof generation times are required. A *bookmarking block commitment validation* technique can also be constructed on top of these. The idea is to save the target block hash to the contract state before generating a Steel proof that targets that specific block. Once the block hash (or beacon root) is bookmarked, it can be used later for validation, ensuring that the proof corresponds to the correct blockchain state. This method is demonstrated in the [message-passing] example and offers flexibility by decoupling the proof generation from immediate block constraints. From e7604bef0fd29d39c7098d80165ec8bb3538ce88 Mon Sep 17 00:00:00 2001 From: capossele Date: Mon, 26 Aug 2024 11:30:13 +0100 Subject: [PATCH 14/44] address review comments --- examples/message-passing/README.md | 5 +++-- .../{scripts => contracts/script}/Deploy.s.sol | 2 +- examples/message-passing/contracts/src/Counter.sol | 9 +++++++-- examples/message-passing/contracts/test/E2E.t.sol | 2 +- 4 files changed, 12 insertions(+), 6 deletions(-) rename examples/message-passing/{scripts => contracts/script}/Deploy.s.sol (97%) diff --git a/examples/message-passing/README.md b/examples/message-passing/README.md index 617dd992..23bc9cbc 100644 --- a/examples/message-passing/README.md +++ b/examples/message-passing/README.md @@ -9,8 +9,9 @@ It also showcases a *bookmarking block commitment validation* technique, by savi ## Key Steps 1. **Send Message from L1:**
Call `L1CrossDomainMessenger.sendMessage` with the message you want to relay to L2. -2. **Generate Steel Proof:**
Generate a Steel proof verifying the message's inclusion in the L1 state. -3. **Relay Message on L2:**
On L2, use `L2CrossDomainMessenger.relayMessage` with the message and Steel proof. Upon successful verification, the message will be relayed to the target contract, ensuring its validity as an L1 message. +2. **Bookmark the block hash**
On L2, save the L1 block hash to the contract state before generating the Steel proof. +3. **Generate Steel Proof:**
Generate a Steel proof verifying the message's inclusion in the L1 state, targeting the bookmarked block. +4. **Relay Message on L2:**
On L2, use `L2CrossDomainMessenger.relayMessage` with the message and Steel proof. Upon successful verification, the message will be relayed to the target contract, ensuring its validity as an L1 message. ## 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. diff --git a/examples/message-passing/scripts/Deploy.s.sol b/examples/message-passing/contracts/script/Deploy.s.sol similarity index 97% rename from examples/message-passing/scripts/Deploy.s.sol rename to examples/message-passing/contracts/script/Deploy.s.sol index 65d10303..e1ddef65 100644 --- a/examples/message-passing/scripts/Deploy.s.sol +++ b/examples/message-passing/contracts/script/Deploy.s.sol @@ -58,7 +58,7 @@ contract Deploy is Script, RiscZeroCheats { ); console2.log("Deployed L2 L2CrossDomainMessenger to", address(l2CrossDomainMessenger)); - Counter counter = new Counter(l2CrossDomainMessenger); + Counter counter = new Counter(l2CrossDomainMessenger, address(0x0)); console2.log("Deployed L1 Counter to", address(counter)); vm.stopBroadcast(); diff --git a/examples/message-passing/contracts/src/Counter.sol b/examples/message-passing/contracts/src/Counter.sol index 5c1b7bc8..1b1585b8 100644 --- a/examples/message-passing/contracts/src/Counter.sol +++ b/examples/message-passing/contracts/src/Counter.sol @@ -21,11 +21,13 @@ import "./ICounter.sol"; contract Counter is ICounter { IL2CrossDomainMessenger private immutable L2_CROSS_DOMAIN_MESSENGER; + address private immutable L1_SENDER; uint256 private count; - constructor(IL2CrossDomainMessenger l2CrossDomainMessenger) { + constructor(IL2CrossDomainMessenger l2CrossDomainMessenger, address l1_sender) { L2_CROSS_DOMAIN_MESSENGER = l2CrossDomainMessenger; + L1_SENDER = l1_sender; count = 0; } @@ -34,7 +36,10 @@ contract Counter is ICounter { msg.sender == address(L2_CROSS_DOMAIN_MESSENGER), "Counter: Only L2CrossDomainMessenger can increment the counter" ); - // require(L2_CROSS_DOMAIN_MESSENGER.xDomainMessageSender() == L1_SENDER, "Counter: Invalid L1 sender"); + // Skip the check if L1_SENDER is not set + if (L1_SENDER != address(0)) { + require(L2_CROSS_DOMAIN_MESSENGER.xDomainMessageSender() == L1_SENDER, "Counter: Invalid L1 sender"); + } count++; } diff --git a/examples/message-passing/contracts/test/E2E.t.sol b/examples/message-passing/contracts/test/E2E.t.sol index 906951b5..fd33d3af 100644 --- a/examples/message-passing/contracts/test/E2E.t.sol +++ b/examples/message-passing/contracts/test/E2E.t.sol @@ -54,7 +54,7 @@ contract E2ETest is Test { l2CrossDomainMessenger = new L2CrossDomainMessenger( verifier, CROSS_DOMAIN_MESSENGER_IMAGE_ID, address(l1CrossDomainMessenger), l1Block ); - counter = new Counter(l2CrossDomainMessenger); + counter = new Counter(l2CrossDomainMessenger, address(0x0)); } function testCounterIncrement() public { From 5c8af0dedb9c2eddd5ee27cc765d32642f602f1e Mon Sep 17 00:00:00 2001 From: capossele Date: Mon, 26 Aug 2024 11:52:42 +0100 Subject: [PATCH 15/44] fix deploy script --- .../message-passing/contracts/script/Deploy.s.sol | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/message-passing/contracts/script/Deploy.s.sol b/examples/message-passing/contracts/script/Deploy.s.sol index e1ddef65..164b5ae2 100644 --- a/examples/message-passing/contracts/script/Deploy.s.sol +++ b/examples/message-passing/contracts/script/Deploy.s.sol @@ -21,13 +21,13 @@ import {RiscZeroCheats} from "risc0/test/RiscZeroCheats.sol"; import {IRiscZeroVerifier} from "risc0/IRiscZeroVerifier.sol"; import {ControlID, RiscZeroGroth16Verifier} from "risc0/groth16/RiscZeroGroth16Verifier.sol"; import {RiscZeroMockVerifier} from "risc0/test/RiscZeroMockVerifier.sol"; -import {IL1CrossDomainMessenger} from "../contracts/src/IL1CrossDomainMessenger.sol"; -import {L1CrossDomainMessenger} from "../contracts/src/L1CrossDomainMessenger.sol"; -import {IL2CrossDomainMessenger} from "../contracts/src/IL2CrossDomainMessenger.sol"; -import {L2CrossDomainMessenger} from "../contracts/src/L2CrossDomainMessenger.sol"; -import {IL1Block} from "../contracts/src/IL1Block.sol"; -import {ImageID} from "../contracts/src/ImageID.sol"; -import {Counter} from "../contracts/src/Counter.sol"; +import {IL1CrossDomainMessenger} from "../src/IL1CrossDomainMessenger.sol"; +import {L1CrossDomainMessenger} from "../src/L1CrossDomainMessenger.sol"; +import {IL2CrossDomainMessenger} from "../src/IL2CrossDomainMessenger.sol"; +import {L2CrossDomainMessenger} from "../src/L2CrossDomainMessenger.sol"; +import {IL1Block} from "../src/IL1Block.sol"; +import {ImageID} from "../src/ImageID.sol"; +import {Counter} from "../src/Counter.sol"; contract Deploy is Script, RiscZeroCheats { // In OP the L1Block contract on L2 is always at the same predeployed address From 93bfb90de87b80e41510b7c879c8f2b087fd7647 Mon Sep 17 00:00:00 2001 From: Wolfgang Welz Date: Fri, 6 Sep 2024 14:37:35 +0200 Subject: [PATCH 16/44] us OP devnet --- .github/workflows/main.yml | 42 +++++++++++++++++++ .gitmodules | 3 ++ examples/message-passing/.env | 21 +++++++--- examples/message-passing/Cargo.toml | 8 ++-- .../contracts/script/Deploy.s.sol | 13 +++--- .../{ => crates}/apps/Cargo.toml | 0 .../{ => crates}/apps/src/bin/publisher.rs | 10 ++--- .../{ => crates}/core/Cargo.toml | 0 .../{ => crates}/core/src/contracts.rs | 6 +-- .../{ => crates}/core/src/lib.rs | 0 .../{ => crates}/methods/Cargo.toml | 0 .../{ => crates}/methods/build.rs | 4 +- .../{ => crates}/methods/guest/Cargo.toml | 5 +-- .../guest/src/bin/cross_domain_messenger.rs | 0 .../{ => crates}/methods/src/lib.rs | 0 examples/message-passing/foundry.toml | 10 ++--- examples/message-passing/optimism | 1 + 17 files changed, 86 insertions(+), 37 deletions(-) rename examples/message-passing/{ => crates}/apps/Cargo.toml (100%) rename examples/message-passing/{ => crates}/apps/src/bin/publisher.rs (97%) rename examples/message-passing/{ => crates}/core/Cargo.toml (100%) rename examples/message-passing/{ => crates}/core/src/contracts.rs (97%) rename examples/message-passing/{ => crates}/core/src/lib.rs (100%) rename examples/message-passing/{ => crates}/methods/Cargo.toml (100%) rename examples/message-passing/{ => crates}/methods/build.rs (92%) rename examples/message-passing/{ => crates}/methods/guest/Cargo.toml (78%) rename examples/message-passing/{ => crates}/methods/guest/src/bin/cross_domain_messenger.rs (100%) rename examples/message-passing/{ => crates}/methods/src/lib.rs (100%) create mode 160000 examples/message-passing/optimism diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fcb39745..64249b11 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -117,6 +117,48 @@ jobs: working-directory: contracts - run: sccache --show-stats + op-message-passing: + runs-on: [ self-hosted, prod, "${{ matrix.os }}", "${{ matrix.device }}" ] + strategy: + # Run only on Linux with GPU. Additional coverage is marginal, and GPU is fastest. + matrix: + include: + - os: Linux + feature: cuda + device: nvidia_rtx_a5000 + steps: + # This is a workaround from: https://github.com/actions/checkout/issues/590#issuecomment-970586842 + - run: "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :" + - uses: actions/checkout@v4 + with: + submodules: recursive + - if: matrix.feature == 'cuda' + uses: risc0/risc0/.github/actions/cuda@main + - uses: actions/setup-go@v5 + - uses: risc0/risc0/.github/actions/rustup@main + - uses: risc0/risc0/.github/actions/sccache@main + with: + key: ${{ matrix.os }}-${{ matrix.feature }} + - uses: risc0/foundry-toolchain@2fe7e70b520f62368a0e3c464f997df07ede420f + - uses: ./.github/actions/cargo-risczero-install + with: + ref: ${{ env.RISC0_MONOREPO_REF }} + toolchain-version: ${{ env.RISC0_TOOLCHAIN_VERSION }} + features: ${{ matrix.feature }} + - name: Pull and tag OP docker images + run: | + IMAGE_BASE_PREFIX="us-docker.pkg.dev/oplabs-tools-artifacts/images" + docker pull "$IMAGE_BASE_PREFIX/op-node:v1.9.1" + docker pull "$IMAGE_BASE_PREFIX/op-proposer:v1.9.1" + docker pull "$IMAGE_BASE_PREFIX/op-batcher:v1.9.1" + # rename to the tags that the docker-compose of the devnet expects + docker tag "$IMAGE_BASE_PREFIX/op-node:v1.9.1" "$IMAGE_BASE_PREFIX/op-node:devnet" + docker tag "$IMAGE_BASE_PREFIX/op-proposer:v1.9.1" "$IMAGE_BASE_PREFIX/op-proposer:devnet" + docker tag "$IMAGE_BASE_PREFIX/op-batcher:v1.9.1" "$IMAGE_BASE_PREFIX/op-batcher:devnet" + - name: Bring up the OP stack + run: DEVNET_NO_BUILD=false DEVNET_L2OO=true DEVNET_ALTDA=false make -C optimism devnet-up + working-directory: examples/message-passing + examples: runs-on: [self-hosted, prod, "${{ matrix.os }}", "${{ matrix.device }}"] strategy: diff --git a/.gitmodules b/.gitmodules index 690924b6..87b80576 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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 diff --git a/examples/message-passing/.env b/examples/message-passing/.env index 499aad52..24b01d64 100644 --- a/examples/message-passing/.env +++ b/examples/message-passing/.env @@ -1,8 +1,17 @@ -L1_RPC_URL="https://ethereum-sepolia-rpc.publicnode.com" -L2_RPC_URL="https://optimism-sepolia-rpc.publicnode.com" +## L1 Ethereum Config -### Publisher Config +L1_RPC_URL="http://localhost:8545/" -COUNTER_ADDRESS="0xb0916431C28203c4426955CE72E93440211FA4E2" -L1_CROSS_DOMAIN_MESSENGER_ADDRESS="0x794D2335A98693d05f7cbc18601f997702C77FA1" -L2_CROSS_DOMAIN_MESSENGER_ADDRESS="0x11052f9b1484ED5d24e37d5B27Ac9ef56eE0449d" \ No newline at end of file +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" diff --git a/examples/message-passing/Cargo.toml b/examples/message-passing/Cargo.toml index 6cd11673..30add462 100644 --- a/examples/message-passing/Cargo.toml +++ b/examples/message-passing/Cargo.toml @@ -1,7 +1,6 @@ [workspace] resolver = "2" -members = ["apps", "core", "methods"] -exclude = ["lib"] +members = ["crates/*"] [workspace.package] version = "0.1.0" @@ -13,6 +12,9 @@ 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 } @@ -29,8 +31,6 @@ dotenvy = { version = "0.15" } ethers = { version = "2.0" } hex = { version = "0.4" } log = { version = "0.4" } -cross-domain-messenger-methods = { path = "./methods" } -cross-domain-messenger-core = { path = "./core" } serde = { version = "1.0", features = ["derive", "std"] } tokio = { version = "1.39", features = ["full"] } tracing = { version = "0.1" } diff --git a/examples/message-passing/contracts/script/Deploy.s.sol b/examples/message-passing/contracts/script/Deploy.s.sol index 164b5ae2..0b504ead 100644 --- a/examples/message-passing/contracts/script/Deploy.s.sol +++ b/examples/message-passing/contracts/script/Deploy.s.sol @@ -19,11 +19,8 @@ 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 {ControlID, RiscZeroGroth16Verifier} from "risc0/groth16/RiscZeroGroth16Verifier.sol"; import {RiscZeroMockVerifier} from "risc0/test/RiscZeroMockVerifier.sol"; -import {IL1CrossDomainMessenger} from "../src/IL1CrossDomainMessenger.sol"; import {L1CrossDomainMessenger} from "../src/L1CrossDomainMessenger.sol"; -import {IL2CrossDomainMessenger} from "../src/IL2CrossDomainMessenger.sol"; import {L2CrossDomainMessenger} from "../src/L2CrossDomainMessenger.sol"; import {IL1Block} from "../src/IL1Block.sol"; import {ImageID} from "../src/ImageID.sol"; @@ -39,11 +36,15 @@ contract Deploy is Script, RiscZeroCheats { 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); - IL1CrossDomainMessenger l1CrossDomainMessenger = new L1CrossDomainMessenger(); + L1CrossDomainMessenger l1CrossDomainMessenger = new L1CrossDomainMessenger(); console2.log("Deployed L1 IL1CrossDomainMessenger to", address(l1CrossDomainMessenger)); vm.stopBroadcast(); @@ -53,12 +54,12 @@ contract Deploy is Script, RiscZeroCheats { IRiscZeroVerifier verifier = deployRiscZeroVerifier(); - IL2CrossDomainMessenger l2CrossDomainMessenger = new L2CrossDomainMessenger( + L2CrossDomainMessenger l2CrossDomainMessenger = new L2CrossDomainMessenger( verifier, ImageID.CROSS_DOMAIN_MESSENGER_ID, address(l1CrossDomainMessenger), L1_BLOCK ); console2.log("Deployed L2 L2CrossDomainMessenger to", address(l2CrossDomainMessenger)); - Counter counter = new Counter(l2CrossDomainMessenger, address(0x0)); + Counter counter = new Counter(l2CrossDomainMessenger, l1Sender); console2.log("Deployed L1 Counter to", address(counter)); vm.stopBroadcast(); diff --git a/examples/message-passing/apps/Cargo.toml b/examples/message-passing/crates/apps/Cargo.toml similarity index 100% rename from examples/message-passing/apps/Cargo.toml rename to examples/message-passing/crates/apps/Cargo.toml diff --git a/examples/message-passing/apps/src/bin/publisher.rs b/examples/message-passing/crates/apps/src/bin/publisher.rs similarity index 97% rename from examples/message-passing/apps/src/bin/publisher.rs rename to examples/message-passing/crates/apps/src/bin/publisher.rs index 0626145a..8940bf89 100644 --- a/examples/message-passing/apps/src/bin/publisher.rs +++ b/examples/message-passing/crates/apps/src/bin/publisher.rs @@ -39,10 +39,10 @@ use tracing_subscriber::EnvFilter; use url::Url; // Contract to call via L1. -sol!("../contracts/src/ICounter.sol"); +sol!("../../contracts/src/ICounter.sol"); /// Arguments of the publisher CLI. -#[derive(Parser, Debug)] +#[derive(Parser)] #[clap(author, version, about, long_about = None)] struct Args { /// L1 private key. @@ -145,8 +145,7 @@ async fn main() -> Result<()> { let env = ExecutorEnv::builder() .write(&evm_input)? .write(&cross_domain_messenger_input)? - .build() - .unwrap(); + .build()?; default_prover().prove_with_ctx( env, @@ -155,8 +154,7 @@ async fn main() -> Result<()> { &ProverOpts::groth16(), ) }) - .await? - .context("failed to create proof")?; + .await??; println!( "Proving finished in {} cycles", prove_info.stats.total_cycles diff --git a/examples/message-passing/core/Cargo.toml b/examples/message-passing/crates/core/Cargo.toml similarity index 100% rename from examples/message-passing/core/Cargo.toml rename to examples/message-passing/crates/core/Cargo.toml diff --git a/examples/message-passing/core/src/contracts.rs b/examples/message-passing/crates/core/src/contracts.rs similarity index 97% rename from examples/message-passing/core/src/contracts.rs rename to examples/message-passing/crates/core/src/contracts.rs index 6d84648d..52d29a78 100644 --- a/examples/message-passing/core/src/contracts.rs +++ b/examples/message-passing/crates/core/src/contracts.rs @@ -33,18 +33,18 @@ use crate::{ sol!( #[sol(rpc, all_derives)] - "../contracts/src/IL1CrossDomainMessenger.sol" + "../../contracts/src/IL1CrossDomainMessenger.sol" ); sol!( #[sol(rpc, all_derives)] - "../contracts/src/IL2CrossDomainMessenger.sol" + "../../contracts/src/IL2CrossDomainMessenger.sol" ); // Contract to bookmark L1 blocks for later verification. sol!( #[sol(rpc, all_derives)] - "../contracts/src/IBookmark.sol" + "../../contracts/src/IBookmark.sol" ); #[derive(Clone)] diff --git a/examples/message-passing/core/src/lib.rs b/examples/message-passing/crates/core/src/lib.rs similarity index 100% rename from examples/message-passing/core/src/lib.rs rename to examples/message-passing/crates/core/src/lib.rs diff --git a/examples/message-passing/methods/Cargo.toml b/examples/message-passing/crates/methods/Cargo.toml similarity index 100% rename from examples/message-passing/methods/Cargo.toml rename to examples/message-passing/crates/methods/Cargo.toml diff --git a/examples/message-passing/methods/build.rs b/examples/message-passing/crates/methods/build.rs similarity index 92% rename from examples/message-passing/methods/build.rs rename to examples/message-passing/crates/methods/build.rs index 4d704506..410700c3 100644 --- a/examples/message-passing/methods/build.rs +++ b/examples/message-passing/crates/methods/build.rs @@ -18,8 +18,8 @@ use risc0_build::{embed_methods_with_options, DockerOptions, GuestOptions}; use risc0_build_ethereum::generate_solidity_files; // Paths where the generated Solidity files will be written. -const SOLIDITY_IMAGE_ID_PATH: &str = "../contracts/src/ImageID.sol"; -const SOLIDITY_ELF_PATH: &str = "../contracts/src/Elf.sol"; +const SOLIDITY_IMAGE_ID_PATH: &str = "../../contracts/src/ImageID.sol"; +const SOLIDITY_ELF_PATH: &str = "../../contracts/src/Elf.sol"; fn main() { // Builds can be made deterministic, and thereby reproducible, by using Docker to build the diff --git a/examples/message-passing/methods/guest/Cargo.toml b/examples/message-passing/crates/methods/guest/Cargo.toml similarity index 78% rename from examples/message-passing/methods/guest/Cargo.toml rename to examples/message-passing/crates/methods/guest/Cargo.toml index b66f68b2..4283539e 100644 --- a/examples/message-passing/methods/guest/Cargo.toml +++ b/examples/message-passing/crates/methods/guest/Cargo.toml @@ -10,11 +10,8 @@ path = "src/bin/cross_domain_messenger.rs" [workspace] [dependencies] - -# Force the `compile-time-rng` feature, to prevent `getrandom` from being used. -ahash = { version = "0.8", default-features = false, features = ["compile-time-rng"] } alloy-sol-types = { version = "0.7" } -risc0-steel = { path = "../../../../steel" } +risc0-steel = { path = "../../../../../steel" } cross-domain-messenger-core = { path = "../../core" } risc0-zkvm = { git = "https://github.com/risc0/risc0", branch = "main", default-features = false, features = ["std"] } diff --git a/examples/message-passing/methods/guest/src/bin/cross_domain_messenger.rs b/examples/message-passing/crates/methods/guest/src/bin/cross_domain_messenger.rs similarity index 100% rename from examples/message-passing/methods/guest/src/bin/cross_domain_messenger.rs rename to examples/message-passing/crates/methods/guest/src/bin/cross_domain_messenger.rs diff --git a/examples/message-passing/methods/src/lib.rs b/examples/message-passing/crates/methods/src/lib.rs similarity index 100% rename from examples/message-passing/methods/src/lib.rs rename to examples/message-passing/crates/methods/src/lib.rs diff --git a/examples/message-passing/foundry.toml b/examples/message-passing/foundry.toml index a553c56a..0cf40fb2 100644 --- a/examples/message-passing/foundry.toml +++ b/examples/message-passing/foundry.toml @@ -1,9 +1,7 @@ [profile.default] -src = "contracts" +src = "contracts/src" out = "out" libs = ["../../lib", "../../contracts/src"] -test = "tests" -script = "scripts" -ffi = true - -# See more config options https://github.com/foundry-rs/foundry/tree/master/config +test = "contracts/test" +script = "contracts/script" +evm_version = 'cancun' diff --git a/examples/message-passing/optimism b/examples/message-passing/optimism new file mode 160000 index 00000000..4797ddb7 --- /dev/null +++ b/examples/message-passing/optimism @@ -0,0 +1 @@ +Subproject commit 4797ddb70e05d4952685bad53e608cb5606284e6 From b2d8ef65b1b0860d1f854a1a3743a65e0a9d7e1d Mon Sep 17 00:00:00 2001 From: Wolfgang Welz Date: Fri, 6 Sep 2024 15:53:55 +0200 Subject: [PATCH 17/44] Update main.yml --- .github/workflows/main.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 64249b11..3c7db4b3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -126,20 +126,30 @@ jobs: - os: Linux feature: cuda device: nvidia_rtx_a5000 + defaults: + run: + working-directory: examples/message-passing steps: # This is a workaround from: https://github.com/actions/checkout/issues/590#issuecomment-970586842 - run: "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :" - uses: actions/checkout@v4 with: submodules: recursive + - name: Load versions JSON + uses: rgarcia-phi/json-to-variables@v1.1.0 + with: + prefix: version + filename: 'versions.json' - if: matrix.feature == 'cuda' uses: risc0/risc0/.github/actions/cuda@main - uses: actions/setup-go@v5 + with: + go-version: ${{ env.version_go }} - uses: risc0/risc0/.github/actions/rustup@main - uses: risc0/risc0/.github/actions/sccache@main with: key: ${{ matrix.os }}-${{ matrix.feature }} - - uses: risc0/foundry-toolchain@2fe7e70b520f62368a0e3c464f997df07ede420f + - uses: risc0/foundry-toolchain@${{ env.version_foundry }} - uses: ./.github/actions/cargo-risczero-install with: ref: ${{ env.RISC0_MONOREPO_REF }} @@ -157,7 +167,6 @@ jobs: docker tag "$IMAGE_BASE_PREFIX/op-batcher:v1.9.1" "$IMAGE_BASE_PREFIX/op-batcher:devnet" - name: Bring up the OP stack run: DEVNET_NO_BUILD=false DEVNET_L2OO=true DEVNET_ALTDA=false make -C optimism devnet-up - working-directory: examples/message-passing examples: runs-on: [self-hosted, prod, "${{ matrix.os }}", "${{ matrix.device }}"] From 17a7a585a6a43c3a21a766bfed55340a14c90a3e Mon Sep 17 00:00:00 2001 From: Wolfgang Welz Date: Fri, 6 Sep 2024 16:21:56 +0200 Subject: [PATCH 18/44] Update main.yml --- .github/workflows/main.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3c7db4b3..85670c88 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -136,20 +136,17 @@ jobs: with: submodules: recursive - name: Load versions JSON - uses: rgarcia-phi/json-to-variables@v1.1.0 - with: - prefix: version - filename: 'versions.json' + run: jq -r 'to_entries[] | ["version_\(.key)",.value] | join("=")' optimism/versions.json >> "$GITHUB_ENV" - if: matrix.feature == 'cuda' uses: risc0/risc0/.github/actions/cuda@main - uses: actions/setup-go@v5 with: - go-version: ${{ env.version_go }} + go-version: $version_go - uses: risc0/risc0/.github/actions/rustup@main - uses: risc0/risc0/.github/actions/sccache@main with: key: ${{ matrix.os }}-${{ matrix.feature }} - - uses: risc0/foundry-toolchain@${{ env.version_foundry }} + - uses: risc0/foundry-toolchain@$version_foundry - uses: ./.github/actions/cargo-risczero-install with: ref: ${{ env.RISC0_MONOREPO_REF }} From 8f2f448eab51af1d5863b2c3371e177f44f08f55 Mon Sep 17 00:00:00 2001 From: Wolfgang Welz Date: Fri, 6 Sep 2024 16:32:53 +0200 Subject: [PATCH 19/44] Update main.yml --- .github/workflows/main.yml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 85670c88..d93b452e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -146,7 +146,9 @@ jobs: - uses: risc0/risc0/.github/actions/sccache@main with: key: ${{ matrix.os }}-${{ matrix.feature }} - - uses: risc0/foundry-toolchain@$version_foundry + - uses: risc0/foundry-toolchain@main + with: + version: $version_foundry - uses: ./.github/actions/cargo-risczero-install with: ref: ${{ env.RISC0_MONOREPO_REF }} @@ -154,16 +156,20 @@ jobs: features: ${{ matrix.feature }} - name: Pull and tag OP docker images run: | - IMAGE_BASE_PREFIX="us-docker.pkg.dev/oplabs-tools-artifacts/images" docker pull "$IMAGE_BASE_PREFIX/op-node:v1.9.1" docker pull "$IMAGE_BASE_PREFIX/op-proposer:v1.9.1" docker pull "$IMAGE_BASE_PREFIX/op-batcher:v1.9.1" - # rename to the tags that the docker-compose of the devnet expects docker tag "$IMAGE_BASE_PREFIX/op-node:v1.9.1" "$IMAGE_BASE_PREFIX/op-node:devnet" docker tag "$IMAGE_BASE_PREFIX/op-proposer:v1.9.1" "$IMAGE_BASE_PREFIX/op-proposer:devnet" docker tag "$IMAGE_BASE_PREFIX/op-batcher:v1.9.1" "$IMAGE_BASE_PREFIX/op-batcher:devnet" + env: + IMAGE_BASE_PREFIX: "us-docker.pkg.dev/oplabs-tools-artifacts/images" - name: Bring up the OP stack - run: DEVNET_NO_BUILD=false DEVNET_L2OO=true DEVNET_ALTDA=false make -C optimism devnet-up + run: make -C optimism devnet-up + env: + DEVNET_NO_BUILD: false + DEVNET_L2OO: true + DEVNET_ALTDA: false examples: runs-on: [self-hosted, prod, "${{ matrix.os }}", "${{ matrix.device }}"] From edd43058ac13216925c268c2881b6bc2a2032971 Mon Sep 17 00:00:00 2001 From: Wolfgang Welz Date: Fri, 6 Sep 2024 16:35:41 +0200 Subject: [PATCH 20/44] Update main.yml --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d93b452e..44b3fa9a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -146,7 +146,7 @@ jobs: - uses: risc0/risc0/.github/actions/sccache@main with: key: ${{ matrix.os }}-${{ matrix.feature }} - - uses: risc0/foundry-toolchain@main + - uses: risc0/foundry-toolchain@2fe7e70b520f62368a0e3c464f997df07ede420f with: version: $version_foundry - uses: ./.github/actions/cargo-risczero-install From da1ce8123928d10f43ca5c464fdc75cdb999c330 Mon Sep 17 00:00:00 2001 From: Wolfgang Welz Date: Fri, 6 Sep 2024 16:39:39 +0200 Subject: [PATCH 21/44] Update main.yml --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 44b3fa9a..f81c5765 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -141,14 +141,14 @@ jobs: uses: risc0/risc0/.github/actions/cuda@main - uses: actions/setup-go@v5 with: - go-version: $version_go + go-version: ${{ var.version_go }} - uses: risc0/risc0/.github/actions/rustup@main - uses: risc0/risc0/.github/actions/sccache@main with: key: ${{ matrix.os }}-${{ matrix.feature }} - uses: risc0/foundry-toolchain@2fe7e70b520f62368a0e3c464f997df07ede420f with: - version: $version_foundry + version: ${{ var.version_foundry }} - uses: ./.github/actions/cargo-risczero-install with: ref: ${{ env.RISC0_MONOREPO_REF }} From 8cab6bfc902a1cbabf4a4f418e73f12555b1d269 Mon Sep 17 00:00:00 2001 From: Wolfgang Welz Date: Fri, 6 Sep 2024 16:40:50 +0200 Subject: [PATCH 22/44] Update main.yml --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f81c5765..5d4ae94c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -141,14 +141,14 @@ jobs: uses: risc0/risc0/.github/actions/cuda@main - uses: actions/setup-go@v5 with: - go-version: ${{ var.version_go }} + go-version: ${{ env.version_go }} - uses: risc0/risc0/.github/actions/rustup@main - uses: risc0/risc0/.github/actions/sccache@main with: key: ${{ matrix.os }}-${{ matrix.feature }} - uses: risc0/foundry-toolchain@2fe7e70b520f62368a0e3c464f997df07ede420f with: - version: ${{ var.version_foundry }} + version: ${{ env.version_foundry }} - uses: ./.github/actions/cargo-risczero-install with: ref: ${{ env.RISC0_MONOREPO_REF }} From bacbeb201fe45956fc86c23e79921326f49f1fa4 Mon Sep 17 00:00:00 2001 From: Wolfgang Welz Date: Fri, 6 Sep 2024 16:48:02 +0200 Subject: [PATCH 23/44] Update main.yml --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5d4ae94c..af32b9be 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -148,7 +148,7 @@ jobs: key: ${{ matrix.os }}-${{ matrix.feature }} - uses: risc0/foundry-toolchain@2fe7e70b520f62368a0e3c464f997df07ede420f with: - version: ${{ env.version_foundry }} + version: nightly-${{ env.version_foundry }} - uses: ./.github/actions/cargo-risczero-install with: ref: ${{ env.RISC0_MONOREPO_REF }} From cc9eb4c9d74446e2120ba1377261ca080189457d Mon Sep 17 00:00:00 2001 From: Wolfgang Welz Date: Fri, 6 Sep 2024 16:54:02 +0200 Subject: [PATCH 24/44] Update main.yml --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index af32b9be..7b88a7c7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -132,6 +132,7 @@ jobs: steps: # This is a workaround from: https://github.com/actions/checkout/issues/590#issuecomment-970586842 - run: "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :" + - run: rm -rf /opt/actions-runner/_work/risc0-ethereum/risc0-ethereum/.git/modules/examples/message-passing/optimism/shallow.lock - uses: actions/checkout@v4 with: submodules: recursive From 3a3afb9b02761ce26fe9147a9d20496b4c21a838 Mon Sep 17 00:00:00 2001 From: Wolfgang Welz Date: Fri, 6 Sep 2024 16:58:46 +0200 Subject: [PATCH 25/44] Update main.yml --- .github/workflows/main.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7b88a7c7..abcdf6fa 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -132,7 +132,9 @@ jobs: steps: # This is a workaround from: https://github.com/actions/checkout/issues/590#issuecomment-970586842 - run: "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :" - - run: rm -rf /opt/actions-runner/_work/risc0-ethereum/risc0-ethereum/.git/modules/examples/message-passing/optimism/shallow.lock + - name: Clean the repo + working-directory: ${{ github.event.repository.name }} + run: find . -wholename "*.git/modules/*shallow.lock" -type f -delete - uses: actions/checkout@v4 with: submodules: recursive From 1fe54476e579eb7c0404d9d867bb359270c3894d Mon Sep 17 00:00:00 2001 From: Wolfgang Welz Date: Fri, 6 Sep 2024 17:01:00 +0200 Subject: [PATCH 26/44] Update main.yml --- .github/workflows/main.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index abcdf6fa..1173bea8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -132,12 +132,10 @@ jobs: steps: # This is a workaround from: https://github.com/actions/checkout/issues/590#issuecomment-970586842 - run: "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :" + - uses: actions/checkout@v4 - name: Clean the repo working-directory: ${{ github.event.repository.name }} run: find . -wholename "*.git/modules/*shallow.lock" -type f -delete - - uses: actions/checkout@v4 - with: - submodules: recursive - name: Load versions JSON run: jq -r 'to_entries[] | ["version_\(.key)",.value] | join("=")' optimism/versions.json >> "$GITHUB_ENV" - if: matrix.feature == 'cuda' From 937a15f6d5cfba9aaaf66fb1f4ed62f19a3b356a Mon Sep 17 00:00:00 2001 From: Wolfgang Welz Date: Fri, 6 Sep 2024 17:04:35 +0200 Subject: [PATCH 27/44] Update main.yml --- .github/workflows/main.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1173bea8..c831d45e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -134,8 +134,7 @@ jobs: - run: "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :" - uses: actions/checkout@v4 - name: Clean the repo - working-directory: ${{ github.event.repository.name }} - run: find . -wholename "*.git/modules/*shallow.lock" -type f -delete + run: rm -rf /opt/actions-runner/_work/risc0-ethereum/risc0-ethereum/.git/modules/examples/message-passing/optimism/shallow.lock - name: Load versions JSON run: jq -r 'to_entries[] | ["version_\(.key)",.value] | join("=")' optimism/versions.json >> "$GITHUB_ENV" - if: matrix.feature == 'cuda' From 053135df38aea8d926bd27ae8c83e41222073b27 Mon Sep 17 00:00:00 2001 From: Wolfgang Welz Date: Fri, 6 Sep 2024 17:06:54 +0200 Subject: [PATCH 28/44] Update main.yml --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c831d45e..af32b9be 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -133,8 +133,8 @@ jobs: # This is a workaround from: https://github.com/actions/checkout/issues/590#issuecomment-970586842 - run: "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :" - uses: actions/checkout@v4 - - name: Clean the repo - run: rm -rf /opt/actions-runner/_work/risc0-ethereum/risc0-ethereum/.git/modules/examples/message-passing/optimism/shallow.lock + with: + submodules: recursive - name: Load versions JSON run: jq -r 'to_entries[] | ["version_\(.key)",.value] | join("=")' optimism/versions.json >> "$GITHUB_ENV" - if: matrix.feature == 'cuda' From cd15dcbfdecb831665d7af150f7f05a43fa7b176 Mon Sep 17 00:00:00 2001 From: Wolfgang Welz Date: Fri, 6 Sep 2024 17:23:13 +0200 Subject: [PATCH 29/44] Update main.yml --- .github/workflows/main.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index af32b9be..62c23141 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -142,6 +142,9 @@ jobs: - 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/rustup@main - uses: risc0/risc0/.github/actions/sccache@main with: From 2cb7041278d3d89122726df3ff6f3a7b03d23320 Mon Sep 17 00:00:00 2001 From: Wolfgang Welz Date: Fri, 6 Sep 2024 17:42:48 +0200 Subject: [PATCH 30/44] Update main.yml --- .github/workflows/main.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 62c23141..6561455c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -87,8 +87,6 @@ jobs: FEATURE: ${{ matrix.feature }} RUST_BACKTRACE: full steps: - # This is a workaround from: https://github.com/actions/checkout/issues/590#issuecomment-970586842 - - run: "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :" - uses: actions/checkout@v4 with: submodules: recursive From c341cf317e1acb5085a6428d3238d4ed61912b4a Mon Sep 17 00:00:00 2001 From: Wolfgang Welz Date: Fri, 6 Sep 2024 17:54:54 +0200 Subject: [PATCH 31/44] Update main.yml --- .github/workflows/main.yml | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6561455c..29d7dcd3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -87,6 +87,8 @@ jobs: FEATURE: ${{ matrix.feature }} RUST_BACKTRACE: full steps: + # This is a workaround from: https://github.com/actions/checkout/issues/590#issuecomment-970586842 + - run: "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :" - uses: actions/checkout@v4 with: submodules: recursive @@ -133,7 +135,7 @@ jobs: - uses: actions/checkout@v4 with: submodules: recursive - - name: Load versions JSON + - name: Load versions.json run: jq -r 'to_entries[] | ["version_\(.key)",.value] | join("=")' optimism/versions.json >> "$GITHUB_ENV" - if: matrix.feature == 'cuda' uses: risc0/risc0/.github/actions/cuda@main @@ -150,11 +152,10 @@ jobs: - uses: risc0/foundry-toolchain@2fe7e70b520f62368a0e3c464f997df07ede420f with: version: nightly-${{ env.version_foundry }} - - uses: ./.github/actions/cargo-risczero-install - with: - ref: ${{ env.RISC0_MONOREPO_REF }} - toolchain-version: ${{ env.RISC0_TOOLCHAIN_VERSION }} - features: ${{ matrix.feature }} + - name: Install Geth + run: | + wget "https://gethstore.blob.core.windows.net/builds/geth-alltools-linux-amd64-${version_geth_release}.tar.gz" -O - | tar xz + sudo cp "geth-alltools-linux-amd64-${version_geth_release}"/* /usr/local/bin - name: Pull and tag OP docker images run: | docker pull "$IMAGE_BASE_PREFIX/op-node:v1.9.1" @@ -168,9 +169,15 @@ jobs: - name: Bring up the OP stack run: make -C optimism devnet-up env: - DEVNET_NO_BUILD: false + LOG_LEVEL: "INFO" + 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: ${{ matrix.feature }} examples: runs-on: [self-hosted, prod, "${{ matrix.os }}", "${{ matrix.device }}"] From cf8fa5a77bf6a403753aaad7d4fbabaad3ead3b8 Mon Sep 17 00:00:00 2001 From: Wolfgang Welz Date: Fri, 6 Sep 2024 18:26:47 +0200 Subject: [PATCH 32/44] run e2e test in the CI --- .github/workflows/main.yml | 8 +++----- examples/message-passing/crates/apps/src/bin/publisher.rs | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 29d7dcd3..ec2c35c4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -152,10 +152,6 @@ jobs: - uses: risc0/foundry-toolchain@2fe7e70b520f62368a0e3c464f997df07ede420f with: version: nightly-${{ env.version_foundry }} - - name: Install Geth - run: | - wget "https://gethstore.blob.core.windows.net/builds/geth-alltools-linux-amd64-${version_geth_release}.tar.gz" -O - | tar xz - sudo cp "geth-alltools-linux-amd64-${version_geth_release}"/* /usr/local/bin - name: Pull and tag OP docker images run: | docker pull "$IMAGE_BASE_PREFIX/op-node:v1.9.1" @@ -169,7 +165,6 @@ jobs: - name: Bring up the OP stack run: make -C optimism devnet-up env: - LOG_LEVEL: "INFO" DEVNET_NO_BUILD: true DEVNET_L2OO: true DEVNET_ALTDA: false @@ -178,6 +173,9 @@ jobs: ref: ${{ env.RISC0_MONOREPO_REF }} toolchain-version: ${{ env.RISC0_TOOLCHAIN_VERSION }} features: ${{ matrix.feature }} + - run: cargo build + - name: Run E2E test + run: ./test-local-devnet.sh examples: runs-on: [self-hosted, prod, "${{ matrix.os }}", "${{ matrix.device }}"] diff --git a/examples/message-passing/crates/apps/src/bin/publisher.rs b/examples/message-passing/crates/apps/src/bin/publisher.rs index 8940bf89..d5e9f14c 100644 --- a/examples/message-passing/crates/apps/src/bin/publisher.rs +++ b/examples/message-passing/crates/apps/src/bin/publisher.rs @@ -21,7 +21,7 @@ use alloy::{ sol_types::SolCall, }; use alloy_primitives::Address; -use anyhow::{ensure, Context, Result}; +use anyhow::{ensure, Result}; use clap::Parser; use cross_domain_messenger_core::{ contracts::{ From e09f1e69187be17318f39a1f0bc28ba05150fcd2 Mon Sep 17 00:00:00 2001 From: Wolfgang Welz Date: Fri, 6 Sep 2024 18:47:32 +0200 Subject: [PATCH 33/44] add missing script --- .github/workflows/main.yml | 7 +------ examples/message-passing/test-local-devnet.sh | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 6 deletions(-) create mode 100755 examples/message-passing/test-local-devnet.sh diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ec2c35c4..06d3cb1a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -174,6 +174,7 @@ jobs: toolchain-version: ${{ env.RISC0_TOOLCHAIN_VERSION }} features: ${{ matrix.feature }} - run: cargo build + - run: forge test - name: Run E2E test run: ./test-local-devnet.sh @@ -222,12 +223,6 @@ jobs: - name: build token-stats run: cargo build working-directory: examples/token-stats - - name: build message-passing - run: cargo build - working-directory: examples/message-passing - - name: forge test message-passing - run: forge test - working-directory: examples/message-passing - name: test erc20-Counter run: ./test-local-deployment.sh working-directory: examples/erc20-counter diff --git a/examples/message-passing/test-local-devnet.sh b/examples/message-passing/test-local-devnet.sh new file mode 100755 index 00000000..993ad721 --- /dev/null +++ b/examples/message-passing/test-local-devnet.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +forge script --broadcast Deploy + +L1_CROSS_DOMAIN_MESSENGER_ADDRESS=$(jq -re '[.deployments[].transactions[] | select(.contractName == "L1CrossDomainMessenger")][0] | .contractAddress' ./broadcast/multi/Deploy.s.sol-latest/run.json) +L2_CROSS_DOMAIN_MESSENGER_ADDRESS=$(jq -re '[.deployments[].transactions[] | select(.contractName == "L2CrossDomainMessenger")][0] | .contractAddress' ./broadcast/multi/Deploy.s.sol-latest/run.json) +COUNTER_ADDRESS=$(jq -re '.deployments[].transactions[] | select(.contractName == "Counter") | .contractAddress' ./broadcast/multi/Deploy.s.sol-latest/run.json) + +RUST_LOG=info cargo run -- \ + --counter-address=$COUNTER_ADDRESS \ + --l1-cross-domain-messenger-address=$L1_CROSS_DOMAIN_MESSENGER_ADDRESS \ + --l2-cross-domain-messenger-address=$L2_CROSS_DOMAIN_MESSENGER_ADDRESS + +echo "Verifying state..." +COUNTER_VALUE=$(cast call --rpc-url http://localhost:9545 ${COUNTER_ADDRESS:?} 'get()(uint256)') +if [ "$COUNTER_VALUE" != "1" ]; then + echo "Counter value is not 1 as expected, but $COUNTER_VALUE." + exit 1 +fi From 2dc02b86820733f78c93870694818512c966879f Mon Sep 17 00:00:00 2001 From: Wolfgang Welz Date: Fri, 6 Sep 2024 21:18:56 +0200 Subject: [PATCH 34/44] use random Anvil port --- examples/erc20-counter/test-local-deployment.sh | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/examples/erc20-counter/test-local-deployment.sh b/examples/erc20-counter/test-local-deployment.sh index cd78a315..2d933096 100755 --- a/examples/erc20-counter/test-local-deployment.sh +++ b/examples/erc20-counter/test-local-deployment.sh @@ -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 @@ -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) @@ -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 From 48d034a65b4a0b6654945626f0f588bce6fe0ce0 Mon Sep 17 00:00:00 2001 From: Wolfgang Welz Date: Fri, 6 Sep 2024 21:39:56 +0200 Subject: [PATCH 35/44] CI cleanups --- .github/workflows/main.yml | 41 ++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 06d3cb1a..99bcaa24 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -118,50 +118,42 @@ jobs: - run: sccache --show-stats op-message-passing: - runs-on: [ self-hosted, prod, "${{ matrix.os }}", "${{ matrix.device }}" ] - strategy: - # Run only on Linux with GPU. Additional coverage is marginal, and GPU is fastest. - matrix: - include: - - os: Linux - feature: cuda - device: nvidia_rtx_a5000 + # Run only on Linux with GPU. + runs-on: [ self-hosted, prod, "Linux", "nvidia_rtx_a5000" ] defaults: run: working-directory: examples/message-passing steps: - # This is a workaround from: https://github.com/actions/checkout/issues/590#issuecomment-970586842 - - run: "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :" - uses: actions/checkout@v4 with: submodules: recursive - - name: Load versions.json + - name: Load OP versions run: jq -r 'to_entries[] | ["version_\(.key)",.value] | join("=")' optimism/versions.json >> "$GITHUB_ENV" - - if: matrix.feature == 'cuda' - uses: risc0/risc0/.github/actions/cuda@main - uses: actions/setup-go@v5 with: go-version: ${{ env.version_go }} - uses: actions/setup-python@v5 with: - python-version: '3.10' + 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: ${{ matrix.os }}-${{ matrix.feature }} + 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_BASE_PREFIX/op-node:v1.9.1" - docker pull "$IMAGE_BASE_PREFIX/op-proposer:v1.9.1" - docker pull "$IMAGE_BASE_PREFIX/op-batcher:v1.9.1" - docker tag "$IMAGE_BASE_PREFIX/op-node:v1.9.1" "$IMAGE_BASE_PREFIX/op-node:devnet" - docker tag "$IMAGE_BASE_PREFIX/op-proposer:v1.9.1" "$IMAGE_BASE_PREFIX/op-proposer:devnet" - docker tag "$IMAGE_BASE_PREFIX/op-batcher:v1.9.1" "$IMAGE_BASE_PREFIX/op-batcher:devnet" + 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_BASE_PREFIX: "us-docker.pkg.dev/oplabs-tools-artifacts/images" + 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: @@ -172,11 +164,12 @@ jobs: with: ref: ${{ env.RISC0_MONOREPO_REF }} toolchain-version: ${{ env.RISC0_TOOLCHAIN_VERSION }} - features: ${{ matrix.feature }} + features: cuda - run: cargo build - - run: forge test + - run: forge test -vvv - name: Run E2E test run: ./test-local-devnet.sh + - run: sccache --show-stats examples: runs-on: [self-hosted, prod, "${{ matrix.os }}", "${{ matrix.device }}"] From 035111c508e4f9685fb63ea9a0d6431b6af13510 Mon Sep 17 00:00:00 2001 From: Wolfgang Welz Date: Sat, 7 Sep 2024 01:30:02 +0200 Subject: [PATCH 36/44] WIP: beacon commitments --- examples/message-passing/.env | 1 + .../contracts/script/Deploy.s.sol | 9 +-- .../contracts/src/Bookmark.sol | 45 ------------- .../contracts/src/IBookmark.sol | 29 -------- .../contracts/src/IL1Block.sol | 26 ------- .../contracts/src/IL2CrossDomainMessenger.sol | 4 +- .../contracts/src/L2CrossDomainMessenger.sol | 16 +---- .../message-passing/contracts/test/E2E.t.sol | 36 +++++----- .../contracts/test/L1BlockMock.sol | 29 -------- .../crates/apps/src/bin/publisher.rs | 16 ++--- .../crates/core/src/contracts.rs | 67 +------------------ steel/src/beacon.rs | 2 +- 12 files changed, 36 insertions(+), 244 deletions(-) delete mode 100644 examples/message-passing/contracts/src/Bookmark.sol delete mode 100644 examples/message-passing/contracts/src/IBookmark.sol delete mode 100644 examples/message-passing/contracts/src/IL1Block.sol delete mode 100644 examples/message-passing/contracts/test/L1BlockMock.sol diff --git a/examples/message-passing/.env b/examples/message-passing/.env index 24b01d64..e3b26e8f 100644 --- a/examples/message-passing/.env +++ b/examples/message-passing/.env @@ -1,6 +1,7 @@ ## L1 Ethereum Config L1_RPC_URL="http://localhost:8545/" +BEACON_API_URL="http://localhost:5052" L1_ADMIN_PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" diff --git a/examples/message-passing/contracts/script/Deploy.s.sol b/examples/message-passing/contracts/script/Deploy.s.sol index 0b504ead..b9a402c7 100644 --- a/examples/message-passing/contracts/script/Deploy.s.sol +++ b/examples/message-passing/contracts/script/Deploy.s.sol @@ -22,14 +22,10 @@ import {IRiscZeroVerifier} from "risc0/IRiscZeroVerifier.sol"; import {RiscZeroMockVerifier} from "risc0/test/RiscZeroMockVerifier.sol"; import {L1CrossDomainMessenger} from "../src/L1CrossDomainMessenger.sol"; import {L2CrossDomainMessenger} from "../src/L2CrossDomainMessenger.sol"; -import {IL1Block} from "../src/IL1Block.sol"; import {ImageID} from "../src/ImageID.sol"; import {Counter} from "../src/Counter.sol"; contract Deploy is Script, RiscZeroCheats { - // In OP the L1Block contract on L2 is always at the same predeployed address - IL1Block constant L1_BLOCK = IL1Block(0x4200000000000000000000000000000000000015); - function run() external { // load ENV variables first uint256 key1 = vm.envUint("L1_ADMIN_PRIVATE_KEY"); @@ -54,9 +50,8 @@ contract Deploy is Script, RiscZeroCheats { IRiscZeroVerifier verifier = deployRiscZeroVerifier(); - L2CrossDomainMessenger l2CrossDomainMessenger = new L2CrossDomainMessenger( - verifier, ImageID.CROSS_DOMAIN_MESSENGER_ID, address(l1CrossDomainMessenger), L1_BLOCK - ); + L2CrossDomainMessenger l2CrossDomainMessenger = + new L2CrossDomainMessenger(verifier, ImageID.CROSS_DOMAIN_MESSENGER_ID, address(l1CrossDomainMessenger)); console2.log("Deployed L2 L2CrossDomainMessenger to", address(l2CrossDomainMessenger)); Counter counter = new Counter(l2CrossDomainMessenger, l1Sender); diff --git a/examples/message-passing/contracts/src/Bookmark.sol b/examples/message-passing/contracts/src/Bookmark.sol deleted file mode 100644 index c09beb06..00000000 --- a/examples/message-passing/contracts/src/Bookmark.sol +++ /dev/null @@ -1,45 +0,0 @@ -// 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]; - } -} diff --git a/examples/message-passing/contracts/src/IBookmark.sol b/examples/message-passing/contracts/src/IBookmark.sol deleted file mode 100644 index 6e31f28d..00000000 --- a/examples/message-passing/contracts/src/IBookmark.sol +++ /dev/null @@ -1,29 +0,0 @@ -// 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; - -/// @notice Interface to bookmark L1 blocks. -interface IBookmark { - /// @notice A new L1 block has been bookmarked. - event BookmarkedL1Block(uint64 number, bytes32 hash); - - /// @notice Bookmarks the current L1 block. - function bookmarkL1Block() external returns (uint64); - - /// @notice Returns the bookmarked hash of the block with the given number. - function getBookmark(uint64 blockNumber) external view returns (bytes32); -} diff --git a/examples/message-passing/contracts/src/IL1Block.sol b/examples/message-passing/contracts/src/IL1Block.sol deleted file mode 100644 index 33bd8a91..00000000 --- a/examples/message-passing/contracts/src/IL1Block.sol +++ /dev/null @@ -1,26 +0,0 @@ -// 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; - -/// @notice The L1Block predeploy gives users access to information about the last known L1 block. -interface IL1Block { - /// @notice The latest L1 block number known by the L2 system. - function number() external view returns (uint64); - - /// @notice The latest L1 blockhash. - function hash() external view returns (bytes32); -} diff --git a/examples/message-passing/contracts/src/IL2CrossDomainMessenger.sol b/examples/message-passing/contracts/src/IL2CrossDomainMessenger.sol index 22a00c23..fee81dee 100644 --- a/examples/message-passing/contracts/src/IL2CrossDomainMessenger.sol +++ b/examples/message-passing/contracts/src/IL2CrossDomainMessenger.sol @@ -16,9 +16,7 @@ pragma solidity ^0.8.20; -import {IBookmark} from "./IBookmark.sol"; - -interface IL2CrossDomainMessenger is IBookmark { +interface IL2CrossDomainMessenger { /// @notice Emitted whenever a message is successfully relayed on this chain. /// @param msgHash Hash of the message that was relayed. event RelayedMessage(bytes32 indexed msgHash); diff --git a/examples/message-passing/contracts/src/L2CrossDomainMessenger.sol b/examples/message-passing/contracts/src/L2CrossDomainMessenger.sol index 0836f3a1..053c100c 100644 --- a/examples/message-passing/contracts/src/L2CrossDomainMessenger.sol +++ b/examples/message-passing/contracts/src/L2CrossDomainMessenger.sol @@ -17,18 +17,14 @@ pragma solidity ^0.8.20; import {Address} from "openzeppelin/contracts/utils/Address.sol"; -import {SafeCast} from "openzeppelin/contracts/utils/math/SafeCast.sol"; import {IRiscZeroVerifier} from "risc0/IRiscZeroVerifier.sol"; import {Steel} from "risc0/steel/Steel.sol"; import {IL2CrossDomainMessenger} from "./IL2CrossDomainMessenger.sol"; -import {IL1Block} from "./IL1Block.sol"; -import {Bookmark} from "./Bookmark.sol"; import {Journal, Message, Digest} from "./Structs.sol"; /// @notice L1Bridging verifier contract for RISC Zero receipts of execution. -contract L2CrossDomainMessenger is IL2CrossDomainMessenger, Bookmark { +contract L2CrossDomainMessenger is IL2CrossDomainMessenger { using Address for address; - using SafeCast for uint256; using Digest for Journal; /// @notice Value used for the L1 sender storage slot before an actual sender is set. This value is non-zero to @@ -51,9 +47,7 @@ contract L2CrossDomainMessenger is IL2CrossDomainMessenger, Bookmark { // it has successfully been relayed, and can therefore not be relayed again. mapping(bytes32 => bool) private relayedMessages; - constructor(IRiscZeroVerifier verifier, bytes32 imageId, address l1CrossDomainMessenger, IL1Block l1Block) - Bookmark(l1Block) - { + constructor(IRiscZeroVerifier verifier, bytes32 imageId, address l1CrossDomainMessenger) { VERIFIER = verifier; IMAGE_ID = imageId; L1_CROSS_DOMAIN_MESSENGER = l1CrossDomainMessenger; @@ -66,7 +60,7 @@ contract L2CrossDomainMessenger is IL2CrossDomainMessenger, Bookmark { Journal memory journal = abi.decode(journalData, (Journal)); require(journal.l1CrossDomainMessenger == L1_CROSS_DOMAIN_MESSENGER, "invalid l1CrossDomainMessenger"); - require(validateCommitment(journal.commitment), "commitment verification failed"); + require(Steel.validateCommitment(journal.commitment), "commitment verification failed"); relayVerifiedMessage(journal.message, journal.messageDigest); } @@ -77,10 +71,6 @@ contract L2CrossDomainMessenger is IL2CrossDomainMessenger, Bookmark { return xDomainMsgSender; } - function validateCommitment(Steel.Commitment memory commitment) internal view returns (bool) { - return commitment.blockDigest == Bookmark.blocks[commitment.blockID.toUint64()]; - } - function relayVerifiedMessage(Message memory message, bytes32 digest) internal { require(xDomainMsgSender == DEFAULT_L1_SENDER, "L2CrossDomainMessenger: reentrant call"); require(!relayedMessages[digest], "L2CrossDomainMessenger: message already relayed"); diff --git a/examples/message-passing/contracts/test/E2E.t.sol b/examples/message-passing/contracts/test/E2E.t.sol index fd33d3af..5750e0df 100644 --- a/examples/message-passing/contracts/test/E2E.t.sol +++ b/examples/message-passing/contracts/test/E2E.t.sol @@ -23,38 +23,41 @@ import {RiscZeroMockVerifier} from "risc0/test/RiscZeroMockVerifier.sol"; import {Journal, Message, Digest} from "../src/Structs.sol"; import {IL1CrossDomainMessenger} from "../src/IL1CrossDomainMessenger.sol"; import {L1CrossDomainMessenger} from "../src/L1CrossDomainMessenger.sol"; -import {IL1Block} from "../src/IL1Block.sol"; import {Journal, L2CrossDomainMessenger} from "../src/L2CrossDomainMessenger.sol"; -import {L1BlockMock} from "./L1BlockMock.sol"; import {Counter} from "../src/Counter.sol"; -import {Steel} from "risc0/steel/Steel.sol"; +import {Steel, Beacon, Encoding} from "risc0/steel/Steel.sol"; contract E2ETest is Test { using Digest for Message; using Digest for Journal; + address internal constant BEACON_ROOTS_ADDRESS = 0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02; + bytes32 internal CROSS_DOMAIN_MESSENGER_IMAGE_ID = bytes32(uint256(0x03)); + bytes4 MOCK_SELECTOR = bytes4(0); + RiscZeroMockVerifier private verifier; L1CrossDomainMessenger private l1CrossDomainMessenger; L2CrossDomainMessenger private l2CrossDomainMessenger; - IL1Block private l1Block; Counter private counter; address private sender; - bytes32 internal CROSS_DOMAIN_MESSENGER_IMAGE_ID = bytes32(uint256(0x03)); - - bytes4 MOCK_SELECTOR = bytes4(0); - function setUp() public { sender = address(1); vm.startPrank(sender); l1CrossDomainMessenger = new L1CrossDomainMessenger(); verifier = new RiscZeroMockVerifier(MOCK_SELECTOR); - l1Block = new L1BlockMock(); - l2CrossDomainMessenger = new L2CrossDomainMessenger( - verifier, CROSS_DOMAIN_MESSENGER_IMAGE_ID, address(l1CrossDomainMessenger), l1Block + l2CrossDomainMessenger = + new L2CrossDomainMessenger(verifier, CROSS_DOMAIN_MESSENGER_IMAGE_ID, address(l1CrossDomainMessenger)); + counter = new Counter(l2CrossDomainMessenger, sender); + + // mock the beacon roots contract + vm.warp(60); + vm.mockCall( + BEACON_ROOTS_ADDRESS, + abi.encode(uint256(block.timestamp)), + abi.encode(keccak256(abi.encodePacked(block.timestamp))) ); - counter = new Counter(l2CrossDomainMessenger, address(0x0)); } function testCounterIncrement() public { @@ -86,15 +89,14 @@ contract E2ETest is Test { emit IL1CrossDomainMessenger.SentMessage(target, sender, data, nonce); l1CrossDomainMessenger.sendMessage(target, data); - // bookmark the next block - vm.roll(1); - uint256 blockNumber = l2CrossDomainMessenger.bookmarkL1Block(); - bytes32 blockHash = l1Block.hash(); + // get the root of a previous Beacon block + uint240 beaconTimestamp = uint240(block.timestamp - 12); + bytes32 beaconRoot = Beacon.blockRoot(beaconTimestamp); // mock the Journal Message memory message = Message(target, sender, data, nonce); Journal memory journal = Journal({ - commitment: Steel.Commitment(blockNumber, blockHash), + commitment: Steel.Commitment(Encoding.encodeVersionedID(beaconTimestamp, 1), beaconRoot), l1CrossDomainMessenger: address(l1CrossDomainMessenger), message: message, messageDigest: message.digest() diff --git a/examples/message-passing/contracts/test/L1BlockMock.sol b/examples/message-passing/contracts/test/L1BlockMock.sol deleted file mode 100644 index 2fd29612..00000000 --- a/examples/message-passing/contracts/test/L1BlockMock.sol +++ /dev/null @@ -1,29 +0,0 @@ -// 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 "../src/IL1Block.sol"; - -contract L1BlockMock is IL1Block { - function number() external view returns (uint64) { - return uint64(block.number - 1); - } - - function hash() external view returns (bytes32) { - return blockhash(block.number - 1); - } -} diff --git a/examples/message-passing/crates/apps/src/bin/publisher.rs b/examples/message-passing/crates/apps/src/bin/publisher.rs index 6f8457eb..ef7c1513 100644 --- a/examples/message-passing/crates/apps/src/bin/publisher.rs +++ b/examples/message-passing/crates/apps/src/bin/publisher.rs @@ -25,8 +25,7 @@ use anyhow::{ensure, Result}; use clap::Parser; use cross_domain_messenger_core::{ contracts::{ - IBookmarkService, IL1CrossDomainMessenger, IL1CrossDomainMessengerService, - IL2CrossDomainMessengerService, + IL1CrossDomainMessenger, IL1CrossDomainMessengerService, IL2CrossDomainMessengerService, }, CrossDomainMessengerInput, }; @@ -57,6 +56,10 @@ struct Args { #[clap(long, env)] l1_rpc_url: Url, + /// Beacon API endpoint URL. + #[clap(long, env)] + beacon_api_url: Url, + /// L2 RPC node endpoint. #[clap(long, env)] l2_rpc_url: Url, @@ -106,8 +109,6 @@ async fn main() -> Result<()> { args.l2_cross_domain_messenger_address, l2_provider.clone(), ); - let bookmark_contract = - IBookmarkService::new(args.l2_cross_domain_messenger_address, l2_provider.clone()); // Prepare the message to be passed from L1 to L2 let target = args.counter_address; @@ -118,14 +119,11 @@ async fn main() -> Result<()> { .send_message(target, data.into()) .await?; - // Bookmark the block number of the message - let bookmark_block_number = bookmark_contract.bookmark(message_block_number).await?; - // Run Steel: // Create an EVM environment from that provider and a block number. let mut env = EthEvmEnv::builder() .provider(l1_provider.clone()) - .block_number(bookmark_block_number) + .block_number(message_block_number) .build() .await?; // Prepare the function call to be called inside steal @@ -137,7 +135,7 @@ async fn main() -> Result<()> { let success = contract.call_builder(&call).call().await?._0; ensure!(success, "message {} not found", call.digest); // Finally, construct the input for the guest. - let evm_input = env.into_input().await?; + let evm_input = env.into_beacon_input(args.beacon_api_url).await?; let cross_domain_messenger_input = CrossDomainMessengerInput { l1_cross_domain_messenger: args.l1_cross_domain_messenger_address, message, diff --git a/examples/message-passing/crates/core/src/contracts.rs b/examples/message-passing/crates/core/src/contracts.rs index 52d29a78..f47ae20e 100644 --- a/examples/message-passing/crates/core/src/contracts.rs +++ b/examples/message-passing/crates/core/src/contracts.rs @@ -20,12 +20,11 @@ use alloy::{ }; use alloy_primitives::{Address, Bytes, B256}; use alloy_sol_types::SolEvent; -use anyhow::{bail, ensure, Context, Result}; -use tokio::time; +use anyhow::{bail, ensure, Result}; use crate::{ contracts::{ - IBookmark::IBookmarkInstance, IL1CrossDomainMessenger::IL1CrossDomainMessengerInstance, + IL1CrossDomainMessenger::IL1CrossDomainMessengerInstance, IL2CrossDomainMessenger::IL2CrossDomainMessengerInstance, }, Message, @@ -41,12 +40,6 @@ sol!( "../../contracts/src/IL2CrossDomainMessenger.sol" ); -// Contract to bookmark L1 blocks for later verification. -sol!( - #[sol(rpc, all_derives)] - "../../contracts/src/IBookmark.sol" -); - #[derive(Clone)] pub struct IL1CrossDomainMessengerService { instance: IL1CrossDomainMessengerInstance, @@ -139,62 +132,6 @@ where } } -#[derive(Clone)] -pub struct IBookmarkService { - instance: IBookmarkInstance, -} - -impl IBookmarkService -where - T: Transport + Clone, - P: Provider + 'static, -{ - pub const TX_TIMEOUT: Duration = Duration::from_secs(30); - - pub fn new(address: Address, provider: P) -> Self { - let instance = IBookmark::new(address, provider); - - IBookmarkService { instance } - } - - pub fn instance(&self) -> &IBookmarkInstance { - &self.instance - } - - pub async fn bookmark(&self, message_block_number: u64) -> Result { - // Call IBookmark.bookmarkL1Block until we can bookmark a block that contains the sent message. - let bookmark_call = self.instance.bookmarkL1Block(); - loop { - let current_block_number = bookmark_call.call().await?._0; - if current_block_number >= message_block_number { - break; - } - println!( - "Waiting for L1 block to catch up: {} < {}", - current_block_number, message_block_number - ); - time::sleep(Duration::from_secs(5)).await; - } - - // Send a transaction calling IBookmark.bookmarkL1Block to create an on-chain bookmark. - let pending_tx = bookmark_call - .send() - .await - .context("failed to send bookmarkL1Block")?; - let receipt = pending_tx - .with_timeout(Some(Duration::from_secs(60))) - .get_receipt() - .await - .context("failed to confirm tx")?; - - // Get the number of the actual bookmarked block. - let event: IBookmark::BookmarkedL1Block = into_event(receipt)?; - let bookmark_block_number = event.number; - - Ok(bookmark_block_number) - } -} - fn into_event(receipt: TransactionReceipt) -> Result { ensure!(receipt.status(), "transaction failed"); for log in receipt.inner.logs() { diff --git a/steel/src/beacon.rs b/steel/src/beacon.rs index e7c0eb8a..e9a24c16 100644 --- a/steel/src/beacon.rs +++ b/steel/src/beacon.rs @@ -90,7 +90,7 @@ mod host { use alloy::{network::Ethereum, providers::Provider, transports::Transport}; use alloy_primitives::Sealable; use anyhow::{bail, ensure, Context}; - use beacon_api_client::{mainnet::Client as BeaconClient, BeaconHeaderSummary, BlockId}; + use beacon_api_client::{minimal::Client as BeaconClient, BeaconHeaderSummary, BlockId}; use ethereum_consensus::{ssz::prelude::*, types::SignedBeaconBlock, Fork}; use proofs::{Proof, ProofAndWitness}; use url::Url; From c0239010abd14335c5d63301d3ab5d68079413aa Mon Sep 17 00:00:00 2001 From: Wolfgang Welz Date: Mon, 9 Sep 2024 11:22:00 +0200 Subject: [PATCH 37/44] only prover --- .../contracts/script/Deploy.s.sol | 2 +- .../crates/apps/src/bin/publisher.rs | 125 +++++---------- .../message-passing/crates/core/Cargo.toml | 7 - .../crates/core/src/contracts.rs | 144 ------------------ .../message-passing/crates/core/src/lib.rs | 23 +-- .../message-passing/crates/methods/Cargo.toml | 1 - examples/message-passing/test-local-devnet.sh | 33 +++- steel/src/beacon.rs | 10 +- steel/src/block.rs | 4 +- steel/src/lib.rs | 2 - 10 files changed, 91 insertions(+), 260 deletions(-) delete mode 100644 examples/message-passing/crates/core/src/contracts.rs diff --git a/examples/message-passing/contracts/script/Deploy.s.sol b/examples/message-passing/contracts/script/Deploy.s.sol index b9a402c7..1f8510ba 100644 --- a/examples/message-passing/contracts/script/Deploy.s.sol +++ b/examples/message-passing/contracts/script/Deploy.s.sol @@ -55,7 +55,7 @@ contract Deploy is Script, RiscZeroCheats { console2.log("Deployed L2 L2CrossDomainMessenger to", address(l2CrossDomainMessenger)); Counter counter = new Counter(l2CrossDomainMessenger, l1Sender); - console2.log("Deployed L1 Counter to", address(counter)); + console2.log("Deployed L2 Counter to", address(counter)); vm.stopBroadcast(); } diff --git a/examples/message-passing/crates/apps/src/bin/publisher.rs b/examples/message-passing/crates/apps/src/bin/publisher.rs index ef7c1513..7b91e232 100644 --- a/examples/message-passing/crates/apps/src/bin/publisher.rs +++ b/examples/message-passing/crates/apps/src/bin/publisher.rs @@ -16,19 +16,11 @@ // to the Bonsai proving service and publish the received proofs directly // to your deployed app contract. -use alloy::{ - network::EthereumWallet, providers::ProviderBuilder, signers::local::PrivateKeySigner, sol, - sol_types::SolCall, -}; -use alloy_primitives::Address; -use anyhow::{ensure, Result}; +use alloy::providers::{Provider, ProviderBuilder}; +use alloy_primitives::{Address, TxHash}; +use anyhow::{ensure, Context, Result}; use clap::Parser; -use cross_domain_messenger_core::{ - contracts::{ - IL1CrossDomainMessenger, IL1CrossDomainMessengerService, IL2CrossDomainMessengerService, - }, - CrossDomainMessengerInput, -}; +use cross_domain_messenger_core::{CrossDomainMessengerInput, IL1CrossDomainMessenger, Message}; use cross_domain_messenger_methods::CROSS_DOMAIN_MESSENGER_ELF; use risc0_ethereum_contracts::encode_seal; use risc0_steel::{ethereum::EthEvmEnv, Contract}; @@ -37,49 +29,31 @@ use tokio::task; use tracing_subscriber::EnvFilter; use url::Url; -// Contract to call via L1. -sol!("../../contracts/src/ICounter.sol"); - /// Arguments of the publisher CLI. #[derive(Parser)] #[clap(author, version, about, long_about = None)] struct Args { - /// L1 private key. - #[clap(long, env)] - l1_wallet_private_key: PrivateKeySigner, - - /// L2 private key. - #[clap(long, env)] - l2_wallet_private_key: PrivateKeySigner, - - /// L1 RPC node endpoint. + /// RPC node endpoint. #[clap(long, env)] - l1_rpc_url: Url, + rpc_url: Url, /// Beacon API endpoint URL. #[clap(long, env)] beacon_api_url: Url, - /// L2 RPC node endpoint. - #[clap(long, env)] - l2_rpc_url: Url, - - /// Target's contract address on L2 - #[clap(long, env)] - counter_address: Address, - /// l1_cross_domain_messenger_address's contract address on L1 #[clap(long, env)] - l1_cross_domain_messenger_address: Address, + cross_domain_messenger_address: Address, - /// l2_cross_domain_messenger_address's contract address on L2 - #[clap(long, env)] - l2_cross_domain_messenger_address: Address, + /// Hash of the IL1CrossDomainMessenger::sendMessage() transaction + #[clap(long)] + tx_hash: TxHash, } +alloy::sol!("../../contracts/src/IL2CrossDomainMessenger.sol"); + #[tokio::main] async fn main() -> Result<()> { - // Initialize tracing. In order to view logs, run `RUST_LOG=info cargo run` tracing_subscriber::fmt() .with_env_filter(EnvFilter::from_default_env()) .init(); @@ -87,42 +61,29 @@ async fn main() -> Result<()> { dotenvy::dotenv()?; let args = Args::try_parse()?; - // Create an alloy provider for that private key and URL. - let wallet = EthereumWallet::from(args.l1_wallet_private_key); - let l1_provider = ProviderBuilder::new() - .with_recommended_fillers() - .wallet(wallet) - .on_http(args.l1_rpc_url); - - let wallet = EthereumWallet::from(args.l2_wallet_private_key); - let l2_provider = ProviderBuilder::new() - .with_recommended_fillers() - .wallet(wallet) - .on_http(args.l2_rpc_url); - - // Instantiate all the contracts we want to call. - let l1_messenger_contract = IL1CrossDomainMessengerService::new( - args.l1_cross_domain_messenger_address, - l1_provider.clone(), - ); - let l2_messenger_contract = IL2CrossDomainMessengerService::new( - args.l2_cross_domain_messenger_address, - l2_provider.clone(), - ); - - // Prepare the message to be passed from L1 to L2 - let target = args.counter_address; - let data = ICounter::incrementCall {}.abi_encode(); - - // Send a transaction calling IL1CrossDomainMessenger.sendMessage - let (message, message_block_number) = l1_messenger_contract - .send_message(target, data.into()) - .await?; + let provider = ProviderBuilder::new().on_http(args.rpc_url); + + let receipt = provider + .get_transaction_receipt(args.tx_hash) + .await? + .context("tx pending or unknown")?; + ensure!(receipt.status(), "tx failed"); + let message_block_number = receipt.block_number.context("tx pending")?; + let log = receipt + .inner + .logs() + .iter() + .filter(|log| log.address() == args.cross_domain_messenger_address) + .find_map(|log| { + log.log_decode::() + .ok() + }) + .context("tx invalid")?; + let message: Message = log.inner.data.into(); - // Run Steel: // Create an EVM environment from that provider and a block number. let mut env = EthEvmEnv::builder() - .provider(l1_provider.clone()) + .provider(provider) .block_number(message_block_number) .build() .await?; @@ -131,17 +92,17 @@ async fn main() -> Result<()> { digest: message.digest(), }; // Preflight the call to prepare the input for the guest. - let mut contract = Contract::preflight(args.l1_cross_domain_messenger_address, &mut env); + let mut contract = Contract::preflight(args.cross_domain_messenger_address, &mut env); let success = contract.call_builder(&call).call().await?._0; - ensure!(success, "message {} not found", call.digest); + assert!(success, "message {} not found", call.digest); + // Finally, construct the input for the guest. let evm_input = env.into_beacon_input(args.beacon_api_url).await?; let cross_domain_messenger_input = CrossDomainMessengerInput { - l1_cross_domain_messenger: args.l1_cross_domain_messenger_address, + l1_cross_domain_messenger: args.cross_domain_messenger_address, message, }; - println!("Creating proof for the constructed input..."); let prove_info = task::spawn_blocking(move || { let env = ExecutorEnv::builder() .write(&evm_input)? @@ -156,20 +117,16 @@ async fn main() -> Result<()> { ) }) .await??; - println!( - "Proving finished in {} cycles", - prove_info.stats.total_cycles - ); let receipt = prove_info.receipt; // Encode the groth16 seal with the selector. let seal = encode_seal(&receipt)?; - // Call the increment function of the contract and wait for confirmation. - let msg_hash = l2_messenger_contract - .relay_message(receipt.journal.bytes.into(), seal.into()) - .await?; - println!("Message relayed {:?}", msg_hash); + let call = IL2CrossDomainMessenger::relayMessageCall { + journal: receipt.journal.bytes.into(), + seal: seal.into(), + }; + println!("{} {}", call.journal, call.seal); Ok(()) } diff --git a/examples/message-passing/crates/core/Cargo.toml b/examples/message-passing/crates/core/Cargo.toml index ee4f4e07..47599dea 100644 --- a/examples/message-passing/crates/core/Cargo.toml +++ b/examples/message-passing/crates/core/Cargo.toml @@ -8,10 +8,3 @@ alloy-primitives = { workspace = true } alloy-sol-types = { workspace = true } risc0-steel = { workspace = true } serde = { workspace = true } - -[target.'cfg(not(target_os = "zkvm"))'.dependencies] -alloy = { workspace = true } -anyhow = { workspace = true } -tokio = { workspace = true } -tracing = { workspace = true } -tracing-subscriber = { workspace = true } \ No newline at end of file diff --git a/examples/message-passing/crates/core/src/contracts.rs b/examples/message-passing/crates/core/src/contracts.rs deleted file mode 100644 index f47ae20e..00000000 --- a/examples/message-passing/crates/core/src/contracts.rs +++ /dev/null @@ -1,144 +0,0 @@ -// 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. - -use std::time::Duration; - -use alloy::{ - network::Ethereum, providers::Provider, rpc::types::TransactionReceipt, sol, - transports::Transport, -}; -use alloy_primitives::{Address, Bytes, B256}; -use alloy_sol_types::SolEvent; -use anyhow::{bail, ensure, Result}; - -use crate::{ - contracts::{ - IL1CrossDomainMessenger::IL1CrossDomainMessengerInstance, - IL2CrossDomainMessenger::IL2CrossDomainMessengerInstance, - }, - Message, -}; - -sol!( - #[sol(rpc, all_derives)] - "../../contracts/src/IL1CrossDomainMessenger.sol" -); - -sol!( - #[sol(rpc, all_derives)] - "../../contracts/src/IL2CrossDomainMessenger.sol" -); - -#[derive(Clone)] -pub struct IL1CrossDomainMessengerService { - instance: IL1CrossDomainMessengerInstance, -} - -impl IL1CrossDomainMessengerService -where - T: Transport + Clone, - P: Provider + 'static, -{ - pub const TX_TIMEOUT: Duration = Duration::from_secs(30); - - pub fn new(address: Address, provider: P) -> Self { - let instance = IL1CrossDomainMessenger::new(address, provider); - - IL1CrossDomainMessengerService { instance } - } - - pub fn instance(&self) -> &IL1CrossDomainMessengerInstance { - &self.instance - } - - pub async fn contains(&self, digest: B256) -> Result { - tracing::debug!("Calling contains({:?})", digest); - let call = self.instance.contains(digest); - let result = call.call().await?; - Ok(result._0) - } - - pub async fn send_message(&self, target: Address, data: Bytes) -> Result<(Message, u64)> { - tracing::debug!("Calling sendMessage({:?},{:?})", target, data); - let call = self.instance.sendMessage(target, data); - let pending_tx = call.send().await?; - tracing::debug!("Broadcasting tx {}", pending_tx.tx_hash()); - let receipt = pending_tx - .with_timeout(Some(Self::TX_TIMEOUT)) - .get_receipt() - .await?; - - // Process the transaction result - ensure!(receipt.status(), "transaction failed"); - let message_block_number = receipt.block_number.unwrap(); - let event: IL1CrossDomainMessenger::SentMessage = into_event(receipt)?; - println!("Message submitted on L1: {:?}", event); - let message = Message { - target: event.target, - sender: event.sender, - data: event.data, - nonce: event.messageNonce, - }; - - Ok((message, message_block_number)) - } -} - -#[derive(Clone)] -pub struct IL2CrossDomainMessengerService { - instance: IL2CrossDomainMessengerInstance, -} - -impl IL2CrossDomainMessengerService -where - T: Transport + Clone, - P: Provider + 'static, -{ - pub const TX_TIMEOUT: Duration = Duration::from_secs(30); - - pub fn new(address: Address, provider: P) -> Self { - let instance = IL2CrossDomainMessenger::new(address, provider); - - IL2CrossDomainMessengerService { instance } - } - - pub fn instance(&self) -> &IL2CrossDomainMessengerInstance { - &self.instance - } - - pub async fn relay_message(&self, journal: Bytes, seal: Bytes) -> Result { - tracing::debug!("Calling relayMessage({:?},{:?})", journal, seal); - let call = self.instance.relayMessage(journal, seal); - let pending_tx = call.send().await?; - tracing::debug!("Broadcasting tx {}", pending_tx.tx_hash()); - let receipt = pending_tx - .with_timeout(Some(Self::TX_TIMEOUT)) - .get_receipt() - .await?; - - let event = into_event::(receipt)?; - Ok(event.msgHash) - } -} - -fn into_event(receipt: TransactionReceipt) -> Result { - ensure!(receipt.status(), "transaction failed"); - for log in receipt.inner.logs() { - match log.log_decode::() { - Ok(decoded_log) => return Ok(decoded_log.inner.data), - Err(_) => {} - } - } - bail!("invalid events emitted") -} diff --git a/examples/message-passing/crates/core/src/lib.rs b/examples/message-passing/crates/core/src/lib.rs index f3f5677c..1e1e1fac 100644 --- a/examples/message-passing/crates/core/src/lib.rs +++ b/examples/message-passing/crates/core/src/lib.rs @@ -17,15 +17,7 @@ use alloy_sol_types::{sol, SolStruct}; use risc0_steel::Commitment; use serde::{Deserialize, Serialize}; -#[cfg(not(target_os = "zkvm"))] -pub mod contracts; - -sol! { - interface IL1CrossDomainMessenger { - /// Returns whether the digest of the message has been committed to be relayed. - function contains(bytes32 digest) external view returns (bool); - } -} +sol!("../../contracts/src/IL1CrossDomainMessenger.sol"); sol! { /// A Message to be relayed. @@ -49,7 +41,18 @@ sol! { impl Message { #[inline] pub fn digest(&self) -> B256 { - return self.eip712_hash_struct(); + self.eip712_hash_struct() + } +} + +impl From for Message { + fn from(event: IL1CrossDomainMessenger::SentMessage) -> Self { + Self { + target: event.target, + sender: event.sender, + data: event.data, + nonce: event.messageNonce, + } } } diff --git a/examples/message-passing/crates/methods/Cargo.toml b/examples/message-passing/crates/methods/Cargo.toml index c3a3c24e..8ef4fc97 100644 --- a/examples/message-passing/crates/methods/Cargo.toml +++ b/examples/message-passing/crates/methods/Cargo.toml @@ -7,7 +7,6 @@ edition = { workspace = true } methods = ["guest"] [build-dependencies] -cross-domain-messenger-core = { workspace = true } hex = { workspace = true } risc0-build = { workspace = true } risc0-build-ethereum = { workspace = true } diff --git a/examples/message-passing/test-local-devnet.sh b/examples/message-passing/test-local-devnet.sh index 993ad721..8680d465 100755 --- a/examples/message-passing/test-local-devnet.sh +++ b/examples/message-passing/test-local-devnet.sh @@ -1,4 +1,7 @@ #!/bin/bash +set -e -o pipefail + +. .env forge script --broadcast Deploy @@ -6,13 +9,33 @@ L1_CROSS_DOMAIN_MESSENGER_ADDRESS=$(jq -re '[.deployments[].transactions[] | sel L2_CROSS_DOMAIN_MESSENGER_ADDRESS=$(jq -re '[.deployments[].transactions[] | select(.contractName == "L2CrossDomainMessenger")][0] | .contractAddress' ./broadcast/multi/Deploy.s.sol-latest/run.json) COUNTER_ADDRESS=$(jq -re '.deployments[].transactions[] | select(.contractName == "Counter") | .contractAddress' ./broadcast/multi/Deploy.s.sol-latest/run.json) -RUST_LOG=info cargo run -- \ - --counter-address=$COUNTER_ADDRESS \ - --l1-cross-domain-messenger-address=$L1_CROSS_DOMAIN_MESSENGER_ADDRESS \ - --l2-cross-domain-messenger-address=$L2_CROSS_DOMAIN_MESSENGER_ADDRESS +CALL_DATA=$(cast cd 'increment()') + +echo "L1CrossDomainMessenger.sendMessage..." +SEND_MESSAGE_RECEIPT=$(cast send --rpc-url $L1_RPC_URL --private-key $L1_WALLET_PRIVATE_KEY --json $L1_CROSS_DOMAIN_MESSENGER_ADDRESS 'sendMessage(address target, bytes calldata data)' $COUNTER_ADDRESS $CALL_DATA) +SEND_MESSAGE_TX_HASH=$(echo $SEND_MESSAGE_RECEIPT | jq -re '.transactionHash') +cast run --rpc-url $L1_RPC_URL $SEND_MESSAGE_TX_HASH + +START_TIME=$(date +%s) + +RELAY_MESSAGE_ARGS="$(RUST_LOG=warn cargo run -- \ + --rpc-url ${L1_RPC_URL:?} \ + --beacon-api-url ${BEACON_API_URL:?} \ + --cross-domain-messenger-address=${L1_CROSS_DOMAIN_MESSENGER_ADDRESS:?} \ + --tx-hash ${SEND_MESSAGE_TX_HASH:?})" + +EXECUTION_TIME=$((`date +%s` - START_TIME)) +if [ $EXECUTION_TIME -lt 12 ]; then + sleep $((12 - EXECUTION_TIME)) +fi + +echo "L2CrossDomainMessenger.relayMessage..." +RELAY_MESSAGE_RECEIPT=$(cast send --rpc-url $L2_RPC_URL --private-key $L2_WALLET_PRIVATE_KEY --json $L2_CROSS_DOMAIN_MESSENGER_ADDRESS 'relayMessage(bytes,bytes)' $RELAY_MESSAGE_ARGS) +RELAY_MESSAGE_TX_HASH=$(echo $RELAY_MESSAGE_RECEIPT | jq -re '.transactionHash') +cast run --rpc-url $L2_RPC_URL $RELAY_MESSAGE_TX_HASH echo "Verifying state..." -COUNTER_VALUE=$(cast call --rpc-url http://localhost:9545 ${COUNTER_ADDRESS:?} 'get()(uint256)') +COUNTER_VALUE=$(cast call --rpc-url $L2_RPC_URL ${COUNTER_ADDRESS:?} 'get()(uint256)') if [ "$COUNTER_VALUE" != "1" ]; then echo "Counter value is not 1 as expected, but $COUNTER_VALUE." exit 1 diff --git a/steel/src/beacon.rs b/steel/src/beacon.rs index e9a24c16..830d6ae0 100644 --- a/steel/src/beacon.rs +++ b/steel/src/beacon.rs @@ -82,16 +82,13 @@ impl MerkleProof { #[cfg(feature = "host")] mod host { use super::{BeaconInput, MerkleProof}; - use crate::{ - block::BlockInput, - ethereum::EthBlockHeader, - host::{db::AlloyDb, HostEvmEnv}, - }; + use crate::{block::BlockInput, ethereum::EthBlockHeader, host::{db::AlloyDb, HostEvmEnv}, EvmBlockHeader}; use alloy::{network::Ethereum, providers::Provider, transports::Transport}; use alloy_primitives::Sealable; use anyhow::{bail, ensure, Context}; use beacon_api_client::{minimal::Client as BeaconClient, BeaconHeaderSummary, BlockId}; use ethereum_consensus::{ssz::prelude::*, types::SignedBeaconBlock, Fork}; + use log::info; use proofs::{Proof, ProofAndWitness}; use url::Url; @@ -106,6 +103,7 @@ mod host { P: Provider, { let block_hash = env.header().hash_slow(); + let block_ts = env.header().timestamp(); let parent_beacon_block_root = env .header() .inner() @@ -159,6 +157,8 @@ mod host { "proof derived from API does not verify", ); + info!("Commitment to beacon block root {} at {}", beacon_root, block_ts); + Ok(BeaconInput { input, proof }) } } diff --git a/steel/src/block.rs b/steel/src/block.rs index fe7ad956..ab0ebf47 100644 --- a/steel/src/block.rs +++ b/steel/src/block.rs @@ -84,7 +84,7 @@ pub mod host { transports::Transport, }; use anyhow::{anyhow, ensure}; - use log::debug; + use log::{debug, info}; impl BlockInput { /// Derives the verifiable input from a [HostEvmEnv]. @@ -132,6 +132,8 @@ pub mod host { debug!("contracts: {}", contracts.len()); debug!("ancestor blocks: {}", ancestors.len()); + info!("Commitment to block hash {} at {}", env.header.seal(), env.header.number()); + let input = BlockInput { header: env.header.into_inner(), state_trie, diff --git a/steel/src/lib.rs b/steel/src/lib.rs index 7afb8d57..6a2ac7a3 100644 --- a/steel/src/lib.rs +++ b/steel/src/lib.rs @@ -76,8 +76,6 @@ impl EvmEnv { pub fn new(db: D, header: Sealed) -> Self { let cfg_env = CfgEnvWithHandlerCfg::new_with_spec_id(Default::default(), SpecId::LATEST); let commitment = Commitment::from_header(&header); - #[cfg(feature = "host")] - log::info!("Commitment to block {}", commitment.blockDigest); Self { db: Some(db), From de9f4dee037cd53f5ad9e833869c85bb006bf313 Mon Sep 17 00:00:00 2001 From: Wolfgang Welz Date: Mon, 9 Sep 2024 12:36:56 +0200 Subject: [PATCH 38/44] fix script --- examples/message-passing/Cargo.toml | 1 + .../message-passing/crates/apps/Cargo.toml | 4 +- .../crates/apps/src/bin/publisher.rs | 81 ++++++++++++------- .../message-passing/crates/core/src/lib.rs | 5 +- examples/message-passing/test-local-devnet.sh | 28 ++++--- 5 files changed, 73 insertions(+), 46 deletions(-) diff --git a/examples/message-passing/Cargo.toml b/examples/message-passing/Cargo.toml index 30add462..5b165ead 100644 --- a/examples/message-passing/Cargo.toml +++ b/examples/message-passing/Cargo.toml @@ -32,6 +32,7 @@ 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"] } diff --git a/examples/message-passing/crates/apps/Cargo.toml b/examples/message-passing/crates/apps/Cargo.toml index fa4e2efd..06c38c31 100644 --- a/examples/message-passing/crates/apps/Cargo.toml +++ b/examples/message-passing/crates/apps/Cargo.toml @@ -10,10 +10,12 @@ anyhow = { workspace = true } clap = { workspace = true, features = ["derive", "env"] } cross-domain-messenger-core = { workspace = true } cross-domain-messenger-methods = { workspace = true } +dotenvy = { workspace = true } +log = { workspace = true } risc0-ethereum-contracts = { workspace = true } risc0-steel = { workspace = true, features = ["host"] } risc0-zkvm = { workspace = true, features = ["client"] } +serde_json = { workspace = true } tokio = { workspace = true } tracing-subscriber = { workspace = true } url = { workspace = true } -dotenvy = { workspace = true } \ No newline at end of file diff --git a/examples/message-passing/crates/apps/src/bin/publisher.rs b/examples/message-passing/crates/apps/src/bin/publisher.rs index 7b91e232..4442a260 100644 --- a/examples/message-passing/crates/apps/src/bin/publisher.rs +++ b/examples/message-passing/crates/apps/src/bin/publisher.rs @@ -16,8 +16,9 @@ // to the Bonsai proving service and publish the received proofs directly // to your deployed app contract. +use alloy::sol_types::SolCall; use alloy::providers::{Provider, ProviderBuilder}; -use alloy_primitives::{Address, TxHash}; +use alloy_primitives::{Address, Bytes, TxHash}; use anyhow::{ensure, Context, Result}; use clap::Parser; use cross_domain_messenger_core::{CrossDomainMessengerInput, IL1CrossDomainMessenger, Message}; @@ -25,32 +26,35 @@ use cross_domain_messenger_methods::CROSS_DOMAIN_MESSENGER_ELF; use risc0_ethereum_contracts::encode_seal; use risc0_steel::{ethereum::EthEvmEnv, Contract}; use risc0_zkvm::{default_prover, ExecutorEnv, ProverOpts, VerifierContext}; +use std::fs::File; +use std::path::PathBuf; use tokio::task; use tracing_subscriber::EnvFilter; use url::Url; -/// Arguments of the publisher CLI. +/// Simple program to create the Risc Zero proof that a message to be passed to L2 was sent on L1 #[derive(Parser)] -#[clap(author, version, about, long_about = None)] struct Args { - /// RPC node endpoint. + /// L1 RPC node endpoint #[clap(long, env)] - rpc_url: Url, + l1_rpc_url: Url, - /// Beacon API endpoint URL. + /// Beacon API endpoint URL #[clap(long, env)] beacon_api_url: Url, - /// l1_cross_domain_messenger_address's contract address on L1 + /// L1CrossDomainMessenger's contract address on L1 #[clap(long, env)] - cross_domain_messenger_address: Address, + l1_cross_domain_messenger_address: Address, - /// Hash of the IL1CrossDomainMessenger::sendMessage() transaction + /// Hash of the transaction calling 'L1CrossDomainMessenger.sendMessage(...)' #[clap(long)] tx_hash: TxHash, -} -alloy::sol!("../../contracts/src/IL2CrossDomainMessenger.sol"); + /// Path of the output JSON file with the generated proof + #[clap(long, short, default_value = "proof.json")] + output: PathBuf, +} #[tokio::main] async fn main() -> Result<()> { @@ -61,8 +65,9 @@ async fn main() -> Result<()> { dotenvy::dotenv()?; let args = Args::try_parse()?; - let provider = ProviderBuilder::new().on_http(args.rpc_url); + let provider = ProviderBuilder::new().on_http(args.l1_rpc_url); + // Try to extract the sent message for the given tx hash. let receipt = provider .get_transaction_receipt(args.tx_hash) .await? @@ -73,12 +78,13 @@ async fn main() -> Result<()> { .inner .logs() .iter() - .filter(|log| log.address() == args.cross_domain_messenger_address) + .filter(|log| log.address() == args.l1_cross_domain_messenger_address) .find_map(|log| { log.log_decode::() .ok() }) .context("tx invalid")?; + log::info!("tx emitted {:?}", log.data()); let message: Message = log.inner.data.into(); // Create an EVM environment from that provider and a block number. @@ -86,20 +92,31 @@ async fn main() -> Result<()> { .provider(provider) .block_number(message_block_number) .build() - .await?; - // Prepare the function call to be called inside steal - let call = IL1CrossDomainMessenger::containsCall { - digest: message.digest(), - }; + .await + .context("failed to create steel env")?; // Preflight the call to prepare the input for the guest. - let mut contract = Contract::preflight(args.cross_domain_messenger_address, &mut env); - let success = contract.call_builder(&call).call().await?._0; - assert!(success, "message {} not found", call.digest); + let mut contract = Contract::preflight(args.l1_cross_domain_messenger_address, &mut env); + let success = contract + .call_builder(&IL1CrossDomainMessenger::containsCall { + digest: message.digest(), + }) + .call() + .await + .context("steel preflight failed")? + ._0; + assert!( + success, + "{} returned 'false'", + IL1CrossDomainMessenger::containsCall::SIGNATURE + ); // Finally, construct the input for the guest. - let evm_input = env.into_beacon_input(args.beacon_api_url).await?; + let evm_input = env + .into_beacon_input(args.beacon_api_url) + .await + .context("failed to crate steel input")?; let cross_domain_messenger_input = CrossDomainMessengerInput { - l1_cross_domain_messenger: args.cross_domain_messenger_address, + l1_cross_domain_messenger: args.l1_cross_domain_messenger_address, message, }; @@ -116,17 +133,19 @@ async fn main() -> Result<()> { &ProverOpts::groth16(), ) }) - .await??; + .await? + .context("failed to create proof")?; let receipt = prove_info.receipt; - // Encode the groth16 seal with the selector. - let seal = encode_seal(&receipt)?; + let seal = encode_seal(&receipt).context("invalid receipt")?; + let proof = serde_json::json!({ + "seal": Bytes::from(seal), + "journal": Bytes::from(receipt.journal.bytes), + }); - let call = IL2CrossDomainMessenger::relayMessageCall { - journal: receipt.journal.bytes.into(), - seal: seal.into(), - }; - println!("{} {}", call.journal, call.seal); + // Create a file and write the JSON string to it. + let file = File::create(args.output).context("failed to create output file")?; + serde_json::to_writer_pretty(file, &proof).context("failed to write output")?; Ok(()) } diff --git a/examples/message-passing/crates/core/src/lib.rs b/examples/message-passing/crates/core/src/lib.rs index 1e1e1fac..ad5b8b4d 100644 --- a/examples/message-passing/crates/core/src/lib.rs +++ b/examples/message-passing/crates/core/src/lib.rs @@ -17,7 +17,10 @@ use alloy_sol_types::{sol, SolStruct}; use risc0_steel::Commitment; use serde::{Deserialize, Serialize}; -sol!("../../contracts/src/IL1CrossDomainMessenger.sol"); +sol!( + #[sol(all_derives)] + "../../contracts/src/IL1CrossDomainMessenger.sol" +); sol! { /// A Message to be relayed. diff --git a/examples/message-passing/test-local-devnet.sh b/examples/message-passing/test-local-devnet.sh index 8680d465..486a2fde 100755 --- a/examples/message-passing/test-local-devnet.sh +++ b/examples/message-passing/test-local-devnet.sh @@ -12,30 +12,32 @@ COUNTER_ADDRESS=$(jq -re '.deployments[].transactions[] | select(.contractName = CALL_DATA=$(cast cd 'increment()') echo "L1CrossDomainMessenger.sendMessage..." -SEND_MESSAGE_RECEIPT=$(cast send --rpc-url $L1_RPC_URL --private-key $L1_WALLET_PRIVATE_KEY --json $L1_CROSS_DOMAIN_MESSENGER_ADDRESS 'sendMessage(address target, bytes calldata data)' $COUNTER_ADDRESS $CALL_DATA) -SEND_MESSAGE_TX_HASH=$(echo $SEND_MESSAGE_RECEIPT | jq -re '.transactionHash') -cast run --rpc-url $L1_RPC_URL $SEND_MESSAGE_TX_HASH +SEND_MESSAGE_RECEIPT=$(cast send --rpc-url "${L1_RPC_URL:?}" --private-key "${L1_WALLET_PRIVATE_KEY:?}" --json "$L1_CROSS_DOMAIN_MESSENGER_ADDRESS" 'sendMessage(address target, bytes calldata data)' "$COUNTER_ADDRESS" "$CALL_DATA") +SEND_MESSAGE_TX_HASH=$(echo "$SEND_MESSAGE_RECEIPT" | jq -re '.transactionHash') +cast run --rpc-url "$L1_RPC_URL" "$SEND_MESSAGE_TX_HASH" START_TIME=$(date +%s) -RELAY_MESSAGE_ARGS="$(RUST_LOG=warn cargo run -- \ - --rpc-url ${L1_RPC_URL:?} \ - --beacon-api-url ${BEACON_API_URL:?} \ - --cross-domain-messenger-address=${L1_CROSS_DOMAIN_MESSENGER_ADDRESS:?} \ - --tx-hash ${SEND_MESSAGE_TX_HASH:?})" +RUST_LOG=info,risc0_steel=debug cargo run -- \ + --l1-rpc-url "${L1_RPC_URL:?}" \ + --beacon-api-url "${BEACON_API_URL:?}" \ + --l1-cross-domain-messenger-address="$L1_CROSS_DOMAIN_MESSENGER_ADDRESS" \ + --tx-hash "$SEND_MESSAGE_TX_HASH" +RELAY_MESSAGE_ARGS=$(jq -re '[.journal, .seal] | @tsv | sub("\t";" ";"g")' proof.json) -EXECUTION_TIME=$((`date +%s` - START_TIME)) +EXECUTION_TIME=$(($(date +%s) - START_TIME)) if [ $EXECUTION_TIME -lt 12 ]; then + echo "Waiting for the next L1 block..." sleep $((12 - EXECUTION_TIME)) fi echo "L2CrossDomainMessenger.relayMessage..." -RELAY_MESSAGE_RECEIPT=$(cast send --rpc-url $L2_RPC_URL --private-key $L2_WALLET_PRIVATE_KEY --json $L2_CROSS_DOMAIN_MESSENGER_ADDRESS 'relayMessage(bytes,bytes)' $RELAY_MESSAGE_ARGS) -RELAY_MESSAGE_TX_HASH=$(echo $RELAY_MESSAGE_RECEIPT | jq -re '.transactionHash') -cast run --rpc-url $L2_RPC_URL $RELAY_MESSAGE_TX_HASH +RELAY_MESSAGE_RECEIPT=$(cast send --rpc-url "${L2_RPC_URL:?}" --private-key "${L2_WALLET_PRIVATE_KEY:?}" --json "${L2_CROSS_DOMAIN_MESSENGER_ADDRESS:?}" 'relayMessage(bytes,bytes)' $RELAY_MESSAGE_ARGS) +RELAY_MESSAGE_TX_HASH=$(echo "$RELAY_MESSAGE_RECEIPT" | jq -re '.transactionHash') +cast run --rpc-url "${L2_RPC_URL:?}" "$RELAY_MESSAGE_TX_HASH" echo "Verifying state..." -COUNTER_VALUE=$(cast call --rpc-url $L2_RPC_URL ${COUNTER_ADDRESS:?} 'get()(uint256)') +COUNTER_VALUE=$(cast call --rpc-url "${L2_RPC_URL:?}" "${COUNTER_ADDRESS:?}" 'get()(uint256)') if [ "$COUNTER_VALUE" != "1" ]; then echo "Counter value is not 1 as expected, but $COUNTER_VALUE." exit 1 From 1f1d0664c8629e82cb23e53167c0e7470022c841 Mon Sep 17 00:00:00 2001 From: Wolfgang Welz Date: Mon, 9 Sep 2024 17:45:44 +0200 Subject: [PATCH 39/44] enable bookmarking --- examples/message-passing/.gitignore | 1 + examples/message-passing/README.md | 26 ++-- .../contracts/script/Deploy.s.sol | 9 +- .../contracts/src/Bookmark.sol | 45 +++++++ .../contracts/src/IBookmark.sol | 29 +++++ .../contracts/src/IL1Block.sol | 26 ++++ .../contracts/src/IL2CrossDomainMessenger.sol | 4 +- .../contracts/src/L2CrossDomainMessenger.sol | 16 ++- .../message-passing/contracts/test/E2E.t.sol | 36 +++--- .../contracts/test/L1BlockMock.sol | 29 +++++ .../apps/src/bin/{publisher.rs => prover.rs} | 116 ++++++++++++++---- .../{test-local-devnet.sh => e2e-test.sh} | 32 +++-- steel/src/beacon.rs | 14 ++- steel/src/block.rs | 6 +- 14 files changed, 306 insertions(+), 83 deletions(-) create mode 100644 examples/message-passing/contracts/src/Bookmark.sol create mode 100644 examples/message-passing/contracts/src/IBookmark.sol create mode 100644 examples/message-passing/contracts/src/IL1Block.sol create mode 100644 examples/message-passing/contracts/test/L1BlockMock.sol rename examples/message-passing/crates/apps/src/bin/{publisher.rs => prover.rs} (58%) rename examples/message-passing/{test-local-devnet.sh => e2e-test.sh} (56%) diff --git a/examples/message-passing/.gitignore b/examples/message-passing/.gitignore index 7c2e0811..fa96e95a 100644 --- a/examples/message-passing/.gitignore +++ b/examples/message-passing/.gitignore @@ -24,3 +24,4 @@ target/ # Misc .DS_Store .idea +proof.json diff --git a/examples/message-passing/README.md b/examples/message-passing/README.md index 23bc9cbc..987810e1 100644 --- a/examples/message-passing/README.md +++ b/examples/message-passing/README.md @@ -8,24 +8,26 @@ It also showcases a *bookmarking block commitment validation* technique, by savi ## Key Steps 1. **Send Message from L1:**
-Call `L1CrossDomainMessenger.sendMessage` with the message you want to relay to L2. -2. **Bookmark the block hash**
On L2, save the L1 block hash to the contract state before generating the Steel proof. -3. **Generate Steel Proof:**
Generate a Steel proof verifying the message's inclusion in the L1 state, targeting the bookmarked block. -4. **Relay Message on L2:**
On L2, use `L2CrossDomainMessenger.relayMessage` with the message and Steel proof. Upon successful verification, the message will be relayed to the target contract, ensuring its validity as an L1 message. +Call `L1CrossDomainMessenger::sendMessage(address,bytes)` with the target and the data of message you want to relay to L2. +2. **Bookmark the block hash**
+Call `L2CrossDomainMessenger::bookmarkL1Block()` to save the current L1 block hash to the contract state before generating the Steel proof. +3. **Generate Steel Proof:**
+Generate a Steel proof verifying the message's inclusion in the L1 state, targeting the bookmarked block. +4. **Relay Message on L2:**
+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 -We deployed this example on Sepolia and OP-Sepolia. You can export: +- 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 -L1_WALLET_PRIVATE_KEY="YOUR_SEPOLIA_WALLET_PRIVATE_KEY" -L2_WALLET_PRIVATE_KEY="YOUR_OP_SEPOLIA_WALLET_PRIVATE_KEY" +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. -and finally run - -```bash -RISC0_DEV_MODE=1 RUST_LOG=info cargo run -``` \ No newline at end of file +The file `e2e-test.sh` contains an examples that performs all those steps in one script. diff --git a/examples/message-passing/contracts/script/Deploy.s.sol b/examples/message-passing/contracts/script/Deploy.s.sol index 1f8510ba..137ac1fc 100644 --- a/examples/message-passing/contracts/script/Deploy.s.sol +++ b/examples/message-passing/contracts/script/Deploy.s.sol @@ -20,12 +20,16 @@ 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"); @@ -50,8 +54,9 @@ contract Deploy is Script, RiscZeroCheats { IRiscZeroVerifier verifier = deployRiscZeroVerifier(); - L2CrossDomainMessenger l2CrossDomainMessenger = - new L2CrossDomainMessenger(verifier, ImageID.CROSS_DOMAIN_MESSENGER_ID, address(l1CrossDomainMessenger)); + 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); diff --git a/examples/message-passing/contracts/src/Bookmark.sol b/examples/message-passing/contracts/src/Bookmark.sol new file mode 100644 index 00000000..c09beb06 --- /dev/null +++ b/examples/message-passing/contracts/src/Bookmark.sol @@ -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]; + } +} diff --git a/examples/message-passing/contracts/src/IBookmark.sol b/examples/message-passing/contracts/src/IBookmark.sol new file mode 100644 index 00000000..6e31f28d --- /dev/null +++ b/examples/message-passing/contracts/src/IBookmark.sol @@ -0,0 +1,29 @@ +// 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; + +/// @notice Interface to bookmark L1 blocks. +interface IBookmark { + /// @notice A new L1 block has been bookmarked. + event BookmarkedL1Block(uint64 number, bytes32 hash); + + /// @notice Bookmarks the current L1 block. + function bookmarkL1Block() external returns (uint64); + + /// @notice Returns the bookmarked hash of the block with the given number. + function getBookmark(uint64 blockNumber) external view returns (bytes32); +} diff --git a/examples/message-passing/contracts/src/IL1Block.sol b/examples/message-passing/contracts/src/IL1Block.sol new file mode 100644 index 00000000..33bd8a91 --- /dev/null +++ b/examples/message-passing/contracts/src/IL1Block.sol @@ -0,0 +1,26 @@ +// 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; + +/// @notice The L1Block predeploy gives users access to information about the last known L1 block. +interface IL1Block { + /// @notice The latest L1 block number known by the L2 system. + function number() external view returns (uint64); + + /// @notice The latest L1 blockhash. + function hash() external view returns (bytes32); +} diff --git a/examples/message-passing/contracts/src/IL2CrossDomainMessenger.sol b/examples/message-passing/contracts/src/IL2CrossDomainMessenger.sol index fee81dee..22a00c23 100644 --- a/examples/message-passing/contracts/src/IL2CrossDomainMessenger.sol +++ b/examples/message-passing/contracts/src/IL2CrossDomainMessenger.sol @@ -16,7 +16,9 @@ pragma solidity ^0.8.20; -interface IL2CrossDomainMessenger { +import {IBookmark} from "./IBookmark.sol"; + +interface IL2CrossDomainMessenger is IBookmark { /// @notice Emitted whenever a message is successfully relayed on this chain. /// @param msgHash Hash of the message that was relayed. event RelayedMessage(bytes32 indexed msgHash); diff --git a/examples/message-passing/contracts/src/L2CrossDomainMessenger.sol b/examples/message-passing/contracts/src/L2CrossDomainMessenger.sol index 053c100c..0836f3a1 100644 --- a/examples/message-passing/contracts/src/L2CrossDomainMessenger.sol +++ b/examples/message-passing/contracts/src/L2CrossDomainMessenger.sol @@ -17,14 +17,18 @@ pragma solidity ^0.8.20; import {Address} from "openzeppelin/contracts/utils/Address.sol"; +import {SafeCast} from "openzeppelin/contracts/utils/math/SafeCast.sol"; import {IRiscZeroVerifier} from "risc0/IRiscZeroVerifier.sol"; import {Steel} from "risc0/steel/Steel.sol"; import {IL2CrossDomainMessenger} from "./IL2CrossDomainMessenger.sol"; +import {IL1Block} from "./IL1Block.sol"; +import {Bookmark} from "./Bookmark.sol"; import {Journal, Message, Digest} from "./Structs.sol"; /// @notice L1Bridging verifier contract for RISC Zero receipts of execution. -contract L2CrossDomainMessenger is IL2CrossDomainMessenger { +contract L2CrossDomainMessenger is IL2CrossDomainMessenger, Bookmark { using Address for address; + using SafeCast for uint256; using Digest for Journal; /// @notice Value used for the L1 sender storage slot before an actual sender is set. This value is non-zero to @@ -47,7 +51,9 @@ contract L2CrossDomainMessenger is IL2CrossDomainMessenger { // it has successfully been relayed, and can therefore not be relayed again. mapping(bytes32 => bool) private relayedMessages; - constructor(IRiscZeroVerifier verifier, bytes32 imageId, address l1CrossDomainMessenger) { + constructor(IRiscZeroVerifier verifier, bytes32 imageId, address l1CrossDomainMessenger, IL1Block l1Block) + Bookmark(l1Block) + { VERIFIER = verifier; IMAGE_ID = imageId; L1_CROSS_DOMAIN_MESSENGER = l1CrossDomainMessenger; @@ -60,7 +66,7 @@ contract L2CrossDomainMessenger is IL2CrossDomainMessenger { Journal memory journal = abi.decode(journalData, (Journal)); require(journal.l1CrossDomainMessenger == L1_CROSS_DOMAIN_MESSENGER, "invalid l1CrossDomainMessenger"); - require(Steel.validateCommitment(journal.commitment), "commitment verification failed"); + require(validateCommitment(journal.commitment), "commitment verification failed"); relayVerifiedMessage(journal.message, journal.messageDigest); } @@ -71,6 +77,10 @@ contract L2CrossDomainMessenger is IL2CrossDomainMessenger { return xDomainMsgSender; } + function validateCommitment(Steel.Commitment memory commitment) internal view returns (bool) { + return commitment.blockDigest == Bookmark.blocks[commitment.blockID.toUint64()]; + } + function relayVerifiedMessage(Message memory message, bytes32 digest) internal { require(xDomainMsgSender == DEFAULT_L1_SENDER, "L2CrossDomainMessenger: reentrant call"); require(!relayedMessages[digest], "L2CrossDomainMessenger: message already relayed"); diff --git a/examples/message-passing/contracts/test/E2E.t.sol b/examples/message-passing/contracts/test/E2E.t.sol index 5750e0df..fd33d3af 100644 --- a/examples/message-passing/contracts/test/E2E.t.sol +++ b/examples/message-passing/contracts/test/E2E.t.sol @@ -23,41 +23,38 @@ import {RiscZeroMockVerifier} from "risc0/test/RiscZeroMockVerifier.sol"; import {Journal, Message, Digest} from "../src/Structs.sol"; import {IL1CrossDomainMessenger} from "../src/IL1CrossDomainMessenger.sol"; import {L1CrossDomainMessenger} from "../src/L1CrossDomainMessenger.sol"; +import {IL1Block} from "../src/IL1Block.sol"; import {Journal, L2CrossDomainMessenger} from "../src/L2CrossDomainMessenger.sol"; +import {L1BlockMock} from "./L1BlockMock.sol"; import {Counter} from "../src/Counter.sol"; -import {Steel, Beacon, Encoding} from "risc0/steel/Steel.sol"; +import {Steel} from "risc0/steel/Steel.sol"; contract E2ETest is Test { using Digest for Message; using Digest for Journal; - address internal constant BEACON_ROOTS_ADDRESS = 0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02; - bytes32 internal CROSS_DOMAIN_MESSENGER_IMAGE_ID = bytes32(uint256(0x03)); - bytes4 MOCK_SELECTOR = bytes4(0); - RiscZeroMockVerifier private verifier; L1CrossDomainMessenger private l1CrossDomainMessenger; L2CrossDomainMessenger private l2CrossDomainMessenger; + IL1Block private l1Block; Counter private counter; address private sender; + bytes32 internal CROSS_DOMAIN_MESSENGER_IMAGE_ID = bytes32(uint256(0x03)); + + bytes4 MOCK_SELECTOR = bytes4(0); + function setUp() public { sender = address(1); vm.startPrank(sender); l1CrossDomainMessenger = new L1CrossDomainMessenger(); verifier = new RiscZeroMockVerifier(MOCK_SELECTOR); - l2CrossDomainMessenger = - new L2CrossDomainMessenger(verifier, CROSS_DOMAIN_MESSENGER_IMAGE_ID, address(l1CrossDomainMessenger)); - counter = new Counter(l2CrossDomainMessenger, sender); - - // mock the beacon roots contract - vm.warp(60); - vm.mockCall( - BEACON_ROOTS_ADDRESS, - abi.encode(uint256(block.timestamp)), - abi.encode(keccak256(abi.encodePacked(block.timestamp))) + l1Block = new L1BlockMock(); + l2CrossDomainMessenger = new L2CrossDomainMessenger( + verifier, CROSS_DOMAIN_MESSENGER_IMAGE_ID, address(l1CrossDomainMessenger), l1Block ); + counter = new Counter(l2CrossDomainMessenger, address(0x0)); } function testCounterIncrement() public { @@ -89,14 +86,15 @@ contract E2ETest is Test { emit IL1CrossDomainMessenger.SentMessage(target, sender, data, nonce); l1CrossDomainMessenger.sendMessage(target, data); - // get the root of a previous Beacon block - uint240 beaconTimestamp = uint240(block.timestamp - 12); - bytes32 beaconRoot = Beacon.blockRoot(beaconTimestamp); + // bookmark the next block + vm.roll(1); + uint256 blockNumber = l2CrossDomainMessenger.bookmarkL1Block(); + bytes32 blockHash = l1Block.hash(); // mock the Journal Message memory message = Message(target, sender, data, nonce); Journal memory journal = Journal({ - commitment: Steel.Commitment(Encoding.encodeVersionedID(beaconTimestamp, 1), beaconRoot), + commitment: Steel.Commitment(blockNumber, blockHash), l1CrossDomainMessenger: address(l1CrossDomainMessenger), message: message, messageDigest: message.digest() diff --git a/examples/message-passing/contracts/test/L1BlockMock.sol b/examples/message-passing/contracts/test/L1BlockMock.sol new file mode 100644 index 00000000..2fd29612 --- /dev/null +++ b/examples/message-passing/contracts/test/L1BlockMock.sol @@ -0,0 +1,29 @@ +// 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 "../src/IL1Block.sol"; + +contract L1BlockMock is IL1Block { + function number() external view returns (uint64) { + return uint64(block.number - 1); + } + + function hash() external view returns (bytes32) { + return blockhash(block.number - 1); + } +} diff --git a/examples/message-passing/crates/apps/src/bin/publisher.rs b/examples/message-passing/crates/apps/src/bin/prover.rs similarity index 58% rename from examples/message-passing/crates/apps/src/bin/publisher.rs rename to examples/message-passing/crates/apps/src/bin/prover.rs index 4442a260..2090081e 100644 --- a/examples/message-passing/crates/apps/src/bin/publisher.rs +++ b/examples/message-passing/crates/apps/src/bin/prover.rs @@ -12,12 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -// This application demonstrates how to send an off-chain proof request -// to the Bonsai proving service and publish the received proofs directly -// to your deployed app contract. - -use alloy::sol_types::SolCall; -use alloy::providers::{Provider, ProviderBuilder}; +use std::{fs::File, path::PathBuf}; + +use alloy::{ + network::{Ethereum, EthereumWallet}, + providers::{PendingTransactionBuilder, Provider, ProviderBuilder}, + signers::local::PrivateKeySigner, + sol_types::SolEvent, + transports::Transport, +}; use alloy_primitives::{Address, Bytes, TxHash}; use anyhow::{ensure, Context, Result}; use clap::Parser; @@ -26,27 +29,36 @@ use cross_domain_messenger_methods::CROSS_DOMAIN_MESSENGER_ELF; use risc0_ethereum_contracts::encode_seal; use risc0_steel::{ethereum::EthEvmEnv, Contract}; use risc0_zkvm::{default_prover, ExecutorEnv, ProverOpts, VerifierContext}; -use std::fs::File; -use std::path::PathBuf; -use tokio::task; +use tokio::{ + task, + time::{sleep, Duration}, +}; use tracing_subscriber::EnvFilter; use url::Url; /// Simple program to create the Risc Zero proof that a message to be passed to L2 was sent on L1 #[derive(Parser)] struct Args { - /// L1 RPC node endpoint + /// L2 private key. + #[clap(long, env)] + l2_wallet_private_key: PrivateKeySigner, + + /// L1 RPC node endpoint. #[clap(long, env)] l1_rpc_url: Url, - /// Beacon API endpoint URL + /// L2 RPC node endpoint. #[clap(long, env)] - beacon_api_url: Url, + l2_rpc_url: Url, /// L1CrossDomainMessenger's contract address on L1 #[clap(long, env)] l1_cross_domain_messenger_address: Address, + /// L2CrossDomainMessenger's contract address on L2 + #[clap(long, env)] + l2_cross_domain_messenger_address: Address, + /// Hash of the transaction calling 'L1CrossDomainMessenger.sendMessage(...)' #[clap(long)] tx_hash: TxHash, @@ -56,6 +68,11 @@ struct Args { output: PathBuf, } +alloy::sol!( + #[sol(rpc, all_derives)] + "../../contracts/src/IBookmark.sol" +); + #[tokio::main] async fn main() -> Result<()> { tracing_subscriber::fmt() @@ -65,10 +82,10 @@ async fn main() -> Result<()> { dotenvy::dotenv()?; let args = Args::try_parse()?; - let provider = ProviderBuilder::new().on_http(args.l1_rpc_url); + let l1_provider = ProviderBuilder::new().on_http(args.l1_rpc_url); // Try to extract the sent message for the given tx hash. - let receipt = provider + let receipt = l1_provider .get_transaction_receipt(args.tx_hash) .await? .context("tx pending or unknown")?; @@ -84,16 +101,52 @@ async fn main() -> Result<()> { .ok() }) .context("tx invalid")?; - log::info!("tx emitted {:?}", log.data()); + log::info!("Transaction emitted {:?}", log.data()); let message: Message = log.inner.data.into(); - // Create an EVM environment from that provider and a block number. + // Create an alloy provider for that private key and URL. + let wallet = EthereumWallet::from(args.l2_wallet_private_key); + let l2_provider = ProviderBuilder::new() + .with_recommended_fillers() + .wallet(wallet) + .on_http(args.l2_rpc_url); + + // Wait until a block containing the message can be bookmarked on L2. + let bookmark_instance = IBookmark::new(args.l2_cross_domain_messenger_address, l2_provider); + let bookmark_call = bookmark_instance.bookmarkL1Block(); + loop { + let l2_l1_block_number = bookmark_call + .call() + .await + .with_context(|| format!("eth_call failed: {:?}", bookmark_call))? + ._0; + if l2_l1_block_number >= message_block_number { + break; + } + log::info!( + "Waiting for L2 to catch up: {} < {}", + l2_l1_block_number, + message_block_number + ); + sleep(Duration::from_secs(12)).await; + } + + log::info!("Bookmarking the current L1 block hash on L2"); + let pending_tx = bookmark_call + .send() + .await + .with_context(|| format!("eth_sendTransaction failed: {:?}", bookmark_call))?; + let bookmarked_l1_block = confirm_tx::<_, IBookmark::BookmarkedL1Block>(pending_tx).await?; + log::info!("{:?}", &bookmarked_l1_block); + + // Create an EVM environment from the L1 provider and the bookmarked block. let mut env = EthEvmEnv::builder() - .provider(provider) - .block_number(message_block_number) + .provider(l1_provider) + .block_number(bookmarked_l1_block.number) .build() .await .context("failed to create steel env")?; + // Preflight the call to prepare the input for the guest. let mut contract = Contract::preflight(args.l1_cross_domain_messenger_address, &mut env); let success = contract @@ -104,15 +157,11 @@ async fn main() -> Result<()> { .await .context("steel preflight failed")? ._0; - assert!( - success, - "{} returned 'false'", - IL1CrossDomainMessenger::containsCall::SIGNATURE - ); + assert!(success, "preflight returned 'false'"); // Finally, construct the input for the guest. let evm_input = env - .into_beacon_input(args.beacon_api_url) + .into_input() .await .context("failed to crate steel input")?; let cross_domain_messenger_input = CrossDomainMessengerInput { @@ -120,6 +169,7 @@ async fn main() -> Result<()> { message, }; + // Create the steel proof. let prove_info = task::spawn_blocking(move || { let env = ExecutorEnv::builder() .write(&evm_input)? @@ -149,3 +199,21 @@ async fn main() -> Result<()> { Ok(()) } + +async fn confirm_tx( + pending_tx: PendingTransactionBuilder<'_, T, Ethereum>, +) -> Result { + let tx_hash = pending_tx.tx_hash().clone(); + let receipt = pending_tx + .with_timeout(Some(Duration::from_secs(30))) + .get_receipt() + .await + .with_context(|| format!("transaction did not confirm: {}", tx_hash))?; + let log = receipt + .inner + .logs() + .iter() + .find_map(|log| log.log_decode::().ok()) + .with_context(|| format!("event not emitted: {}", E::SIGNATURE))?; + Ok(log.inner.data) +} diff --git a/examples/message-passing/test-local-devnet.sh b/examples/message-passing/e2e-test.sh similarity index 56% rename from examples/message-passing/test-local-devnet.sh rename to examples/message-passing/e2e-test.sh index 486a2fde..deb1a898 100755 --- a/examples/message-passing/test-local-devnet.sh +++ b/examples/message-passing/e2e-test.sh @@ -3,41 +3,37 @@ set -e -o pipefail . .env +echo "Deploy contracts..." forge script --broadcast Deploy L1_CROSS_DOMAIN_MESSENGER_ADDRESS=$(jq -re '[.deployments[].transactions[] | select(.contractName == "L1CrossDomainMessenger")][0] | .contractAddress' ./broadcast/multi/Deploy.s.sol-latest/run.json) L2_CROSS_DOMAIN_MESSENGER_ADDRESS=$(jq -re '[.deployments[].transactions[] | select(.contractName == "L2CrossDomainMessenger")][0] | .contractAddress' ./broadcast/multi/Deploy.s.sol-latest/run.json) COUNTER_ADDRESS=$(jq -re '.deployments[].transactions[] | select(.contractName == "Counter") | .contractAddress' ./broadcast/multi/Deploy.s.sol-latest/run.json) +# call 'Counter.increment()' on the L2 CALL_DATA=$(cast cd 'increment()') echo "L1CrossDomainMessenger.sendMessage..." -SEND_MESSAGE_RECEIPT=$(cast send --rpc-url "${L1_RPC_URL:?}" --private-key "${L1_WALLET_PRIVATE_KEY:?}" --json "$L1_CROSS_DOMAIN_MESSENGER_ADDRESS" 'sendMessage(address target, bytes calldata data)' "$COUNTER_ADDRESS" "$CALL_DATA") -SEND_MESSAGE_TX_HASH=$(echo "$SEND_MESSAGE_RECEIPT" | jq -re '.transactionHash') -cast run --rpc-url "$L1_RPC_URL" "$SEND_MESSAGE_TX_HASH" - -START_TIME=$(date +%s) +SEND_MESSAGE_RECEIPT=$(cast send --rpc-url "${L1_RPC_URL:?}" --private-key "${L1_WALLET_PRIVATE_KEY:?}" --json "$L1_CROSS_DOMAIN_MESSENGER_ADDRESS" 'sendMessage(address, bytes)' "$COUNTER_ADDRESS" "$CALL_DATA") +echo "$SEND_MESSAGE_RECEIPT" | jq +SEND_MESSAGE_TX_HASH=$(echo "$SEND_MESSAGE_RECEIPT" | jq -re 'transactionHash') +echo "Create proof..." RUST_LOG=info,risc0_steel=debug cargo run -- \ + --l2-wallet-private-key "${L2_WALLET_PRIVATE_KEY:?}" \ --l1-rpc-url "${L1_RPC_URL:?}" \ - --beacon-api-url "${BEACON_API_URL:?}" \ + --l2-rpc-url "${L2_RPC_URL:?}" \ --l1-cross-domain-messenger-address="$L1_CROSS_DOMAIN_MESSENGER_ADDRESS" \ - --tx-hash "$SEND_MESSAGE_TX_HASH" + --l2-cross-domain-messenger-address="$L2_CROSS_DOMAIN_MESSENGER_ADDRESS" \ + --tx-hash "$SEND_MESSAGE_TX_HASH" \ + --output "proof.json" RELAY_MESSAGE_ARGS=$(jq -re '[.journal, .seal] | @tsv | sub("\t";" ";"g")' proof.json) -EXECUTION_TIME=$(($(date +%s) - START_TIME)) -if [ $EXECUTION_TIME -lt 12 ]; then - echo "Waiting for the next L1 block..." - sleep $((12 - EXECUTION_TIME)) -fi - echo "L2CrossDomainMessenger.relayMessage..." -RELAY_MESSAGE_RECEIPT=$(cast send --rpc-url "${L2_RPC_URL:?}" --private-key "${L2_WALLET_PRIVATE_KEY:?}" --json "${L2_CROSS_DOMAIN_MESSENGER_ADDRESS:?}" 'relayMessage(bytes,bytes)' $RELAY_MESSAGE_ARGS) -RELAY_MESSAGE_TX_HASH=$(echo "$RELAY_MESSAGE_RECEIPT" | jq -re '.transactionHash') -cast run --rpc-url "${L2_RPC_URL:?}" "$RELAY_MESSAGE_TX_HASH" +cast send --rpc-url "${L2_RPC_URL:?}" --private-key "${L2_WALLET_PRIVATE_KEY:?}" --json "${L2_CROSS_DOMAIN_MESSENGER_ADDRESS:?}" 'relayMessage(bytes, bytes)' $RELAY_MESSAGE_ARGS | jq -echo "Verifying state..." -COUNTER_VALUE=$(cast call --rpc-url "${L2_RPC_URL:?}" "${COUNTER_ADDRESS:?}" 'get()(uint256)') +echo "Verifying L2 state..." +COUNTER_VALUE=$(cast call --rpc-url "${L2_RPC_URL:?}" "$COUNTER_ADDRESS" 'get()(uint256)') if [ "$COUNTER_VALUE" != "1" ]; then echo "Counter value is not 1 as expected, but $COUNTER_VALUE." exit 1 diff --git a/steel/src/beacon.rs b/steel/src/beacon.rs index 830d6ae0..2f030644 100644 --- a/steel/src/beacon.rs +++ b/steel/src/beacon.rs @@ -82,11 +82,16 @@ impl MerkleProof { #[cfg(feature = "host")] mod host { use super::{BeaconInput, MerkleProof}; - use crate::{block::BlockInput, ethereum::EthBlockHeader, host::{db::AlloyDb, HostEvmEnv}, EvmBlockHeader}; + use crate::{ + block::BlockInput, + ethereum::EthBlockHeader, + host::{db::AlloyDb, HostEvmEnv}, + EvmBlockHeader, + }; use alloy::{network::Ethereum, providers::Provider, transports::Transport}; use alloy_primitives::Sealable; use anyhow::{bail, ensure, Context}; - use beacon_api_client::{minimal::Client as BeaconClient, BeaconHeaderSummary, BlockId}; + use beacon_api_client::{mainnet::Client as BeaconClient, BeaconHeaderSummary, BlockId}; use ethereum_consensus::{ssz::prelude::*, types::SignedBeaconBlock, Fork}; use log::info; use proofs::{Proof, ProofAndWitness}; @@ -157,7 +162,10 @@ mod host { "proof derived from API does not verify", ); - info!("Commitment to beacon block root {} at {}", beacon_root, block_ts); + info!( + "Commitment to beacon block root {} at {}", + beacon_root, block_ts + ); Ok(BeaconInput { input, proof }) } diff --git a/steel/src/block.rs b/steel/src/block.rs index ab0ebf47..46aa2485 100644 --- a/steel/src/block.rs +++ b/steel/src/block.rs @@ -132,7 +132,11 @@ pub mod host { debug!("contracts: {}", contracts.len()); debug!("ancestor blocks: {}", ancestors.len()); - info!("Commitment to block hash {} at {}", env.header.seal(), env.header.number()); + info!( + "Commitment to block hash {} at {}", + env.header.seal(), + env.header.number() + ); let input = BlockInput { header: env.header.into_inner(), From 771a8aff39e98241a5edd33f8f52e7a01054a282 Mon Sep 17 00:00:00 2001 From: Wolfgang Welz Date: Mon, 9 Sep 2024 17:47:34 +0200 Subject: [PATCH 40/44] revert steel changes --- steel/src/beacon.rs | 8 -------- steel/src/block.rs | 8 +------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/steel/src/beacon.rs b/steel/src/beacon.rs index 2f030644..e7c0eb8a 100644 --- a/steel/src/beacon.rs +++ b/steel/src/beacon.rs @@ -86,14 +86,12 @@ mod host { block::BlockInput, ethereum::EthBlockHeader, host::{db::AlloyDb, HostEvmEnv}, - EvmBlockHeader, }; use alloy::{network::Ethereum, providers::Provider, transports::Transport}; use alloy_primitives::Sealable; use anyhow::{bail, ensure, Context}; use beacon_api_client::{mainnet::Client as BeaconClient, BeaconHeaderSummary, BlockId}; use ethereum_consensus::{ssz::prelude::*, types::SignedBeaconBlock, Fork}; - use log::info; use proofs::{Proof, ProofAndWitness}; use url::Url; @@ -108,7 +106,6 @@ mod host { P: Provider, { let block_hash = env.header().hash_slow(); - let block_ts = env.header().timestamp(); let parent_beacon_block_root = env .header() .inner() @@ -162,11 +159,6 @@ mod host { "proof derived from API does not verify", ); - info!( - "Commitment to beacon block root {} at {}", - beacon_root, block_ts - ); - Ok(BeaconInput { input, proof }) } } diff --git a/steel/src/block.rs b/steel/src/block.rs index 46aa2485..fe7ad956 100644 --- a/steel/src/block.rs +++ b/steel/src/block.rs @@ -84,7 +84,7 @@ pub mod host { transports::Transport, }; use anyhow::{anyhow, ensure}; - use log::{debug, info}; + use log::debug; impl BlockInput { /// Derives the verifiable input from a [HostEvmEnv]. @@ -132,12 +132,6 @@ pub mod host { debug!("contracts: {}", contracts.len()); debug!("ancestor blocks: {}", ancestors.len()); - info!( - "Commitment to block hash {} at {}", - env.header.seal(), - env.header.number() - ); - let input = BlockInput { header: env.header.into_inner(), state_trie, From 85e58108d6c1c501b170c719ebad26bac8a3a482 Mon Sep 17 00:00:00 2001 From: Wolfgang Welz Date: Mon, 9 Sep 2024 17:49:19 +0200 Subject: [PATCH 41/44] revert steel changes --- steel/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/steel/src/lib.rs b/steel/src/lib.rs index 6a2ac7a3..7afb8d57 100644 --- a/steel/src/lib.rs +++ b/steel/src/lib.rs @@ -76,6 +76,8 @@ impl EvmEnv { pub fn new(db: D, header: Sealed) -> Self { let cfg_env = CfgEnvWithHandlerCfg::new_with_spec_id(Default::default(), SpecId::LATEST); let commitment = Commitment::from_header(&header); + #[cfg(feature = "host")] + log::info!("Commitment to block {}", commitment.blockDigest); Self { db: Some(db), From 4d3ff588c33b378f1894a0b6878b598237860895 Mon Sep 17 00:00:00 2001 From: Wolfgang Welz Date: Mon, 9 Sep 2024 17:51:11 +0200 Subject: [PATCH 42/44] Update script name for CI --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 99bcaa24..56fea835 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -168,7 +168,7 @@ jobs: - run: cargo build - run: forge test -vvv - name: Run E2E test - run: ./test-local-devnet.sh + run: ./e2e-test.sh - run: sccache --show-stats examples: From b07c51d4440d619f88fd1977e90c19c0a26737af Mon Sep 17 00:00:00 2001 From: Wolfgang Welz Date: Mon, 9 Sep 2024 17:54:56 +0200 Subject: [PATCH 43/44] fix script --- examples/message-passing/e2e-test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/message-passing/e2e-test.sh b/examples/message-passing/e2e-test.sh index deb1a898..28127447 100755 --- a/examples/message-passing/e2e-test.sh +++ b/examples/message-passing/e2e-test.sh @@ -16,7 +16,7 @@ CALL_DATA=$(cast cd 'increment()') echo "L1CrossDomainMessenger.sendMessage..." SEND_MESSAGE_RECEIPT=$(cast send --rpc-url "${L1_RPC_URL:?}" --private-key "${L1_WALLET_PRIVATE_KEY:?}" --json "$L1_CROSS_DOMAIN_MESSENGER_ADDRESS" 'sendMessage(address, bytes)' "$COUNTER_ADDRESS" "$CALL_DATA") echo "$SEND_MESSAGE_RECEIPT" | jq -SEND_MESSAGE_TX_HASH=$(echo "$SEND_MESSAGE_RECEIPT" | jq -re 'transactionHash') +SEND_MESSAGE_TX_HASH=$(echo "$SEND_MESSAGE_RECEIPT" | jq -re '.transactionHash') echo "Create proof..." RUST_LOG=info,risc0_steel=debug cargo run -- \ From 7b206d704e5d2721e7dcecc69e7dc134333a9ce4 Mon Sep 17 00:00:00 2001 From: Wolfgang Welz Date: Mon, 9 Sep 2024 17:59:52 +0200 Subject: [PATCH 44/44] update alloy --- examples/message-passing/Cargo.toml | 6 +++--- examples/message-passing/crates/methods/guest/Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/message-passing/Cargo.toml b/examples/message-passing/Cargo.toml index 5b165ead..f3d68902 100644 --- a/examples/message-passing/Cargo.toml +++ b/examples/message-passing/Cargo.toml @@ -20,9 +20,9 @@ risc0-build = { git = "https://github.com/risc0/risc0", branch = "main", feature 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.2.1", features = ["full"] } -alloy-primitives = { version = "0.7", features = ["rlp", "serde", "std"] } -alloy-sol-types = { version = "0.7" } +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" } diff --git a/examples/message-passing/crates/methods/guest/Cargo.toml b/examples/message-passing/crates/methods/guest/Cargo.toml index 4283539e..1583da06 100644 --- a/examples/message-passing/crates/methods/guest/Cargo.toml +++ b/examples/message-passing/crates/methods/guest/Cargo.toml @@ -10,7 +10,7 @@ path = "src/bin/cross_domain_messenger.rs" [workspace] [dependencies] -alloy-sol-types = { version = "0.7" } +alloy-sol-types = { version = "0.8" } risc0-steel = { path = "../../../../../steel" } cross-domain-messenger-core = { path = "../../core" } risc0-zkvm = { git = "https://github.com/risc0/risc0", branch = "main", default-features = false, features = ["std"] }