Skip to content

Commit 8efd76f

Browse files
committed
feat: cli to send signed proofs
1 parent c6e7bee commit 8efd76f

File tree

9 files changed

+165
-25
lines changed

9 files changed

+165
-25
lines changed

aggregation_mode/Cargo.lock

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

aggregation_mode/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[workspace]
22
resolver = "2"
3-
members = ["./batcher", "./proof_aggregator", "./db", "./sdk"]
3+
members = ["./batcher", "./proof_aggregator", "./db", "./sdk", "./cli"]
44

55
[workspace.package]
66
version = "0.1.0"

aggregation_mode/cli/Cargo.toml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[package]
2+
name = "agg_mode_cli"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
serde = { workspace = true }
8+
tracing = { version = "0.1", features = ["log"] }
9+
tracing-subscriber = { version = "0.3.0", features = ["env-filter"] }
10+
bincode = "1.3.3"
11+
tokio = { version = "1", features = ["macros", "rt-multi-thread", "time"] }
12+
alloy = { workspace = true }
13+
agg_mode_sdk = { path = "../sdk"}
14+
sp1-sdk = "5.0.0"
15+
clap = { version = "4.5.4", features = ["derive"] }
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub mod submit;
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
use agg_mode_sdk::{gateway::provider::AggregationModeGatewayProvider, types::Network};
2+
use alloy::signers::local::LocalSigner;
3+
use clap::{command, Args, Subcommand};
4+
use sp1_sdk::{SP1ProofWithPublicValues, SP1VerifyingKey};
5+
use std::{path::PathBuf, str::FromStr};
6+
7+
#[derive(Debug, Subcommand)]
8+
pub enum SubmitCommand {
9+
#[command(name = "sp1")]
10+
SP1(SubmitSP1Args),
11+
}
12+
13+
#[derive(Debug, Clone, Args)]
14+
pub struct SubmitSP1Args {
15+
#[arg(short = 'p', long = "proof")]
16+
proof_path: PathBuf,
17+
#[arg(long = "vk")]
18+
verifying_key_path: PathBuf,
19+
#[arg(long = "private-key")]
20+
private_key: String,
21+
#[arg(short = 'n', long = "network", default_value = "devnet", value_parser = parse_network)]
22+
network: Network,
23+
}
24+
25+
fn parse_network(value: &str) -> Result<Network, String> {
26+
Network::from_str(value).map_err(|_| format!("unsupported network supplied: {value}"))
27+
}
28+
29+
pub async fn run(args: SubmitSP1Args) {
30+
tracing::info!("Submitting SP1 proof to {:?} ", args.network);
31+
32+
let proof = load_proof(&args.proof_path).expect("Valid proof");
33+
let vk = load_vk(&args.verifying_key_path).expect("Valid vk");
34+
35+
let signer =
36+
LocalSigner::from_str(args.private_key.trim()).expect("failed to parse private key: {e}");
37+
38+
let provider = AggregationModeGatewayProvider::new_with_signer(args.network.clone(), signer)
39+
.expect("failed to initialize gateway client: {e:?}");
40+
41+
let response = provider
42+
.submit_sp1_proof(&proof, &vk)
43+
.await
44+
.expect("failed to submit proof: {e:?}");
45+
46+
tracing::info!(
47+
"Proof submitted successfully. Task ID: {}",
48+
response.data.task_id
49+
);
50+
}
51+
52+
fn load_proof(path: &PathBuf) -> Result<SP1ProofWithPublicValues, String> {
53+
let bytes = std::fs::read(path)
54+
.map_err(|e| format!("failed to read proof from {}: {e}", path.display()))?;
55+
56+
bincode::deserialize(&bytes)
57+
.map_err(|e| format!("failed to deserialize proof {}: {e}", path.display()))
58+
}
59+
60+
fn load_vk(path: &PathBuf) -> Result<SP1VerifyingKey, String> {
61+
let bytes = std::fs::read(path)
62+
.map_err(|e| format!("failed to read verifying key from {}: {e}", path.display()))?;
63+
64+
bincode::deserialize(&bytes).map_err(|e| {
65+
format!(
66+
"failed to deserialize verifying key {}: {e}",
67+
path.display()
68+
)
69+
})
70+
}

aggregation_mode/cli/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub mod commands;

aggregation_mode/cli/src/main.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
use agg_mode_cli::commands::{self, submit::SubmitCommand};
2+
use clap::{Parser, Subcommand};
3+
use tracing_subscriber::{EnvFilter, FmtSubscriber};
4+
5+
#[derive(Debug, Parser)]
6+
struct Cli {
7+
#[command(subcommand)]
8+
command: Command,
9+
}
10+
11+
#[derive(Debug, Subcommand)]
12+
enum Command {
13+
#[command(subcommand)]
14+
Submit(SubmitCommand),
15+
}
16+
17+
#[tokio::main]
18+
async fn main() {
19+
let filter = EnvFilter::new("info");
20+
let subscriber = FmtSubscriber::builder().with_env_filter(filter).finish();
21+
tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed");
22+
23+
let cli = Cli::parse();
24+
25+
match cli.command {
26+
Command::Submit(subcommand) => match subcommand {
27+
SubmitCommand::SP1(args) => commands::submit::run(args).await,
28+
},
29+
};
30+
}

