Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Reorganization and boundless adapter #19

Merged
merged 14 commits into from
Jan 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3,177 changes: 3,085 additions & 92 deletions Cargo.lock

Large diffs are not rendered by default.

5 changes: 2 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[workspace]
resolver = "2"
members = ["circuits", "core", "guests/header-chain", "guests/final-circuit"]
members = ["core", "header-chain", "final-spv", "boundless-client", "header-chain/header-chain-guest", "final-spv/final-spv-guest"]

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

Expand All @@ -9,12 +9,11 @@ hex = "0.4.3"
risc0-zkvm = { version="1.2.0", features = ["prove"]}
risc0-zkp = "1.2.0"
env_logger = "0.10"
borsh = {version = "1.5.3", features = ["derive"] }
serde = "1.0"
serde_json = "1.0.108"
anyhow = { version = "1.0", default-features = false}
risc0-groth16 = {version="1.2.0", features=["prove"]}
bitcoin-pow = {path="./risc0_circuits/bitcoin-pow"}
verify-stark = {path="./risc0_circuits/verify-stark"}
tempfile = "3.10.1"
bitcoincore-rpc = "0.19.0"
crypto-bigint = { git = "https://github.com/risc0/RustCrypto-crypto-bigint", tag = "v0.5.2-risczero.0", default-features = false }
Expand Down
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
**Warning: This software is experimental and should not be used in production.**

