forked from risc0/risc0
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* add bonsai-ethereum-relay, bonsai-sdk-async and bonsai-rest-api-mock * add solc and forge to main workflow * fix copyright * fix copyright * fix e2e_test.rs license * add solc action * replace solc with svm * put sdk-async behind a feature flag * add svm action * add conditional svm installation * improve Bonsai Relay README * fix svm action * set default debug level to info
Showing
62 changed files
with
15,986 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
name: svm install | ||
description: Install svm | ||
runs: | ||
using: composite | ||
steps: | ||
- if: runner.os == 'macOS' && runner.arch == 'ARM64' | ||
run: | | ||
pgrep -q oahd && echo Rosetta already installed || /usr/sbin/softwareupdate --install-rosetta --agree-to-license | ||
shell: bash | ||
|
||
- run: | | ||
which solc && echo Solc already installed || { cargo install svm-rs && svm install 0.8.17 && svm use 0.8.17; } | ||
shell: bash |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
[package] | ||
name = "bonsai-ethereum-relay" | ||
description = "A relayer to integrate Ethereum with Bonsai." | ||
version = "0.2.0" | ||
edition = { workspace = true } | ||
license = { workspace = true } | ||
homepage = { workspace = true } | ||
repository = { workspace = true } | ||
|
||
[dependencies] | ||
anyhow = "1.0" | ||
async-trait = "0.1.58" | ||
axum = { version = "0.6.1", features = ["macros", "headers"] } | ||
bincode = "1.3.3" | ||
bonsai-proxy-contract = { path = "src/proxy-contract" } | ||
bonsai-rest-api-mock = { workspace = true } | ||
bonsai-sdk = { workspace = true, features = ["async"] } | ||
clap = { version = "4.0", features = ["derive", "env"] } | ||
displaydoc = "0.2" | ||
ethers = { version = "=2.0.2", features = ["rustls", "ws"] } | ||
ethers-signers = { version = "=2.0.2", features = ["aws"] } | ||
ethers-solc = { version = "=2.0.2" } | ||
futures = "0.3" | ||
hex = "0.4.3" | ||
hyper = "0.14" | ||
pin-project = "1" | ||
reqwest = { version = "0.11.14", features = ["stream", "json", "gzip"] } | ||
risc0-zkvm = { workspace = true, features = ["binfmt"] } | ||
rusoto_core = { version = "0.48.0", default-features = false, features = ["rustls"] } | ||
rusoto_kms = { version = "0.48.0", default-features = false } | ||
semver = "1.0" | ||
serde = { version = "1.0", default-features = false, features = ["derive"] } | ||
serde_json = "1.0" | ||
snafu = "0.7" | ||
thiserror = "1.0.11" | ||
tokio = { version = "1.19", features = ["full", "sync"] } | ||
tokio-stream = "0.1.12" | ||
tower-http = { version = "0.4", features = ["trace"] } | ||
tracing = "0.1" | ||
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] } | ||
typed-builder = "0.12.0" | ||
utoipa = { version = "3.0.0", features = ["axum_extras", "time", "uuid"] } | ||
utoipa-swagger-ui = { version = "3.0.2", features = ["axum", "debug-embed"] } | ||
validator = { version = "0.16.0", features = ["derive"] } | ||
|
||
[dev-dependencies] | ||
bincode = "1" | ||
bytemuck = "1.13" | ||
risc0-zkvm-methods = { path = "../../risc0/zkvm/methods", default-features = false } | ||
time = "0.3.11" | ||
uuid = { version = "1.3.1", features = ["v4", "serde"] } | ||
wiremock = "0.5" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
# Bonsai Ethereum Relay | ||
This repository provides the `bonsai-ethereum-relay`, a tool to integrate Ethereum with Bonsai. It is coupled with an Ethereum Smart Contract able to proxy the interaction from Ethereum to Bonsai and vice versa. | ||
|
||
## Usage | ||
```console | ||
Usage: bonsai-ethereum-relay [OPTIONS] --contract-address <CONTRACT_ADDRESS> --eth-node-url <ETH_NODE_URL> --wallet-key-identifier <WALLET_KEY_IDENTIFIER> | ||
|
||
Options: | ||
-p, --port <PORT> | ||
The port of the relay server API [default: 8080] | ||
--publish-mode | ||
Toggle to disable the relay server API | ||
--contract-address <CONTRACT_ADDRESS> | ||
Bonsai Relay contract address on Ethereum | ||
--eth-node-url <ETH_NODE_URL> | ||
Ethereum Node endpoint | ||
--eth-chain-id <ETH_CHAIN_ID> | ||
Ethereum chain ID [default: 5] | ||
-w, --wallet-key-identifier <WALLET_KEY_IDENTIFIER> | ||
Wallet Key Identifier. Can be a private key as a hex string, or an AWS KMS key identifier [env: WALLET_KEY_IDENTIFIER] | ||
--use-kms | ||
Toggle to use a KMS client | ||
-h, --help | ||
Print help | ||
-V, --version | ||
Print version | ||
``` | ||
|
||
A typical flow works as follows: | ||
1. Deploy a Bonsai Relay Smart Contract on Ethereum at a given address `0xB..`. | ||
2. Start an instance of the relay tool configured with the option `--contract-address` defined as `0xB..`. | ||
3. Delegate some off-chain computation for a given Smart Contract `A` to Bonsai by registering the `Image` or `ELF` (i.e., the compiled binary responsible for executing the given computation on the RISC Zero ZKVM) to Bonsai. | ||
4. The corresponding `Image ID` and the Bonsai Relay Smart Contract `0xB..` can be used to construct and deploy the Smart Contract `A` to Ethereum. | ||
5. Send a transaction to Smart Contract `A` to trigger a `Callback request` event that the Bonsai Relay will catch and forward to Bonsai. | ||
6. Once Bonsai has generated a proof of execution, the Bonsai Relay will forward this proof along with the result of the computation to the Bonsai Relay Smart Contract. | ||
7. This triggers a verification of the proof on-chain, and only upon successful verification, the result of the computation will be forwarded to the original requester Smart Contract `A`. | ||
|
||
### Publish mode | ||
As an alternative to trigger a `Callback request` from Ethereum as described by step 5, the request can be sent directly to the Bonsai Relay via an HTTP REST API. Then, the remaining steps will flow as above. The following example explains how to do that. | ||
|
||
#### Example Usage | ||
The following example assumes that the Bonsai Relay is up and running with the server API enabled, | ||
and that the memory image of your `ELF` is already registered against Bonsai with a given `IMAGE_ID` as its identifier. | ||
|
||
```rust | ||
// initialize a relay client | ||
let relay_client = Client::from_parts( | ||
"http://localhost:8080".to_string(), // here goes the actual url of the Bonsai Relay | ||
"BONSAI_API_KEY" // here goes the actual Bonsai API-Key | ||
) | ||
.expect("Failed to initialize the relay client"); | ||
|
||
// Initialize the input for the guest. | ||
// In this example we are sending a slice of bytes, | ||
// where the first 4 bytes represents the length | ||
// of the slice (in little endian). | ||
let mut input = vec![0; 36]; | ||
input[0] = 32; | ||
input[35] = 100; | ||
|
||
// Create a CallbackRequest for the your contract | ||
// example: (tests/solidity/contracts/Counter.sol). | ||
let image_id: [u8; 32] = bytemuck::cast(IMAGE_ID); | ||
let request = CallbackRequest { | ||
callback_contract: counter.address(), | ||
// you can use the command `solc --hashes tests/solidity/contracts/Counter.sol` | ||
// to get the value for your actual contract | ||
function_selector: [0xff, 0x58, 0x5c, 0xaf], | ||
gas_limit: 3000000, | ||
image_id, | ||
input, | ||
}; | ||
|
||
// Send the callback request to the Bonsai Relay. | ||
relay_client | ||
.callback_request(request) | ||
.await | ||
.expect("Callback request failed"); | ||
|
||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
// Copyright 2023 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 axum::{http::Request, middleware::Next, response::Response}; | ||
|
||
use super::{Error, Result}; | ||
|
||
pub(crate) async fn authorize<B>(mut req: Request<B>, next: Next<B>) -> Result<Response> { | ||
if let Some(auth_header) = req | ||
.headers() | ||
.get("x-api-key") | ||
.and_then(|header| header.to_str().ok()) | ||
{ | ||
let owned_value = auth_header.to_owned(); | ||
// insert the current user into a request extension so the handler can | ||
// extract it | ||
req.extensions_mut().insert(owned_value); | ||
Ok(next.run(req).await) | ||
} else { | ||
Err(Error::Unauthorized) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
// Copyright 2023 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 anyhow::Context; | ||
use async_trait::async_trait; | ||
use axum::{ | ||
body::Bytes, | ||
extract::FromRequest, | ||
http::{Request, StatusCode}, | ||
response::{IntoResponse, Response}, | ||
}; | ||
use hyper::{body::HttpBody, header, HeaderMap}; | ||
use serde::de::DeserializeOwned; | ||
|
||
/// Bincode extractor of the request body as a stream. | ||
/// When used as an extractor, it can deserialize request bodies into some type | ||
/// that implements [`serde::Deserialize`] using [`bincode`]. | ||
pub(crate) struct Bincode<T>(pub T); | ||
|
||
#[async_trait] | ||
impl<T, S, B> FromRequest<S, B> for Bincode<T> | ||
where | ||
T: DeserializeOwned, | ||
B: HttpBody + Send + 'static, | ||
B::Data: Send, | ||
B::Error: std::error::Error + Send + Sync, | ||
S: Send + Sync, | ||
{ | ||
type Rejection = Response; | ||
|
||
async fn from_request(req: Request<B>, state: &S) -> Result<Self, Self::Rejection> { | ||
if !octet_stream_content_type(req.headers()) { | ||
return Err(( | ||
StatusCode::UNSUPPORTED_MEDIA_TYPE, | ||
"Expected request with `Content-Type: application/octet-stream`", | ||
) | ||
.into_response()); | ||
} | ||
|
||
let bytes = Bytes::from_request(req, state) | ||
.await | ||
.map_err(IntoResponse::into_response)?; | ||
|
||
let result = bincode::deserialize(&bytes).context("failed to deserialize"); | ||
|
||
let value = result.map_err(|err: anyhow::Error| { | ||
( | ||
StatusCode::BAD_REQUEST, | ||
format!("Failed to parse request body: {err}"), | ||
) | ||
.into_response() | ||
})?; | ||
|
||
Ok(Bincode(value)) | ||
} | ||
} | ||
|
||
fn octet_stream_content_type(headers: &HeaderMap) -> bool { | ||
let Some(content_type) = headers.get(header::CONTENT_TYPE) else { | ||
return false; | ||
}; | ||
content_type == "application/octet-stream" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
// Copyright 2023 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 axum::{extract::State, Extension}; | ||
use bonsai_proxy_contract::CallbackRequestFilter; | ||
use bonsai_sdk::alpha_async::get_client_from_parts; | ||
|
||
use super::{bincode::Bincode, state::ApiState, Error, Result}; | ||
use crate::{ | ||
downloader::{ | ||
event_processor::EventProcessor, | ||
proxy_callback_proof_processor::ProxyCallbackProofRequestProcessor, | ||
}, | ||
sdk::client::CallbackRequest, | ||
storage::Storage, | ||
}; | ||
|
||
/// Publish a CallbackRequest to the Relayer. | ||
/// | ||
/// Return status 200 on success. | ||
#[utoipa::path( | ||
post, | ||
path = "/v1/callbacks", | ||
request_body = CallbackRequest, | ||
responses( | ||
(status = 200, description = "Callback request sent successfully"), | ||
(status = 400, description = "Bad request error"), | ||
(status = 500, description = "Internal server error"), | ||
) | ||
)] | ||
pub(crate) async fn post_callback_request<S: Storage + Sync + Send + Clone>( | ||
Extension(api_key): Extension<String>, | ||
State(s): State<ApiState<S>>, | ||
Bincode(request): Bincode<CallbackRequest>, | ||
) -> Result<(), Error> { | ||
let client = get_client_from_parts(s.bonsai_url, api_key).await?; | ||
let proxy = ProxyCallbackProofRequestProcessor::new(client, s.storage, Some(s.notifier)); | ||
proxy.process_event(request.into()).await | ||
} | ||
|
||
impl From<CallbackRequest> for CallbackRequestFilter { | ||
fn from(val: CallbackRequest) -> Self { | ||
CallbackRequestFilter { | ||
account: ethers::types::Address::default(), | ||
image_id: val.image_id, | ||
input: val.input.into(), | ||
callback_contract: val.callback_contract, | ||
function_selector: val.function_selector, | ||
gas_limit: val.gas_limit, | ||
} | ||
} | ||
} |
Oops, something went wrong.