From a1027877b88dea30a187c9b0ce2c9023ea4dcdbd Mon Sep 17 00:00:00 2001 From: Austin Abell Date: Tue, 9 Jul 2024 18:50:57 -0400 Subject: [PATCH 01/13] basic service CLI pre-impl --- .vscode/settings.json | 4 ++- Cargo.lock | 16 +++++++++ Cargo.toml | 2 +- service/Cargo.toml | 25 +++++++++++++ service/src/blobstream.rs | 16 +++++++++ service/src/main.rs | 76 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 137 insertions(+), 2 deletions(-) create mode 100644 service/Cargo.toml create mode 100644 service/src/blobstream.rs create mode 100644 service/src/main.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index ca332eb..f5d84be 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,7 +2,9 @@ "rust-analyzer.linkedProjects": [ "./light-client-guest/guest/Cargo.toml", "./batch-guest/guest/Cargo.toml", - "./host/Cargo.toml" + "./host/Cargo.toml", + "./core/Cargo.toml", + "./service/Cargo.toml" ], "rust-analyzer.check.extraEnv": { "RISC0_SKIP_BUILD": "1", diff --git a/Cargo.lock b/Cargo.lock index db7e8e7..ee5afaf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4877,6 +4877,22 @@ dependencies = [ "syn 2.0.66", ] +[[package]] +name = "service" +version = "0.1.0" +dependencies = [ + "alloy", + "alloy-sol-types", + "anyhow", + "bincode", + "clap", + "host", + "risc0-tm-core", + "tendermint-rpc", + "tokio", + "tracing-subscriber 0.3.18", +] + [[package]] name = "sha1" version = "0.10.6" diff --git a/Cargo.toml b/Cargo.toml index f959075..d97223d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "2" -members = ["core", "host"] +members = ["core", "host", "service"] default-members = ["host"] [workspace.dependencies] diff --git a/service/Cargo.toml b/service/Cargo.toml new file mode 100644 index 0000000..fef2be8 --- /dev/null +++ b/service/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "service" +version = "0.1.0" +edition = "2021" + +[dependencies] +alloy = { version = "0.1", features = ["node-bindings", "network", "providers", "transports", "signer-local"] } +alloy-sol-types = "0.7" +anyhow = "1.0" +# batch-guest = { path = "../batch-guest" } +bincode = "1.3.3" +# ciborium = { workspace = true } +clap = { version = "4.5", features = ["derive"] } +# light-client-guest = { path = "../light-client-guest" } +# reqwest = "0.12.4" +# risc0-ethereum-contracts = { git = "https://github.com/risc0/risc0-ethereum", tag = "v1.0.0" } +risc0-tm-core = { path = "../core" } +# risc0-zkvm = { version = "1.0.1" } +# serde = { workspace = true } +# tendermint = { workspace = true } +# tendermint-light-client-verifier = { workspace = true } +tendermint-rpc = { workspace = true, features = ["http-client"] } +tokio = { version = "1.38.0", features = ["rt", "macros", "fs"] } +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +host = { path = "../host" } diff --git a/service/src/blobstream.rs b/service/src/blobstream.rs new file mode 100644 index 0000000..d5af12c --- /dev/null +++ b/service/src/blobstream.rs @@ -0,0 +1,16 @@ +use risc0_tm_core::IBlobstream::IBlobstreamInstance; +use tendermint_rpc::HttpClient; + +pub(crate) struct BlobstreamService { + contract: IBlobstreamInstance, + tm_client: HttpClient, +} + +impl BlobstreamService { + pub fn new(contract: IBlobstreamInstance, tm_client: HttpClient) -> Self { + Self { + contract, + tm_client, + } + } +} diff --git a/service/src/main.rs b/service/src/main.rs new file mode 100644 index 0000000..220b211 --- /dev/null +++ b/service/src/main.rs @@ -0,0 +1,76 @@ +// 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 self::blobstream::BlobstreamService; +use alloy::{ + network::EthereumWallet, primitives::Address, providers::ProviderBuilder, + signers::local::PrivateKeySigner, +}; +use clap::Parser; +use risc0_tm_core::IBlobstream; +use tendermint_rpc::HttpClient; +use tracing_subscriber::EnvFilter; + +mod blobstream; + +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +struct Args { + /// The Tendermint RPC URL + #[clap(long)] + tendermint_rpc: String, + + /// The Ethereum RPC URL + #[clap(long)] + eth_rpc: String, + + /// The deployed contract on Ethereum to reference + #[clap(long)] + eth_address: Address, + + /// Hex encoded private key to use for submitting proofs to Ethereum + #[clap(long)] + private_key_hex: String, +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + tracing_subscriber::fmt() + .with_env_filter(EnvFilter::from_default_env()) + .init(); + + let Args { + tendermint_rpc, + eth_rpc, + eth_address, + private_key_hex, + } = Args::parse(); + + let tm_client = HttpClient::new(tendermint_rpc.as_str())?; + + let signer: PrivateKeySigner = private_key_hex.parse().expect("should parse private key"); + let wallet = EthereumWallet::from(signer); + + // Create a provider with the wallet. + let provider = ProviderBuilder::new() + .with_recommended_fillers() + .wallet(wallet) + .on_http(eth_rpc.parse()?); + + let contract = IBlobstream::new(eth_address, provider); + + let service = BlobstreamService::new(contract, tm_client); + + Ok(()) +} From 05bbadd39bee7eb789d3de1b53c9ff99287619eb Mon Sep 17 00:00:00 2001 From: Austin Abell Date: Tue, 9 Jul 2024 22:13:05 -0400 Subject: [PATCH 02/13] very basic version working, messy --- dev_deploy_tmp.md | 11 ++++ host/Cargo.toml | 2 +- host/src/lib.rs | 8 ++- host/src/main.rs | 105 ++++++++++++++++++++++++++++++++++---- service/src/blobstream.rs | 70 ++++++++++++++++++++++--- service/src/main.rs | 9 +++- 6 files changed, 185 insertions(+), 20 deletions(-) create mode 100644 dev_deploy_tmp.md diff --git a/dev_deploy_tmp.md b/dev_deploy_tmp.md new file mode 100644 index 0000000..fdf5a23 --- /dev/null +++ b/dev_deploy_tmp.md @@ -0,0 +1,11 @@ +``` +anvil +``` + +``` +cargo run -- deploy --eth-rpc http://127.0.0.1:8545 --private-key-hex 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --tm-height 10 --tm-block-hash 5D3BDD6B58620A0B6C5A9122863D11DA68EB18935D12A9F4E4CF1A27EB39F1AC --dev +``` + +``` +RISC0_DEV_MODE=true cargo run -p service -- --tendermint-rpc https://rpc.celestia-mocha.com --eth-rpc http://127.0.0.1:8545/ --eth-address 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 --private-key-hex 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --batch-size 5 +``` diff --git a/host/Cargo.toml b/host/Cargo.toml index 6da5e7c..f5d915a 100644 --- a/host/Cargo.toml +++ b/host/Cargo.toml @@ -22,9 +22,9 @@ tendermint-light-client-verifier = { workspace = true } tendermint-rpc = { workspace = true, features = ["http-client"] } tokio = { version = "1.38.0", features = ["rt", "macros", "fs"] } tracing-subscriber = { version = "0.3", features = ["env-filter"] } +alloy-contract = "0.1" [dev-dependencies] -alloy-contract = "0.1" serde_json = "1.0" serde_with = { version = "3.8", features = ["base64"] } tokio = { version = "1.38.0", features = ["rt", "macros", "fs"] } diff --git a/host/src/lib.rs b/host/src/lib.rs index bdcfc10..cbb8b17 100644 --- a/host/src/lib.rs +++ b/host/src/lib.rs @@ -21,7 +21,9 @@ use risc0_tm_core::{ IBlobstream::{IBlobstreamInstance, RangeCommitment}, LightClientCommit, }; -use risc0_zkvm::{default_prover, is_dev_mode, sha::Digestible, ExecutorEnv, Prover, Receipt}; +use risc0_zkvm::{ + default_prover, is_dev_mode, sha::Digestible, ExecutorEnv, Prover, ProverOpts, Receipt, +}; use std::ops::Range; use tendermint::{block::Height, node::Id, validator::Set}; use tendermint_light_client_verifier::types::LightBlock; @@ -127,7 +129,9 @@ pub async fn prove_block_range(client: &HttpClient, range: Range) -> anyhow let env = batch_env_builder.write(&batch_receipts)?.build()?; // Note: must block in place to not have issues with Bonsai blocking client when selected - let prove_info = tokio::task::block_in_place(move || prover.prove(env, BATCH_GUEST_ELF))?; + let prove_info = tokio::task::block_in_place(move || { + prover.prove_with_opts(env, BATCH_GUEST_ELF, &ProverOpts::groth16()) + })?; Ok(prove_info.receipt) } diff --git a/host/src/main.rs b/host/src/main.rs index 08a58b5..7a7c8c2 100644 --- a/host/src/main.rs +++ b/host/src/main.rs @@ -16,15 +16,40 @@ use std::path::PathBuf; +use alloy::{ + hex::FromHex, + network::EthereumWallet, + primitives::{Address, FixedBytes}, + providers::ProviderBuilder, + signers::local::PrivateKeySigner, +}; +use alloy_sol_types::sol; use clap::Parser; use host::prove_block_range; +use risc0_tm_core::IBlobstream; use tendermint_rpc::HttpClient; use tokio::fs; use tracing_subscriber::EnvFilter; +// TODO elsewhere if keeping dev mode deploy through CLI +sol!( + #[sol(rpc)] + MockVerifier, + // TODO probably not ideal to reference build directory, fine for now. + "../contracts/out/RiscZeroMockVerifier.sol/RiscZeroMockVerifier.json" +); + +#[derive(Parser, Debug)] +#[command(name = "blobstream0-cli")] +#[command(bin_name = "blobstream0-cli")] +enum BlobstreamCli { + ProveRange(ProveRangeArgs), + Deploy(DeployArgs), +} + #[derive(Parser, Debug)] #[clap(author, version, about, long_about = None)] -struct Args { +struct ProveRangeArgs { /// The start height #[clap(long)] start: u64, @@ -42,24 +67,84 @@ struct Args { out: PathBuf, } +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +struct DeployArgs { + /// The Ethereum RPC URL + #[clap(long)] + eth_rpc: String, + + /// Hex encoded private key to use for submitting proofs to Ethereum + #[clap(long)] + private_key_hex: String, + + #[clap(long)] + verifier_address: Option, + + #[clap(long)] + tm_height: u64, + + #[clap(long)] + tm_block_hash: String, + + #[clap(long)] + dev: bool, +} + #[tokio::main] async fn main() -> anyhow::Result<()> { tracing_subscriber::fmt() .with_env_filter(EnvFilter::from_default_env()) .init(); - let Args { - start, - end, - tendermint_rpc, - out, - } = Args::parse(); + match BlobstreamCli::parse() { + BlobstreamCli::ProveRange(range) => { + let ProveRangeArgs { + start, + end, + tendermint_rpc, + out, + } = range; + + let client = HttpClient::new(tendermint_rpc.as_str())?; + + let receipt = prove_block_range(&client, start..end).await?; + + fs::write(out, bincode::serialize(&receipt)?).await?; + } + BlobstreamCli::Deploy(deploy) => { + let signer: PrivateKeySigner = deploy.private_key_hex.parse()?; + let wallet = EthereumWallet::from(signer); - let client = HttpClient::new(tendermint_rpc.as_str())?; + let provider = ProviderBuilder::new() + .with_recommended_fillers() + .wallet(wallet) + .on_http(deploy.eth_rpc.parse()?); + let verifier_address: Address = if let Some(address) = deploy.verifier_address { + address.parse()? + } else { + if deploy.dev { + MockVerifier::deploy(&provider, [0, 0, 0, 0].into()) + .await? + .address() + .clone() + } else { + unimplemented!("Cannot deploy groth16 verifier yet") + } + }; - let receipt = prove_block_range(&client, start..end).await?; + // Deploy the contract. + let contract = IBlobstream::deploy( + &provider, + verifier_address, + FixedBytes::<32>::from_hex(deploy.tm_block_hash)?, + deploy.tm_height, + ) + .await?; - fs::write(out, bincode::serialize(&receipt)?).await?; + println!("deployed contract to address: {}", contract.address()); + } + } Ok(()) } diff --git a/service/src/blobstream.rs b/service/src/blobstream.rs index d5af12c..59ef4d5 100644 --- a/service/src/blobstream.rs +++ b/service/src/blobstream.rs @@ -1,16 +1,74 @@ +use std::{sync::Arc, time::Duration}; + +use alloy::{network::Network, providers::Provider, transports::Transport}; +use host::{post_batch, prove_block_range}; use risc0_tm_core::IBlobstream::IBlobstreamInstance; -use tendermint_rpc::HttpClient; +use tendermint_rpc::{Client, HttpClient}; pub(crate) struct BlobstreamService { - contract: IBlobstreamInstance, - tm_client: HttpClient, + contract: Arc>, + tm_client: Arc, + batch_size: u64, } impl BlobstreamService { - pub fn new(contract: IBlobstreamInstance, tm_client: HttpClient) -> Self { + pub fn new( + contract: IBlobstreamInstance, + tm_client: HttpClient, + batch_size: u64, + ) -> Self { Self { - contract, - tm_client, + contract: Arc::new(contract), + tm_client: Arc::new(tm_client), + batch_size, + } + } +} + +impl BlobstreamService +where + T: Transport + Clone + 'static, + P: Provider + 'static, + N: Network + 'static, + IBlobstreamInstance: 'static, + Self: 'static, +{ + pub async fn spawn(&self) { + loop { + let contract = Arc::clone(&self.contract); + let height_task = tokio::spawn(async move { contract.latestHeight().call().await }); + let contract = Arc::clone(&self.contract); + let hash_task = tokio::spawn(async move { contract.latestBlockHash().call().await }); + let tm_client = Arc::clone(&self.tm_client); + let tm_height_task = tokio::spawn(async move { + tm_client + .status() + .await + .map(|status| status.sync_info.latest_block_height) + }); + + let (height, hash, tm_height) = tokio::join!(height_task, hash_task, tm_height_task); + + // TODO handle errors gracefully + let height = height.unwrap().unwrap()._0; + // TODO check this hash against tm node as sanity check + let hash = hash.unwrap().unwrap()._0; + let tm_height = tm_height.unwrap().unwrap(); + + // TODO can prove proactively, this is very basic impl + let block_target = height + self.batch_size; + if block_target > tm_height.value() { + // Cannot create a batch yet, wait until ready + tokio::time::sleep(Duration::from_secs(15 * (block_target - tm_height.value()))) + .await; + continue; + } + + // TODO gracefully handle errors + let receipt = prove_block_range(&self.tm_client, height..block_target).await.unwrap(); + post_batch(&self.contract, &receipt).await.unwrap(); + + // TODO ensure height is updated } } } diff --git a/service/src/main.rs b/service/src/main.rs index 220b211..057063a 100644 --- a/service/src/main.rs +++ b/service/src/main.rs @@ -42,6 +42,10 @@ struct Args { /// Hex encoded private key to use for submitting proofs to Ethereum #[clap(long)] private_key_hex: String, + + /// Number of blocks proved in each batch of block headers + #[clap(long)] + batch_size: u64, } #[tokio::main] @@ -55,6 +59,7 @@ async fn main() -> anyhow::Result<()> { eth_rpc, eth_address, private_key_hex, + batch_size, } = Args::parse(); let tm_client = HttpClient::new(tendermint_rpc.as_str())?; @@ -70,7 +75,9 @@ async fn main() -> anyhow::Result<()> { let contract = IBlobstream::new(eth_address, provider); - let service = BlobstreamService::new(contract, tm_client); + BlobstreamService::new(contract, tm_client, batch_size) + .spawn() + .await; Ok(()) } From d67936c7602f1270dca0ab691ef5cf76412d8f63 Mon Sep 17 00:00:00 2001 From: Austin Abell Date: Wed, 10 Jul 2024 11:38:42 -0400 Subject: [PATCH 03/13] add midding comments --- host/src/main.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/host/src/main.rs b/host/src/main.rs index 7a7c8c2..186b56b 100644 --- a/host/src/main.rs +++ b/host/src/main.rs @@ -78,15 +78,19 @@ struct DeployArgs { #[clap(long)] private_key_hex: String, + /// Address of risc0 verifier to use (either mock or groth16) #[clap(long)] verifier_address: Option, + /// Trusted height for contract #[clap(long)] tm_height: u64, + /// Trusted block hash for contract #[clap(long)] tm_block_hash: String, + /// If deploying verifier, will it deploy the mock verifier #[clap(long)] dev: bool, } From c2c3ff3c3073b260981448e362ac1c4a13da5788 Mon Sep 17 00:00:00 2001 From: Austin Abell Date: Wed, 10 Jul 2024 11:38:58 -0400 Subject: [PATCH 04/13] remove unnecessary trait bounds --- service/src/blobstream.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/service/src/blobstream.rs b/service/src/blobstream.rs index 59ef4d5..46c552f 100644 --- a/service/src/blobstream.rs +++ b/service/src/blobstream.rs @@ -27,11 +27,9 @@ impl BlobstreamService { impl BlobstreamService where - T: Transport + Clone + 'static, + T: Transport + Clone, P: Provider + 'static, - N: Network + 'static, - IBlobstreamInstance: 'static, - Self: 'static, + N: Network, { pub async fn spawn(&self) { loop { @@ -65,7 +63,9 @@ where } // TODO gracefully handle errors - let receipt = prove_block_range(&self.tm_client, height..block_target).await.unwrap(); + let receipt = prove_block_range(&self.tm_client, height..block_target) + .await + .unwrap(); post_batch(&self.contract, &receipt).await.unwrap(); // TODO ensure height is updated From 0ea13a928f6712c2c3d43310f4d744fdb8528d07 Mon Sep 17 00:00:00 2001 From: Austin Abell Date: Wed, 10 Jul 2024 13:43:53 -0400 Subject: [PATCH 05/13] implement bonsai support with g16 verifier deployment --- Cargo.lock | 2 ++ dev_deploy_tmp.md | 12 ++++++++++-- host/Cargo.toml | 1 + host/src/lib.rs | 4 ++++ host/src/main.rs | 25 +++++++++++++++++++++++-- service/Cargo.toml | 1 + service/src/blobstream.rs | 2 +- 7 files changed, 42 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ee5afaf..533eb4d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2580,6 +2580,7 @@ dependencies = [ "tendermint-light-client-verifier", "tendermint-rpc", "tokio", + "tracing", "tracing-subscriber 0.3.18", ] @@ -4890,6 +4891,7 @@ dependencies = [ "risc0-tm-core", "tendermint-rpc", "tokio", + "tracing", "tracing-subscriber 0.3.18", ] diff --git a/dev_deploy_tmp.md b/dev_deploy_tmp.md index fdf5a23..bc22aa4 100644 --- a/dev_deploy_tmp.md +++ b/dev_deploy_tmp.md @@ -4,8 +4,16 @@ anvil ``` cargo run -- deploy --eth-rpc http://127.0.0.1:8545 --private-key-hex 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --tm-height 10 --tm-block-hash 5D3BDD6B58620A0B6C5A9122863D11DA68EB18935D12A9F4E4CF1A27EB39F1AC --dev -``` +# Note: change eth-address if redeploying, will be printed on previous +RISC0_DEV_MODE=true cargo run -p service -- --tendermint-rpc https://rpc.celestia-mocha.com --eth-rpc http://127.0.0.1:8545/ --eth-address 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 --private-key-hex 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --batch-size 4 ``` -RISC0_DEV_MODE=true cargo run -p service -- --tendermint-rpc https://rpc.celestia-mocha.com --eth-rpc http://127.0.0.1:8545/ --eth-address 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 --private-key-hex 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --batch-size 5 + + +Bonsai: + ``` +RUST_LOG=info cargo run -- deploy --eth-rpc http://127.0.0.1:8545 --private-key-hex 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --tm-height 10 --tm-block-hash 5D3BDD6B58620A0B6C5A9122863D11DA68EB18935D12A9F4E4CF1A27EB39F1AC + +RUST_LOG=info cargo run -p service -- --tendermint-rpc https://rpc.celestia-mocha.com --eth-rpc http://127.0.0.1:8545/ --eth-address 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 --private-key-hex 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --batch-size 2 +``` \ No newline at end of file diff --git a/host/Cargo.toml b/host/Cargo.toml index f5d915a..89cdc11 100644 --- a/host/Cargo.toml +++ b/host/Cargo.toml @@ -23,6 +23,7 @@ tendermint-rpc = { workspace = true, features = ["http-client"] } tokio = { version = "1.38.0", features = ["rt", "macros", "fs"] } tracing-subscriber = { version = "0.3", features = ["env-filter"] } alloy-contract = "0.1" +tracing = "0.1.40" [dev-dependencies] serde_json = "1.0" diff --git a/host/src/lib.rs b/host/src/lib.rs index cbb8b17..da25f62 100644 --- a/host/src/lib.rs +++ b/host/src/lib.rs @@ -24,6 +24,7 @@ use risc0_tm_core::{ use risc0_zkvm::{ default_prover, is_dev_mode, sha::Digestible, ExecutorEnv, Prover, ProverOpts, Receipt, }; +use tracing::instrument; use std::ops::Range; use tendermint::{block::Height, node::Id, validator::Set}; use tendermint_light_client_verifier::types::LightBlock; @@ -104,6 +105,7 @@ pub async fn prove_block( } /// Fetches and proves a range of light client blocks. +#[instrument(skip(client), err)] pub async fn prove_block_range(client: &HttpClient, range: Range) -> anyhow::Result { let prover = default_prover(); @@ -137,6 +139,7 @@ pub async fn prove_block_range(client: &HttpClient, range: Range) -> anyhow } /// Post batch proof to Eth based chain. +#[instrument(skip(contract, receipt), err)] pub async fn post_batch( contract: &IBlobstreamInstance, receipt: &Receipt, @@ -146,6 +149,7 @@ where P: Provider, N: Network, { + tracing::info!("Posting batch (dev mode={})", is_dev_mode()); let seal = match is_dev_mode() { true => [&[0u8; 4], receipt.claim()?.digest().as_bytes()].concat(), false => groth16::encode(receipt.inner.groth16()?.seal.clone())?, diff --git a/host/src/main.rs b/host/src/main.rs index 186b56b..b0fcbd0 100644 --- a/host/src/main.rs +++ b/host/src/main.rs @@ -19,7 +19,7 @@ use std::path::PathBuf; use alloy::{ hex::FromHex, network::EthereumWallet, - primitives::{Address, FixedBytes}, + primitives::{hex, Address, FixedBytes}, providers::ProviderBuilder, signers::local::PrivateKeySigner, }; @@ -38,6 +38,18 @@ sol!( // TODO probably not ideal to reference build directory, fine for now. "../contracts/out/RiscZeroMockVerifier.sol/RiscZeroMockVerifier.json" ); +sol!( + #[sol(rpc)] + RiscZeroGroth16Verifier, + // TODO probably not ideal to reference build directory, fine for now. + "../contracts/out/RiscZeroGroth16Verifier.sol/RiscZeroGroth16Verifier.json" +); + +// Pulled from https://github.com/risc0/risc0-ethereum/blob/ebec385cc526adb9279c1af55d699c645ca6d694/contracts/src/groth16/ControlID.sol +const CONTROL_ID: [u8; 32] = + hex!("a516a057c9fbf5629106300934d48e0e775d4230e41e503347cad96fcbde7e2e"); +const BN254_CONTROL_ID: [u8; 32] = + hex!("0eb6febcf06c5df079111be116f79bd8c7e85dc9448776ef9a59aaf2624ab551"); #[derive(Parser, Debug)] #[command(name = "blobstream0-cli")] @@ -128,12 +140,21 @@ async fn main() -> anyhow::Result<()> { address.parse()? } else { if deploy.dev { + tracing::debug!("Deploying mock verifier"); MockVerifier::deploy(&provider, [0, 0, 0, 0].into()) .await? .address() .clone() } else { - unimplemented!("Cannot deploy groth16 verifier yet") + tracing::debug!("Deploying groth16 verifier"); + RiscZeroGroth16Verifier::deploy( + &provider, + CONTROL_ID.into(), + BN254_CONTROL_ID.into(), + ) + .await? + .address() + .clone() } }; diff --git a/service/Cargo.toml b/service/Cargo.toml index fef2be8..8c381ee 100644 --- a/service/Cargo.toml +++ b/service/Cargo.toml @@ -23,3 +23,4 @@ tendermint-rpc = { workspace = true, features = ["http-client"] } tokio = { version = "1.38.0", features = ["rt", "macros", "fs"] } tracing-subscriber = { version = "0.3", features = ["env-filter"] } host = { path = "../host" } +tracing = "0.1.40" diff --git a/service/src/blobstream.rs b/service/src/blobstream.rs index 46c552f..60e7fc2 100644 --- a/service/src/blobstream.rs +++ b/service/src/blobstream.rs @@ -50,7 +50,7 @@ where // TODO handle errors gracefully let height = height.unwrap().unwrap()._0; // TODO check this hash against tm node as sanity check - let hash = hash.unwrap().unwrap()._0; + let _hash = hash.unwrap().unwrap()._0; let tm_height = tm_height.unwrap().unwrap(); // TODO can prove proactively, this is very basic impl From 966ee08d3c388718f2a2f7b034917648b31d6bf8 Mon Sep 17 00:00:00 2001 From: Austin Abell Date: Wed, 10 Jul 2024 14:49:48 -0400 Subject: [PATCH 06/13] add commands for Sepolia --- dev_deploy_tmp.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/dev_deploy_tmp.md b/dev_deploy_tmp.md index bc22aa4..60a9ef0 100644 --- a/dev_deploy_tmp.md +++ b/dev_deploy_tmp.md @@ -16,4 +16,12 @@ Bonsai: RUST_LOG=info cargo run -- deploy --eth-rpc http://127.0.0.1:8545 --private-key-hex 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --tm-height 10 --tm-block-hash 5D3BDD6B58620A0B6C5A9122863D11DA68EB18935D12A9F4E4CF1A27EB39F1AC RUST_LOG=info cargo run -p service -- --tendermint-rpc https://rpc.celestia-mocha.com --eth-rpc http://127.0.0.1:8545/ --eth-address 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 --private-key-hex 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --batch-size 2 +``` + +Sepolia: + +``` +RUST_LOG=info cargo run -- deploy --eth-rpc https://ethereum-sepolia-rpc.publicnode.com --private-key-hex --tm-height 1802142 --tm-block-hash 6D8FD8ADC8FBD5E7765EC557D9DF86041F63F9109202A888D8D246B3BCC3B46A --verifier-address 0x925d8331ddc0a1F0d96E68CF073DFE1d92b69187 + +RUST_LOG=host=debug,info cargo run -p service -- --tendermint-rpc https://rpc.celestia-mocha.com --eth-rpc https://ethereum-sepolia-rpc.publicnode.com --eth-address 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 --private-key-hex --batch-size 16 ``` \ No newline at end of file From e8e3108e2eec1d3a8dea9674f51ee8bb48c7fae9 Mon Sep 17 00:00:00 2001 From: Austin Abell Date: Wed, 10 Jul 2024 16:52:55 -0400 Subject: [PATCH 07/13] add additional logging --- dev_deploy_tmp.md | 7 +++++-- host/src/lib.rs | 4 +++- host/src/main.rs | 6 ++++-- service/src/blobstream.rs | 9 +++++++-- service/src/main.rs | 1 + 5 files changed, 20 insertions(+), 7 deletions(-) diff --git a/dev_deploy_tmp.md b/dev_deploy_tmp.md index 60a9ef0..36f0cb2 100644 --- a/dev_deploy_tmp.md +++ b/dev_deploy_tmp.md @@ -5,7 +5,7 @@ anvil ``` cargo run -- deploy --eth-rpc http://127.0.0.1:8545 --private-key-hex 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --tm-height 10 --tm-block-hash 5D3BDD6B58620A0B6C5A9122863D11DA68EB18935D12A9F4E4CF1A27EB39F1AC --dev -# Note: change eth-address if redeploying, will be printed on previous +# Note: change eth-address if redeploying, will be printed on previous output RISC0_DEV_MODE=true cargo run -p service -- --tendermint-rpc https://rpc.celestia-mocha.com --eth-rpc http://127.0.0.1:8545/ --eth-address 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 --private-key-hex 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --batch-size 4 ``` @@ -15,6 +15,7 @@ Bonsai: ``` RUST_LOG=info cargo run -- deploy --eth-rpc http://127.0.0.1:8545 --private-key-hex 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --tm-height 10 --tm-block-hash 5D3BDD6B58620A0B6C5A9122863D11DA68EB18935D12A9F4E4CF1A27EB39F1AC +# Note: change eth-address if redeploying, will be printed on previous output RUST_LOG=info cargo run -p service -- --tendermint-rpc https://rpc.celestia-mocha.com --eth-rpc http://127.0.0.1:8545/ --eth-address 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 --private-key-hex 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --batch-size 2 ``` @@ -24,4 +25,6 @@ Sepolia: RUST_LOG=info cargo run -- deploy --eth-rpc https://ethereum-sepolia-rpc.publicnode.com --private-key-hex --tm-height 1802142 --tm-block-hash 6D8FD8ADC8FBD5E7765EC557D9DF86041F63F9109202A888D8D246B3BCC3B46A --verifier-address 0x925d8331ddc0a1F0d96E68CF073DFE1d92b69187 RUST_LOG=host=debug,info cargo run -p service -- --tendermint-rpc https://rpc.celestia-mocha.com --eth-rpc https://ethereum-sepolia-rpc.publicnode.com --eth-address 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 --private-key-hex --batch-size 16 -``` \ No newline at end of file +``` + +Sepolia mock verifier: `0x6e5D3e69934814470CEE91C63afb7033056682F3` \ No newline at end of file diff --git a/host/src/lib.rs b/host/src/lib.rs index da25f62..4eecc27 100644 --- a/host/src/lib.rs +++ b/host/src/lib.rs @@ -24,11 +24,11 @@ use risc0_tm_core::{ use risc0_zkvm::{ default_prover, is_dev_mode, sha::Digestible, ExecutorEnv, Prover, ProverOpts, Receipt, }; -use tracing::instrument; use std::ops::Range; use tendermint::{block::Height, node::Id, validator::Set}; use tendermint_light_client_verifier::types::LightBlock; use tendermint_rpc::{Client, HttpClient, Paging}; +use tracing::instrument; async fn fetch_light_block( client: &HttpClient, @@ -63,6 +63,7 @@ pub struct LightBlockProof { } /// Prove a single block with the trusted light client block and the height to fetch and prove. +#[instrument(skip(prover, client, previous_block))] pub async fn prove_block( prover: &dyn Prover, client: &HttpClient, @@ -131,6 +132,7 @@ pub async fn prove_block_range(client: &HttpClient, range: Range) -> anyhow let env = batch_env_builder.write(&batch_receipts)?.build()?; // Note: must block in place to not have issues with Bonsai blocking client when selected + tracing::debug!("Proving batch of blocks"); let prove_info = tokio::task::block_in_place(move || { prover.prove_with_opts(env, BATCH_GUEST_ELF, &ProverOpts::groth16()) })?; diff --git a/host/src/main.rs b/host/src/main.rs index b0fcbd0..b4d63db 100644 --- a/host/src/main.rs +++ b/host/src/main.rs @@ -139,7 +139,7 @@ async fn main() -> anyhow::Result<()> { let verifier_address: Address = if let Some(address) = deploy.verifier_address { address.parse()? } else { - if deploy.dev { + let deployed_address = if deploy.dev { tracing::debug!("Deploying mock verifier"); MockVerifier::deploy(&provider, [0, 0, 0, 0].into()) .await? @@ -155,7 +155,9 @@ async fn main() -> anyhow::Result<()> { .await? .address() .clone() - } + }; + println!("deployed verifier to address: {}", deployed_address); + deployed_address }; // Deploy the contract. diff --git a/service/src/blobstream.rs b/service/src/blobstream.rs index 60e7fc2..3ae8e8c 100644 --- a/service/src/blobstream.rs +++ b/service/src/blobstream.rs @@ -52,13 +52,18 @@ where // TODO check this hash against tm node as sanity check let _hash = hash.unwrap().unwrap()._0; let tm_height = tm_height.unwrap().unwrap(); + tracing::info!("Contract height: {height}, tendermint height: {tm_height}"); // TODO can prove proactively, this is very basic impl let block_target = height + self.batch_size; if block_target > tm_height.value() { + let wait_time = 15 * (block_target - tm_height.value()); + tracing::info!( + "Not enough tendermint blocks to create batch, waiting {} seconds", + wait_time + ); // Cannot create a batch yet, wait until ready - tokio::time::sleep(Duration::from_secs(15 * (block_target - tm_height.value()))) - .await; + tokio::time::sleep(Duration::from_secs(wait_time)).await; continue; } diff --git a/service/src/main.rs b/service/src/main.rs index 057063a..120b9b4 100644 --- a/service/src/main.rs +++ b/service/src/main.rs @@ -75,6 +75,7 @@ async fn main() -> anyhow::Result<()> { let contract = IBlobstream::new(eth_address, provider); + tracing::info!("Starting service"); BlobstreamService::new(contract, tm_client, batch_size) .spawn() .await; From 6b4e565f4fcf87f4442bfcfac2df584619bc1975 Mon Sep 17 00:00:00 2001 From: Austin Abell Date: Thu, 11 Jul 2024 11:32:26 -0400 Subject: [PATCH 08/13] add trace span close events --- host/src/lib.rs | 8 ++++---- host/src/main.rs | 1 + service/src/main.rs | 3 ++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/host/src/lib.rs b/host/src/lib.rs index 4eecc27..f798258 100644 --- a/host/src/lib.rs +++ b/host/src/lib.rs @@ -28,7 +28,7 @@ use std::ops::Range; use tendermint::{block::Height, node::Id, validator::Set}; use tendermint_light_client_verifier::types::LightBlock; use tendermint_rpc::{Client, HttpClient, Paging}; -use tracing::instrument; +use tracing::{instrument, Level}; async fn fetch_light_block( client: &HttpClient, @@ -63,7 +63,7 @@ pub struct LightBlockProof { } /// Prove a single block with the trusted light client block and the height to fetch and prove. -#[instrument(skip(prover, client, previous_block))] +#[instrument(skip(prover, client, previous_block), err, level = Level::TRACE)] pub async fn prove_block( prover: &dyn Prover, client: &HttpClient, @@ -106,7 +106,7 @@ pub async fn prove_block( } /// Fetches and proves a range of light client blocks. -#[instrument(skip(client), err)] +#[instrument(skip(client), err, level = Level::TRACE)] pub async fn prove_block_range(client: &HttpClient, range: Range) -> anyhow::Result { let prover = default_prover(); @@ -141,7 +141,7 @@ pub async fn prove_block_range(client: &HttpClient, range: Range) -> anyhow } /// Post batch proof to Eth based chain. -#[instrument(skip(contract, receipt), err)] +#[instrument(skip(contract, receipt), err, level = Level::TRACE)] pub async fn post_batch( contract: &IBlobstreamInstance, receipt: &Receipt, diff --git a/host/src/main.rs b/host/src/main.rs index b4d63db..b4ef6b0 100644 --- a/host/src/main.rs +++ b/host/src/main.rs @@ -110,6 +110,7 @@ struct DeployArgs { #[tokio::main] async fn main() -> anyhow::Result<()> { tracing_subscriber::fmt() + .with_span_events(FmtSpan::CLOSE) .with_env_filter(EnvFilter::from_default_env()) .init(); diff --git a/service/src/main.rs b/service/src/main.rs index 120b9b4..40b79b4 100644 --- a/service/src/main.rs +++ b/service/src/main.rs @@ -20,7 +20,7 @@ use alloy::{ use clap::Parser; use risc0_tm_core::IBlobstream; use tendermint_rpc::HttpClient; -use tracing_subscriber::EnvFilter; +use tracing_subscriber::{fmt::format::FmtSpan, EnvFilter}; mod blobstream; @@ -51,6 +51,7 @@ struct Args { #[tokio::main] async fn main() -> anyhow::Result<()> { tracing_subscriber::fmt() + .with_span_events(FmtSpan::CLOSE) .with_env_filter(EnvFilter::from_default_env()) .init(); From 66d653fc3c8f70498f83f3d100200caa8515ab7d Mon Sep 17 00:00:00 2001 From: Austin Abell Date: Thu, 11 Jul 2024 12:10:42 -0400 Subject: [PATCH 09/13] update usage guide --- dev_deploy_tmp.md | 30 ------------------------- host/src/main.rs | 4 +++- service/src/main.rs | 2 ++ usage-guide.md | 54 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 59 insertions(+), 31 deletions(-) delete mode 100644 dev_deploy_tmp.md create mode 100644 usage-guide.md diff --git a/dev_deploy_tmp.md b/dev_deploy_tmp.md deleted file mode 100644 index 36f0cb2..0000000 --- a/dev_deploy_tmp.md +++ /dev/null @@ -1,30 +0,0 @@ -``` -anvil -``` - -``` -cargo run -- deploy --eth-rpc http://127.0.0.1:8545 --private-key-hex 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --tm-height 10 --tm-block-hash 5D3BDD6B58620A0B6C5A9122863D11DA68EB18935D12A9F4E4CF1A27EB39F1AC --dev - -# Note: change eth-address if redeploying, will be printed on previous output -RISC0_DEV_MODE=true cargo run -p service -- --tendermint-rpc https://rpc.celestia-mocha.com --eth-rpc http://127.0.0.1:8545/ --eth-address 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 --private-key-hex 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --batch-size 4 -``` - - -Bonsai: - -``` -RUST_LOG=info cargo run -- deploy --eth-rpc http://127.0.0.1:8545 --private-key-hex 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --tm-height 10 --tm-block-hash 5D3BDD6B58620A0B6C5A9122863D11DA68EB18935D12A9F4E4CF1A27EB39F1AC - -# Note: change eth-address if redeploying, will be printed on previous output -RUST_LOG=info cargo run -p service -- --tendermint-rpc https://rpc.celestia-mocha.com --eth-rpc http://127.0.0.1:8545/ --eth-address 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 --private-key-hex 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --batch-size 2 -``` - -Sepolia: - -``` -RUST_LOG=info cargo run -- deploy --eth-rpc https://ethereum-sepolia-rpc.publicnode.com --private-key-hex --tm-height 1802142 --tm-block-hash 6D8FD8ADC8FBD5E7765EC557D9DF86041F63F9109202A888D8D246B3BCC3B46A --verifier-address 0x925d8331ddc0a1F0d96E68CF073DFE1d92b69187 - -RUST_LOG=host=debug,info cargo run -p service -- --tendermint-rpc https://rpc.celestia-mocha.com --eth-rpc https://ethereum-sepolia-rpc.publicnode.com --eth-address 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 --private-key-hex --batch-size 16 -``` - -Sepolia mock verifier: `0x6e5D3e69934814470CEE91C63afb7033056682F3` \ No newline at end of file diff --git a/host/src/main.rs b/host/src/main.rs index b4ef6b0..48fb392 100644 --- a/host/src/main.rs +++ b/host/src/main.rs @@ -29,7 +29,8 @@ use host::prove_block_range; use risc0_tm_core::IBlobstream; use tendermint_rpc::HttpClient; use tokio::fs; -use tracing_subscriber::EnvFilter; +use tracing_subscriber::fmt::format; +use tracing_subscriber::{fmt::format::FmtSpan, EnvFilter}; // TODO elsewhere if keeping dev mode deploy through CLI sol!( @@ -110,6 +111,7 @@ struct DeployArgs { #[tokio::main] async fn main() -> anyhow::Result<()> { tracing_subscriber::fmt() + .event_format(format().compact()) .with_span_events(FmtSpan::CLOSE) .with_env_filter(EnvFilter::from_default_env()) .init(); diff --git a/service/src/main.rs b/service/src/main.rs index 40b79b4..444cfe5 100644 --- a/service/src/main.rs +++ b/service/src/main.rs @@ -20,6 +20,7 @@ use alloy::{ use clap::Parser; use risc0_tm_core::IBlobstream; use tendermint_rpc::HttpClient; +use tracing_subscriber::fmt::format; use tracing_subscriber::{fmt::format::FmtSpan, EnvFilter}; mod blobstream; @@ -51,6 +52,7 @@ struct Args { #[tokio::main] async fn main() -> anyhow::Result<()> { tracing_subscriber::fmt() + .event_format(format().compact()) .with_span_events(FmtSpan::CLOSE) .with_env_filter(EnvFilter::from_default_env()) .init(); diff --git a/usage-guide.md b/usage-guide.md new file mode 100644 index 0000000..ab2b129 --- /dev/null +++ b/usage-guide.md @@ -0,0 +1,54 @@ +## Running service + +### Local testing + +Start up a local Eth dev node: +```console +anvil +``` + +Deploy verifier and blobstream contract + +```console +RUST_LOG=info cargo run -- deploy --eth-rpc http://127.0.0.1:8545 --private-key-hex 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --tm-height 10 --tm-block-hash 5D3BDD6B58620A0B6C5A9122863D11DA68EB18935D12A9F4E4CF1A27EB39F1AC --dev +``` + +> The `--tm-height` and `--tm-block-hash` options are pulled from the network that is being synced. Make sure these match the network from `--tendermint-rpc` in the following command. + +Start the service: + +``` +RISC0_DEV_MODE=true RUST_LOG=host=trace,info cargo run -p service -- --tendermint-rpc https://rpc.celestia-mocha.com --eth-rpc http://127.0.0.1:8545/ --eth-address 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 --private-key-hex 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --batch-size 4 +``` + +Where the `--tendermint-rpc` param can be configured to be any other network endpoint, and the `--batch-size` can be configured. + +> Note: The `--eth-address` here is hard coded to be the first deployment when running the first deployment. Either restart the anvil node or update the `--eth-address` parameter to the output from the deploy if making changes to the contract. + + +#### Local with snark proofs from Bonsai + +The flow is similar to above, except that the `--dev` flag is removed from the deployment, to deploy the groth16 verifier in its place for that step: + +``` +RUST_LOG=info cargo run -- deploy --eth-rpc http://127.0.0.1:8545 --private-key-hex 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --tm-height 10 --tm-block-hash 5D3BDD6B58620A0B6C5A9122863D11DA68EB18935D12A9F4E4CF1A27EB39F1AC +``` + +### Sepolia + +Currently there are already groth16 and mock verifiers deployed to Sepolia. + +- Sepolia groth16 verifier: `0x925d8331ddc0a1F0d96E68CF073DFE1d92b69187` +- Sepolia mock verifier: `0x6e5D3e69934814470CEE91C63afb7033056682F3` + +Deploy the blobstream contract with the `--verifier-address` from above: + +``` +RUST_LOG=info cargo run -- deploy --eth-rpc https://ethereum-sepolia-rpc.publicnode.com --private-key-hex --tm-height 1802142 --tm-block-hash 6D8FD8ADC8FBD5E7765EC557D9DF86041F63F9109202A888D8D246B3BCC3B46A --verifier-address 0x925d8331ddc0a1F0d96E68CF073DFE1d92b69187 +``` + +Run the service with `RISC0_DEV_MODE=true` if you chose the mock verifier. + +``` +RUST_LOG=host=trace,info cargo run -p service --release -- --tendermint-rpc https://rpc.celestia-mocha.com --eth-rpc https://ethereum-sepolia-rpc.publicnode.com --eth-address --private-key-hex --batch-size 16 +``` From df2e809e6cac1f1a99636a666c3a0126a6e50cb7 Mon Sep 17 00:00:00 2001 From: Austin Abell Date: Thu, 11 Jul 2024 12:20:34 -0400 Subject: [PATCH 10/13] update docs and cleanup --- README.md | 2 ++ host/Cargo.toml | 4 ++-- service/Cargo.toml | 4 ++-- service/src/blobstream.rs | 14 ++++++++++++++ usage-guide.md | 4 +++- 5 files changed, 23 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b85de16..b47453e 100644 --- a/README.md +++ b/README.md @@ -38,3 +38,5 @@ cargo run -p host -- --help ``` > Note: This CLI as well as other APIs will change in the short term. If you need anything specific from this, [open an issue](https://github.com/risc0/blobstream0/issues/new)! + +For more docs on running the Blobstream service, see [usage-guide.md](./usage-guide.md). diff --git a/host/Cargo.toml b/host/Cargo.toml index 89cdc11..553cbb5 100644 --- a/host/Cargo.toml +++ b/host/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] alloy = { version = "0.1", features = ["node-bindings", "network", "providers", "transports", "signer-local"] } +alloy-contract = "0.1" alloy-sol-types = "0.7" anyhow = "1.0" batch-guest = { path = "../batch-guest" } @@ -21,9 +22,8 @@ tendermint = { workspace = true } tendermint-light-client-verifier = { workspace = true } tendermint-rpc = { workspace = true, features = ["http-client"] } tokio = { version = "1.38.0", features = ["rt", "macros", "fs"] } -tracing-subscriber = { version = "0.3", features = ["env-filter"] } -alloy-contract = "0.1" tracing = "0.1.40" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } [dev-dependencies] serde_json = "1.0" diff --git a/service/Cargo.toml b/service/Cargo.toml index 8c381ee..f3875cc 100644 --- a/service/Cargo.toml +++ b/service/Cargo.toml @@ -11,6 +11,7 @@ anyhow = "1.0" bincode = "1.3.3" # ciborium = { workspace = true } clap = { version = "4.5", features = ["derive"] } +host = { path = "../host" } # light-client-guest = { path = "../light-client-guest" } # reqwest = "0.12.4" # risc0-ethereum-contracts = { git = "https://github.com/risc0/risc0-ethereum", tag = "v1.0.0" } @@ -21,6 +22,5 @@ risc0-tm-core = { path = "../core" } # tendermint-light-client-verifier = { workspace = true } tendermint-rpc = { workspace = true, features = ["http-client"] } tokio = { version = "1.38.0", features = ["rt", "macros", "fs"] } -tracing-subscriber = { version = "0.3", features = ["env-filter"] } -host = { path = "../host" } tracing = "0.1.40" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } diff --git a/service/src/blobstream.rs b/service/src/blobstream.rs index 3ae8e8c..3d7b112 100644 --- a/service/src/blobstream.rs +++ b/service/src/blobstream.rs @@ -1,3 +1,17 @@ +// 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::{sync::Arc, time::Duration}; use alloy::{network::Network, providers::Provider, transports::Transport}; diff --git a/usage-guide.md b/usage-guide.md index ab2b129..c59e0ad 100644 --- a/usage-guide.md +++ b/usage-guide.md @@ -1,4 +1,6 @@ -## Running service +## Running the Blobstream Zero Service + +This service will watch the Ethereum contract for the current head of the verified chain, request light client blocks from a Tendermint node, generate a batch proof, and then post that proof back to the Ethereum contract. ### Local testing From 3e811587cd5ecf32b7a7f08fc8258910a60670c9 Mon Sep 17 00:00:00 2001 From: Austin Abell Date: Thu, 11 Jul 2024 13:23:46 -0400 Subject: [PATCH 11/13] gracefully handle http errors --- service/src/blobstream.rs | 85 +++++++++++++++++++++++++++------------ service/src/main.rs | 2 +- 2 files changed, 61 insertions(+), 26 deletions(-) diff --git a/service/src/blobstream.rs b/service/src/blobstream.rs index 3d7b112..d7650b0 100644 --- a/service/src/blobstream.rs +++ b/service/src/blobstream.rs @@ -14,10 +14,11 @@ use std::{sync::Arc, time::Duration}; -use alloy::{network::Network, providers::Provider, transports::Transport}; +use alloy::{network::Network, primitives::FixedBytes, providers::Provider, transports::Transport}; use host::{post_batch, prove_block_range}; use risc0_tm_core::IBlobstream::IBlobstreamInstance; use tendermint_rpc::{Client, HttpClient}; +use tokio::task::JoinError; pub(crate) struct BlobstreamService { contract: Arc>, @@ -45,33 +46,60 @@ where P: Provider + 'static, N: Network, { - pub async fn spawn(&self) { - loop { - let contract = Arc::clone(&self.contract); - let height_task = tokio::spawn(async move { contract.latestHeight().call().await }); - let contract = Arc::clone(&self.contract); - let hash_task = tokio::spawn(async move { contract.latestBlockHash().call().await }); - let tm_client = Arc::clone(&self.tm_client); - let tm_height_task = tokio::spawn(async move { - tm_client - .status() - .await - .map(|status| status.sync_info.latest_block_height) - }); + async fn fetch_current_state(&self) -> Result, JoinError> { + let contract = Arc::clone(&self.contract); + let height_task = tokio::spawn(async move { contract.latestHeight().call().await }); + let contract = Arc::clone(&self.contract); + let hash_task = tokio::spawn(async move { contract.latestBlockHash().call().await }); + let tm_client = Arc::clone(&self.tm_client); + let tm_height_task = tokio::spawn(async move { + tm_client + .status() + .await + .map(|status| status.sync_info.latest_block_height) + }); + + let (height, hash, tm_height) = tokio::try_join!(height_task, hash_task, tm_height_task)?; + + let result = || { + let height = height?._0; + let eth_verified_hash = hash?._0; + let tm_height = tm_height?.value(); + Ok(BlobstreamState { + eth_verified_height: height, + eth_verified_hash, + tm_height, + }) + }; - let (height, hash, tm_height) = tokio::join!(height_task, hash_task, tm_height_task); + Ok(result()) + } - // TODO handle errors gracefully - let height = height.unwrap().unwrap()._0; - // TODO check this hash against tm node as sanity check - let _hash = hash.unwrap().unwrap()._0; - let tm_height = tm_height.unwrap().unwrap(); - tracing::info!("Contract height: {height}, tendermint height: {tm_height}"); + /// Spawn blobstream service, which will run indefinitely until a fatal error when awaited. + pub async fn spawn(&self) -> anyhow::Result<()> { + loop { + let BlobstreamState { + eth_verified_height, + tm_height, + // TODO check this hash against tm node as sanity check + eth_verified_hash: _, + } = match self.fetch_current_state().await? { + Ok(r) => r, + Err(e) => { + // Failed to query state, log a warning and wait to avoid being rate limited + tracing::warn!("failed to request current state: {}", e); + tokio::time::sleep(Duration::from_secs(15)).await; + continue; + } + }; + tracing::info!( + "Contract height: {eth_verified_height}, tendermint height: {tm_height}" + ); // TODO can prove proactively, this is very basic impl - let block_target = height + self.batch_size; - if block_target > tm_height.value() { - let wait_time = 15 * (block_target - tm_height.value()); + let block_target = eth_verified_height + self.batch_size; + if block_target > tm_height { + let wait_time = 15 * (block_target - tm_height); tracing::info!( "Not enough tendermint blocks to create batch, waiting {} seconds", wait_time @@ -82,7 +110,7 @@ where } // TODO gracefully handle errors - let receipt = prove_block_range(&self.tm_client, height..block_target) + let receipt = prove_block_range(&self.tm_client, eth_verified_height..block_target) .await .unwrap(); post_batch(&self.contract, &receipt).await.unwrap(); @@ -91,3 +119,10 @@ where } } } + +struct BlobstreamState { + eth_verified_height: u64, + #[allow(dead_code)] + eth_verified_hash: FixedBytes<32>, + tm_height: u64, +} diff --git a/service/src/main.rs b/service/src/main.rs index 444cfe5..1f9d9ac 100644 --- a/service/src/main.rs +++ b/service/src/main.rs @@ -81,7 +81,7 @@ async fn main() -> anyhow::Result<()> { tracing::info!("Starting service"); BlobstreamService::new(contract, tm_client, batch_size) .spawn() - .await; + .await?; Ok(()) } From 3f3cf89895a104f6e6cf7fcb4b2375da59cefda1 Mon Sep 17 00:00:00 2001 From: Austin Abell Date: Thu, 11 Jul 2024 13:37:35 -0400 Subject: [PATCH 12/13] gracefully handle more errors, cap consecutive errors --- service/src/blobstream.rs | 46 +++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/service/src/blobstream.rs b/service/src/blobstream.rs index d7650b0..dbca240 100644 --- a/service/src/blobstream.rs +++ b/service/src/blobstream.rs @@ -20,6 +20,20 @@ use risc0_tm_core::IBlobstream::IBlobstreamInstance; use tendermint_rpc::{Client, HttpClient}; use tokio::task::JoinError; +macro_rules! handle_temporal_result { + ($res:expr, $consecutive_failures:expr) => { + match $res { + Ok(r) => r, + Err(e) => { + tracing::warn!("failed to request current state: {}", e); + $consecutive_failures += 1; + tokio::time::sleep(std::time::Duration::from_secs(15)).await; + continue; + } + } + }; +} + pub(crate) struct BlobstreamService { contract: Arc>, tm_client: Arc, @@ -77,21 +91,14 @@ where /// Spawn blobstream service, which will run indefinitely until a fatal error when awaited. pub async fn spawn(&self) -> anyhow::Result<()> { - loop { + let mut consecutive_failures = 0; + while consecutive_failures < 5 { let BlobstreamState { eth_verified_height, tm_height, // TODO check this hash against tm node as sanity check - eth_verified_hash: _, - } = match self.fetch_current_state().await? { - Ok(r) => r, - Err(e) => { - // Failed to query state, log a warning and wait to avoid being rate limited - tracing::warn!("failed to request current state: {}", e); - tokio::time::sleep(Duration::from_secs(15)).await; - continue; - } - }; + eth_verified_hash: _, + } = handle_temporal_result!(self.fetch_current_state().await?, consecutive_failures); tracing::info!( "Contract height: {eth_verified_height}, tendermint height: {tm_height}" ); @@ -109,14 +116,21 @@ where continue; } - // TODO gracefully handle errors - let receipt = prove_block_range(&self.tm_client, eth_verified_height..block_target) - .await - .unwrap(); - post_batch(&self.contract, &receipt).await.unwrap(); + let receipt = handle_temporal_result!( + prove_block_range(&self.tm_client, eth_verified_height..block_target).await, + consecutive_failures + ); + handle_temporal_result!( + post_batch(&self.contract, &receipt).await, + consecutive_failures + ); + + consecutive_failures = 0; // TODO ensure height is updated } + + anyhow::bail!("Reached limit of consecutive errors"); } } From 1b77e43374c9dbf3595c610279de69e22cf3c383 Mon Sep 17 00:00:00 2001 From: Austin Abell Date: Thu, 11 Jul 2024 13:39:43 -0400 Subject: [PATCH 13/13] fmt --- service/src/blobstream.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/src/blobstream.rs b/service/src/blobstream.rs index dbca240..79d6ed4 100644 --- a/service/src/blobstream.rs +++ b/service/src/blobstream.rs @@ -97,7 +97,7 @@ where eth_verified_height, tm_height, // TODO check this hash against tm node as sanity check - eth_verified_hash: _, + eth_verified_hash: _, } = handle_temporal_result!(self.fetch_current_state().await?, consecutive_failures); tracing::info!( "Contract height: {eth_verified_height}, tendermint height: {tm_height}"