## Building Risc0 Guests
First, install `Risc0` toolchain. You can refer to [here]().
First, install `Risc0` toolchain. You can refer to [here](https://dev.risczero.com/api/zkvm/install).

To build Risc0 guests deterministically, run the following command:

Expand Down Expand Up @@ -54,7 +54,6 @@ docker build -f docker/prover.Dockerfile . -t risc0-groth16-prover
cd ..
```


### Testing

To test the setup, use:
Expand All @@ -63,6 +62,12 @@ To test the setup, use:
cargo test -r --package core --bin core -- tests --show-output
```

## Boundless
Boundless is a decentralized proving network built by Risc0. You can learn more from [here](https://docs.beboundless.xyz/). To use it,
```
RUST_LOG=info PINATA_JWT=<your-pinata-jwt> cargo run --package boundless-client --bin boundless-client -- --rpc-url <your-sepolia-rpc-url> --wallet-private-key <your-private-key> --boundless-market-address 0x01e4130C977b39aaa28A744b8D3dEB23a5297654 --set-verifier-address 0xea6a0Ca4BfD0A6C43081D57672b3B6D43B69265F --storage-provider pinata --offchain --order-stream-url https://order-stream.beboundless.xyz
```

## Our Approach
### Goal
Our goal is to be able to (optimistically) prove any computation inside BitVM. Overall system is as follows:
Expand Down
34 changes: 34 additions & 0 deletions boundless-client/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[package]
name = "boundless-client"
version = "0.1.0"
edition = "2021"

[dependencies]
alloy = { version = "0.6" }
alloy-primitives = { version = "0.8", default-features = false, features = ["rlp", "serde", "std"] }
alloy-sol-types = { version = "0.8" }
header-chain = { path = "../header-chain" }
anyhow = { version = "1.0.68", default-features = false }
backoff = { version = "0.4.0", features = ["futures", "tokio"] }
bincode = "1.3.3"
borsh.workspace = true
bytemuck = "1.13.1"
hex = { workspace = true }
once_cell = { version = "1.19.0", optional = true }
parking_lot = { version = "0.12.1", optional = true }
reqwest = { version = "0.12.5", features = ["rustls-tls", "json", "http2"], default-features = false }
risc0-zkp = { workspace = true, optional = true }
risc0-zkvm = { workspace = true, default-features = false, features = ["std"] }
risc0-circuit-rv32im = { version = "1.1.3" }
risc0-zkvm-platform = { version = "1.1.3" }
serde = { workspace = true }
tokio = { version = "1.39", features = ["full"] }
tracing = { workspace = true }
tracing-subscriber = {version = "0.3.18", features = ["env-filter"] }
dotenvy = "0.15"
url = { version = "2.5" }
clap = { version = "4.5", features = ["derive", "env"] }
boundless-market = "0.4.1"
risc0-build-ethereum = { version = "1.2" }
risc0-ethereum-contracts = { version = "1.2" }

157 changes: 157 additions & 0 deletions boundless-client/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
use std::time::{Duration, Instant};

use alloy::signers::local::PrivateKeySigner;
use alloy_primitives::{utils::parse_ether, Address};
use anyhow::{bail, ensure};
use borsh::BorshDeserialize;
use boundless_market::{
client::ClientBuilder,
contracts::{Input, Offer, Predicate, ProofRequest, Requirements},
storage::StorageProviderConfig,
};
use clap::Parser;
use header_chain::header_chain::{CircuitBlockHeader, HeaderChainCircuitInput};
use risc0_zkvm::sha::Digestible;
use risc0_zkvm::{compute_image_id, default_executor, ExecutorEnv};
use url::Url;

const ELF: &[u8] = include_bytes!("../../elfs/mainnet-header-chain-guest");

/// Arguments of the publisher CLI.
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
struct Args {
/// URL of the Ethereum RPC endpoint.
#[clap(short, long, env)]
rpc_url: Url,
/// Private key used to interact with the EvenNumber contract.
#[clap(short, long, env)]
wallet_private_key: PrivateKeySigner,
/// Submit the request offchain via the provided order stream service url.
#[clap(short, long, requires = "order_stream_url")]
offchain: bool,
/// Offchain order stream service URL to submit offchain requests to.
#[clap(long, env)]
order_stream_url: Option<Url>,
/// Storage provider to use
#[clap(flatten)]
storage_config: Option<StorageProviderConfig>,
/// Address of the RiscZeroSetVerifier contract.
#[clap(short, long, env)]
set_verifier_address: Address,
/// Address of the BoundlessfMarket contract.
#[clap(short, long, env)]
boundless_market_address: Address,
}

#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
tracing_subscriber::fmt()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.init();

match dotenvy::dotenv() {
Ok(path) => tracing::debug!("Loaded environment variables from {:?}", path),
Err(e) if e.not_found() => tracing::debug!("No .env file found"),
Err(e) => bail!("failed to load .env file: {}", e),
}
let args = Args::parse();

let image_id = compute_image_id(ELF).unwrap();

let headers = include_bytes!("../../mainnet-headers.bin");
let headers = headers
.chunks(80)
.map(|header| CircuitBlockHeader::try_from_slice(header).unwrap())
.collect::<Vec<CircuitBlockHeader>>();

let input = HeaderChainCircuitInput {
method_id: [0; 8],
prev_proof: header_chain::header_chain::HeaderChainPrevProofType::GenesisBlock,
block_headers: headers[0..50].to_vec(),
};

// Create a Boundless client from the provided parameters.
let boundless_client = ClientBuilder::default()
.with_rpc_url(args.rpc_url)
.with_boundless_market_address(args.boundless_market_address)
.with_set_verifier_address(args.set_verifier_address)
.with_order_stream_url(args.offchain.then_some(args.order_stream_url).flatten())
.with_storage_provider_config(args.storage_config)
.with_private_key(args.wallet_private_key)
.build()
.await?;

ensure!(
boundless_client.storage_provider.is_some(),
"a storage provider is required to upload the zkVM guest ELF"
);
let image_url = boundless_client.upload_image(ELF).await?;
tracing::info!("Uploaded image to {}", image_url);

let input_bytes = borsh::to_vec(&input).unwrap();

// If the input exceeds 2 kB, upload the input and provide its URL instead, as a rule of thumb.
let input_url = boundless_client.upload_input(&input_bytes).await?;
tracing::info!("Uploaded input to {}", input_url);

let env = ExecutorEnv::builder().write_slice(&input_bytes).build()?;
let session_info = default_executor().execute(env, ELF)?;
let mcycles_count = session_info
.segments
.iter()
.map(|segment| 1 << segment.po2)
.sum::<u64>()
.div_ceil(1_000_000);
println!("{} mcycles", mcycles_count);
let journal = session_info.journal;
println!("Journal: {:#?}", journal);

let request = ProofRequest::default()
.with_image_url(&image_url)
.with_input(Input::url(&input_url))
.with_requirements(Requirements::new(
image_id,
Predicate::digest_match(journal.digest()),
))
.with_offer(
Offer::default()
// The market uses a reverse Dutch auction mechanism to match requests with provers.
// Each request has a price range that a prover can bid on. One way to set the price
// is to choose a desired (min and max) price per million cycles and multiply it
// by the number of cycles. Alternatively, you can use the `with_min_price` and
// `with_max_price` methods to set the price directly.
.with_min_price_per_mcycle(parse_ether("0.001")?, mcycles_count)
// NOTE: If your offer is not being accepted, try increasing the max price.
.with_max_price_per_mcycle(parse_ether("0.002")?, mcycles_count)
// The timeout is the maximum number of blocks the request can stay
// unfulfilled in the market before it expires. If a prover locks in
// the request and does not fulfill it before the timeout, the prover can be
// slashed.
.with_timeout(1000),
);
println!("Request: {:#?}", request);

// Send the request and wait for it to be completed.
let start_time = Instant::now();
let (request_id, expires_at) = boundless_client.submit_request(&request).await?;
tracing::info!("Request 0x{request_id:x} submitted at {:?}", start_time);

// Wait for the request to be fulfilled by the market, returning the journal and seal.
tracing::info!("Waiting for 0x{request_id:x} to be fulfilled");
let (journal, seal) = boundless_client
.wait_for_request_fulfillment(request_id, Duration::from_secs(10), expires_at)
.await?;

let end_time = Instant::now();
let duration = end_time.duration_since(start_time);

tracing::info!("Request 0x{request_id:x} fulfilled");
tracing::info!("End time: {:?}", end_time);
tracing::info!("Time taken: {:?}", duration);
tracing::info!("Request 0x{request_id:x} fulfilled");
tracing::info!("Journal: {:#?}", journal);
tracing::info!("Seal: {:#?}", seal);

Ok(())
}
2 changes: 0 additions & 2 deletions circuits/.gitignore

This file was deleted.

Loading