aggregation_mode/sdk/src/gateway/provider.rs

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use sp1_sdk::{SP1ProofWithPublicValues, SP1VerifyingKey};
55

66
use crate::{
77
gateway::types::{
8-
GatewayResponse, NonceResponse, Receipt, ReceiptsQueryParams, ReceiptsResponse,
8+
EmptyData, GatewayResponse, NonceResponse, Receipt, ReceiptsQueryParams,
99
SubmitProofResponse, SubmitSP1ProofMessage,
1010
},
1111
types::Network,
@@ -56,18 +56,19 @@ impl<S: Signer> AggregationModeGatewayProvider<S> {
5656
&self.gateway_url
5757
}
5858

59-
pub async fn get_nonce_for(&self, address: String) -> Result<u64, GatewayError> {
59+
pub async fn get_nonce_for(
60+
&self,
61+
address: String,
62+
) -> Result<GatewayResponse<NonceResponse>, GatewayError> {
6063
let url = format!("{}/nonce/{}", self.gateway_url, address);
61-
let response: NonceResponse = self.send_request(self.http_client.get(url)).await?;
62-
63-
Ok(response.nonce)
64+
self.send_request(self.http_client.get(url)).await
6465
}
6566

6667
pub async fn get_receipts_for(
6768
&self,
6869
address: String,
6970
nonce: Option<u64>,
70-
) -> Result<Vec<Receipt>, GatewayError> {
71+
) -> Result<GatewayResponse<Vec<Receipt>>, GatewayError> {
7172
let query = ReceiptsQueryParams {
7273
address: address,
7374
nonce,
@@ -78,16 +79,14 @@ impl<S: Signer> AggregationModeGatewayProvider<S> {
7879
.get(format!("{}/receipts", self.gateway_url))
7980
.query(&query);
8081

81-
let response: ReceiptsResponse = self.send_request(request).await?;
82-
83-
Ok(response.receipts)
82+
self.send_request(request).await
8483
}
8584

8685
pub async fn submit_sp1_proof(
8786
&self,
8887
proof: &SP1ProofWithPublicValues,
8988
vk: &SP1VerifyingKey,
90-
) -> Result<SubmitProofResponse, GatewayError> {
89+
) -> Result<GatewayResponse<SubmitProofResponse>, GatewayError> {
9190
let serialized_proof = bincode::serialize(proof)
9291
.map_err(|e| GatewayError::ProofSerialization(e.to_string()))?;
9392
let serialized_vk =
@@ -97,11 +96,12 @@ impl<S: Signer> AggregationModeGatewayProvider<S> {
9796
return Err(GatewayError::SignerNotConfigured);
9897
};
9998
let signer_address = signer.address().to_string();
100-
let nonce = self.get_nonce_for(signer_address).await?;
101-
let message = SubmitSP1ProofMessage::new(nonce, serialized_proof, serialized_vk)
102-
.sign(signer, &self.network)
103-
.await
104-
.map_err(|e| GatewayError::MessageSignature(e))?;
99+
let nonce_response = self.get_nonce_for(signer_address).await?;
100+
let message =
101+
SubmitSP1ProofMessage::new(nonce_response.data.nonce, serialized_proof, serialized_vk)
102+
.sign(signer, &self.network)
103+
.await
104+
.map_err(|e| GatewayError::MessageSignature(e))?;
105105

106106
let form = multipart::Form::new()
107107
.text("nonce", message.nonce.to_string())
@@ -128,24 +128,29 @@ impl<S: Signer> AggregationModeGatewayProvider<S> {
128128
async fn send_request<T: DeserializeOwned>(
129129
&self,
130130
request: reqwest::RequestBuilder,
131-
) -> Result<T, GatewayError> {
131+
) -> Result<GatewayResponse<T>, GatewayError> {
132132
let response = request
133133
.send()
134134
.await
135135
.map_err(|e| GatewayError::Request(e.to_string()))?;
136136

137-
let payload: GatewayResponse<T> = response
138-
.json()
139-
.await
140-
.map_err(|e| GatewayError::Request(e.to_string()))?;
137+
if !(200..300).contains(&response.status().as_u16()) {
138+
let payload: GatewayResponse<EmptyData> = response
139+
.json()
140+
.await
141+
.map_err(|e| GatewayError::Request(e.to_string()))?;
141142

142-
if payload.status != 200 {
143143
return Err(GatewayError::Api {
144144
status: payload.status,
145145
message: payload.message,
146146
});
147147
}
148148

149-
Ok(payload.data)
149+
let payload: GatewayResponse<T> = response
150+
.json()
151+
.await
152+
.map_err(|e| GatewayError::Request(e.to_string()))?;
153+
154+
Ok(payload)
150155
}
151156
}

aggregation_mode/sdk/src/gateway/types.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,17 @@ use serde::{Deserialize, Serialize};
88
use crate::types::Network;
99

1010
#[derive(Debug, Deserialize)]
11-
pub(super) struct GatewayResponse<T> {
11+
pub struct GatewayResponse<T> {
1212
pub status: u16,
1313
pub message: String,
1414
pub data: T,
1515
}
1616

1717
#[derive(Debug, Deserialize)]
18-
pub(super) struct NonceResponse {
18+
pub(super) struct EmptyData {}
19+
20+
#[derive(Debug, Deserialize)]
21+
pub struct NonceResponse {
1922
pub nonce: u64,
2023
}
2124

0 commit comments

Comments
 (0)