diff --git a/Cargo.toml b/Cargo.toml index 1bbd76b..fa662d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,9 +11,9 @@ members = [ [workspace.dependencies] [workspace.dependencies.snarkvm] -#git = "https://github.com/ProvableHQ/snarkVM.git" -#rev = "57bfe32" -version = "=3.8.0" +git = "https://github.com/ProvableHQ/snarkVM.git" +rev = "c1ea0705c" +#version = "=3.8.0" #snarkvm = { path = "../snarkVM" } [profile.release] diff --git a/README.md b/README.md new file mode 100644 index 0000000..f3df936 --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# Service + +## Testing + +``` +git clone git@github.com:ProvableHQ/snarkOS.git +cd snarkOS +./devnet.sh (use network ID 2) +``` + +``` +git clone git@github.com:ProvableHQ/service.git +cargo build --release +./target/release/authorize-service --network canary +./target/release/execute-service --network canary +./target/release/transfer_client --generate-requests +``` + diff --git a/authorize-service/src/authorize.rs b/authorize-service/src/authorize.rs index 5473e37..b798c51 100644 --- a/authorize-service/src/authorize.rs +++ b/authorize-service/src/authorize.rs @@ -18,11 +18,12 @@ use super::*; // Initialize a thread-local `ProcessVariant`. thread_local! { - pub static PROCESS: RefCell> = const { RefCell::new(None) }; + pub static PROCESS1: RefCell> = const { RefCell::new(None) }; + pub static PROCESS2: RefCell> = const { RefCell::new(None) }; } pub fn authorize(bytes: Bytes) -> Result { - PROCESS.with(|process| { + PROCESS1.with(|process| { // Initialize the process if it is not already initialized. if process.borrow().is_none() { *process.borrow_mut() = match N::ID { @@ -45,3 +46,28 @@ pub fn authorize(bytes: Bytes) -> Result { process.borrow().as_ref().unwrap().authorize(&bytes) }) } + +pub fn authorize_signed(bytes: Bytes) -> Result { + PROCESS2.with(|process| { + // Initialize the process if it is not already initialized. + if process.borrow().is_none() { + *process.borrow_mut() = match N::ID { + MainnetV0::ID => { + println!("Loading mainnet process..."); + Some(ProcessVariant::MainnetV0(Process::load()?)) + } + TestnetV0::ID => { + println!("Loading testnet process..."); + Some(ProcessVariant::TestnetV0(Process::load()?)) + } + CanaryV0::ID => { + println!("Loading canary process..."); + Some(ProcessVariant::CanaryV0(Process::load()?)) + } + _ => panic!("Invalid network"), + }; + }; + // Compute the `Authorization`. + process.borrow().as_ref().unwrap().authorize_request(&bytes) + }) +} diff --git a/authorize-service/src/main.rs b/authorize-service/src/main.rs index 7bfae53..6fa3cbc 100644 --- a/authorize-service/src/main.rs +++ b/authorize-service/src/main.rs @@ -33,6 +33,7 @@ async fn run(port: u16) { let routes = keygen_route::() .or(authorize_route::()) + .or(authorize_signed_route::()) .or(sign_route::()) .or(verify_route::()) .with(warp::trace( diff --git a/authorize-service/src/process_variant.rs b/authorize-service/src/process_variant.rs index be4c3d0..25e8030 100644 --- a/authorize-service/src/process_variant.rs +++ b/authorize-service/src/process_variant.rs @@ -78,3 +78,39 @@ impl ProcessVariant { Ok(serde_json::to_value(response)?) } } + +impl ProcessVariant { + pub fn authorize_request(&self, bytes: &[u8]) -> Result { + match self { + ProcessVariant::MainnetV0(process) => { + Self::handle_authorize_request::(process, bytes) + } + ProcessVariant::TestnetV0(process) => { + Self::handle_authorize_request::(process, bytes) + } + ProcessVariant::CanaryV0(process) => { + Self::handle_authorize_request::(process, bytes) + } + } + } + + fn handle_authorize_request, N: Network>( + process: &Process, + bytes: &[u8], + ) -> Result { + // Deserialize the request. + let request = serde_json::from_slice::>(bytes)?; + + // Initialize the RNG. + let rng = &mut rand_chacha::ChaCha20Rng::from_entropy(); + + // Authorize the function. + let authorization = process.authorize_request::(request.request, rng)?; + + // Construct the response. + let response = AuthorizeSignedResponse:: { authorization }; + + // Return the response as JSON. + Ok(serde_json::to_value(response)?) + } +} diff --git a/authorize-service/src/request.rs b/authorize-service/src/request.rs index f6ec532..24207d8 100644 --- a/authorize-service/src/request.rs +++ b/authorize-service/src/request.rs @@ -34,6 +34,12 @@ pub struct AuthorizeRequest { pub priority_fee_in_microcredits: U64, } +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct AuthorizeSignedRequest { + #[serde(bound(deserialize = ""))] + pub request: snarkvm::prelude::Request, +} + #[derive(Clone, Debug, Serialize, Deserialize)] pub struct SignRequest { #[serde(bound(deserialize = ""))] diff --git a/authorize-service/src/response.rs b/authorize-service/src/response.rs index 04f622e..defd82a 100644 --- a/authorize-service/src/response.rs +++ b/authorize-service/src/response.rs @@ -30,6 +30,12 @@ pub struct AuthorizeResponse { pub fee_authorization: Authorization, } +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct AuthorizeSignedResponse { + #[serde(bound(deserialize = ""))] + pub authorization: Authorization, +} + #[derive(Clone, Debug, Serialize, Deserialize)] pub struct SignResponse { pub signed_message: Vec, diff --git a/authorize-service/src/routes.rs b/authorize-service/src/routes.rs index e7835b9..cf22a06 100644 --- a/authorize-service/src/routes.rs +++ b/authorize-service/src/routes.rs @@ -50,6 +50,23 @@ pub fn authorize_route() -> impl Filter( +) -> impl Filter + Clone { + warp::post() + .and(warp::path("authorize_signed")) + .and(warp::path::end()) + .and(warp::body::content_length_limit(32 * 1024)) // 32 KiB + .and(warp::body::bytes()) + .and_then(|bytes: Bytes| async move { + let response = match tokio_rayon::spawn_fifo(|| authorize_signed::(bytes)).await { + Ok(response) => response, + Err(_) => return Err(warp::reject()), + }; + Ok(warp::reply::json(&response)) + }) +} + // POST /sign pub fn sign_route() -> impl Filter + Clone { warp::post() diff --git a/block-parser/src/block_json/input.rs b/block-parser/src/block_json/input.rs index 4dc03eb..1a3aa78 100644 --- a/block-parser/src/block_json/input.rs +++ b/block-parser/src/block_json/input.rs @@ -29,10 +29,10 @@ impl InputJSON { None => bail!("Invalid input ID"), }; // Get the value of the input. - let value = match json.get("value").and_then(|v| v.as_str()) { - Some(value) => Some(value.to_string()), - None => None, - }; + let value = json + .get("value") + .and_then(|v| v.as_str()) + .map(|v| v.to_string()); Ok(Self { type_, id, value }) } diff --git a/block-parser/src/decoders.rs b/block-parser/src/decoders.rs index ae8b14b..82662ae 100644 --- a/block-parser/src/decoders.rs +++ b/block-parser/src/decoders.rs @@ -146,7 +146,7 @@ pub fn decode_block_unchecked(string: &str) -> Result<(Vec *U64::::from_str(&v)?, + Some(v) => *U64::::from_str(v)?, None => bail!("Invalid JSON object"), }; // Add the `bond_public` operation to the credits transactions. @@ -187,7 +187,7 @@ pub fn decode_block_unchecked(string: &str) -> Result<(Vec *U64::::from_str(&v)?, + Some(v) => *U64::::from_str(v)?, None => bail!("Invalid JSON object"), }; // Add the `unbond_public` operation to the credits transactions. diff --git a/block-parser/src/lib.rs b/block-parser/src/lib.rs index f754f4c..92e9744 100644 --- a/block-parser/src/lib.rs +++ b/block-parser/src/lib.rs @@ -130,12 +130,12 @@ pub fn process_block_transactions( #[cfg(test)] mod tests { - use super::*; - use snarkvm::prelude::{CanaryV0, TestnetV0}; + // use super::*; + // use snarkvm::prelude::{CanaryV0}; - use std::fs::File; + // use std::fs::File; - type CurrentNetwork = CanaryV0; + // type CurrentNetwork = CanaryV0; // #[test] // fn test_dont_add_bonded_map() { diff --git a/client/Cargo.toml b/client/Cargo.toml index e2d4456..dbfd7f5 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -12,6 +12,10 @@ path = "../execute-service" [dependencies.anyhow] version = "1.0.75" +[dependencies.clap] +version = "4.3" +features = ["derive"] + [dependencies.rand_chacha] version = "0.3.1" @@ -24,6 +28,7 @@ features = [ "full" ] [dependencies.reqwest] version = "0.11.22" +features = [ "json" ] [dependencies.warp] version = "0.3.6" diff --git a/client/src/main.rs b/client/src/main.rs index d735135..ea8951a 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -16,19 +16,22 @@ use snarkvm::ledger::block::Transaction; use snarkvm::prelude::{ - Field, FromBytes, Identifier, Network, PrivateKey, ProgramID, ToBytes, Uniform, Value, U64, + Field, FromBytes, Identifier, Literal, Network, PrivateKey, ProgramID, Request, ToBytes, + Uniform, Value, ValueType, U64, }; use authorize_service::*; use execute_service::*; use anyhow::{bail, Result}; +use clap::Parser; use rand_chacha::rand_core::SeedableRng; use reqwest::Client; use std::str::FromStr; const KEYGEN_URL: &str = "http://localhost:8080/keygen"; const AUTHORIZE_URL: &str = "http://localhost:8080/authorize"; +const AUTHORIZE_SIGNED_URL: &str = "http://localhost:8080/authorize_signed"; const EXECUTE_URL: &str = "http://localhost:8081/execute"; const BROADCAST_URL: &str = "http://localhost:3033/canary/transaction/broadcast"; @@ -38,8 +41,19 @@ const DEVNET_PRIVATE_KEY: &str = "APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKU type CurrentNetwork = snarkvm::prelude::CanaryV0; +/// Command-line arguments parser +#[derive(Parser)] +struct Args { + /// Determine whether or not to let the client generate the request. + #[arg(long, default_value = "false")] + generate_requests: bool, +} + #[tokio::main] async fn main() -> Result<()> { + // Parse command-line arguments + let args = Args::parse(); + // Create a `Client` instance. let client = Client::new(); @@ -79,12 +93,51 @@ async fn main() -> Result<()> { // Construct the priority fee. let priority_fee_in_microcredits = U64::new(10); + // Determine whether the authorization service will sign the request. + if !args.generate_requests { + sign_authorize_execute( + &client, + private_key, + recipient, + amount_in_microcredits, + base_fee_in_microcredits, + priority_fee_in_microcredits, + ) + .await + } else { + authorize_execute( + &client, + private_key, + recipient, + amount_in_microcredits, + base_fee_in_microcredits, + priority_fee_in_microcredits, + rng, + ) + .await + } +} + +/// Sends an `AuthorizeRequest` to the authorization service and executes the transaction. +async fn sign_authorize_execute( + client: &Client, + private_key: PrivateKey, + recipient: Value, + amount_in_microcredits: Value, + base_fee_in_microcredits: U64, + priority_fee_in_microcredits: U64, +) -> Result<()> { + // Construct the inputs for the request. + let program_id = ProgramID::from_str("credits.aleo")?; + let function_name = Identifier::from_str("transfer_public")?; + let inputs = vec![recipient, amount_in_microcredits]; + // Construct an `AuthorizeRequest`. let authorize_request = AuthorizeRequest:: { private_key, - program_id: ProgramID::from_str("credits.aleo")?, - function_name: Identifier::from_str("transfer_public")?, - inputs: vec![recipient, amount_in_microcredits], + program_id, + function_name, + inputs, base_fee_in_microcredits, priority_fee_in_microcredits, }; @@ -174,3 +227,183 @@ async fn main() -> Result<()> { Ok(()) } + +/// Sends an `AuthorizeSignedRequest` to the authorization service and executes the transaction. +async fn authorize_execute( + client: &Client, + private_key: PrivateKey, + recipient: Value, + amount_in_microcredits: Value, + base_fee_in_microcredits: U64, + priority_fee_in_microcredits: U64, + rng: &mut rand_chacha::ChaCha20Rng, +) -> Result<()> { + // Construct the inputs for the request. + let program_id = ProgramID::from_str("credits.aleo")?; + let function_name = Identifier::from_str("transfer_public")?; + let is_root = true; + let root_tvk = None; + let inputs = vec![recipient, amount_in_microcredits]; + let input_types = [ + ValueType::from_str("address.public").unwrap(), + ValueType::from_str("u64.public").unwrap(), + ]; + // Compute the request. + let request = Request::sign( + &private_key, + program_id, + function_name, + inputs.into_iter(), + &input_types, + root_tvk, + is_root, + rng, + )?; + + // Construct an `AuthorizeSignRequest`. + let authorize_request = AuthorizeSignedRequest:: { request }; + + // Send the request. + let response = client + .post(AUTHORIZE_SIGNED_URL) + .json(&authorize_request) + .send() + .await?; + + // If the request was successful, deserialize the response as an `AuthorizeSignedResponse`. + let authorize_response = match response.status().is_success() { + true => { + response + .json::>() + .await? + } + false => bail!( + "Authorization request failed with status: {}", + response.status() + ), + }; + + // NOTE: in this example, it is critical for security reasons that the + // client computes the execution id, and checks that the tcm/scm matches the + // request. + let execution_id = authorize_response.authorization.to_execution_id()?; + + // Construct the inputs for the fee request. + let program_id = ProgramID::from_str("credits.aleo")?; + let function_name = Identifier::from_str("fee_public")?; + let is_root = true; + let root_tvk = None; + let inputs = vec![ + Value::from(Literal::U64(base_fee_in_microcredits)), + Value::from(Literal::U64(priority_fee_in_microcredits)), + Value::from(Literal::Field(execution_id)), + ]; + let input_types = [ + ValueType::from_str("u64.public").unwrap(), + ValueType::from_str("u64.public").unwrap(), + ValueType::from_str("field.public").unwrap(), + ]; + // Compute the request. + let request = Request::sign( + &private_key, + program_id, + function_name, + inputs.into_iter(), + &input_types, + root_tvk, + is_root, + rng, + )?; + + // Construct an `AuthorizeSignRequest`. + let authorize_request = AuthorizeSignedRequest:: { request }; + + // Send the request. + let response = client + .post(AUTHORIZE_SIGNED_URL) + .json(&authorize_request) + .send() + .await?; + + // If the request was successful, deserialize the response as an `AuthorizeSignedResponse`. + let authorize_fee_response = match response.status().is_success() { + true => { + response + .json::>() + .await? + } + false => bail!( + "Authorization request failed with status: {}", + response.status() + ), + }; + + // Get the latest state root. + let response = client.get(STATE_ROOT_URL).send().await?; + + // If the request was successful, deserialize the response JSON as a `StateRoot`. + let state_root = match response.status().is_success() { + true => { + response + .json::<::StateRoot>() + .await? + } + false => bail!( + "State root request failed with status: {}", + response.status() + ), + }; + + println!("Using state root: {}", state_root); + + // Construct an `ExecuteRequest`. + let execute_request = ExecuteRequest:: { + function_authorization: authorize_response.authorization, + fee_authorization: authorize_fee_response.authorization, + state_root: Some(state_root), + state_path: None, + }; + + // Send the request. + let response = client + .post(EXECUTE_URL) + .body(execute_request.to_bytes_le()?) + .header("Content-Type", "application/octet-stream") + .send() + .await?; + + // If the request was successful, deserialize the response archive as a `Transaction`. + let transaction = match response.status().is_success() { + true => { + let bytes = response.bytes().await?; + Transaction::::from_bytes_le(&bytes)? + } + false => bail!( + "Execution request failed with status: {}", + response.status() + ), + }; + + // Send the transaction as a broadcast request as JSON. + let response = client.post(BROADCAST_URL).json(&transaction).send().await?; + + // If the request was successful, print the response and the response body. + match response.status().is_success() { + true => { + println!( + "Broadcast request succeeded with status: {}", + response.status() + ); + println!( + "Broadcast request response body: {}", + response.text().await? + ); + } + false => bail!( + "Broadcast request failed with status: {}", + response.status() + ), + } + + Ok(()) +} diff --git a/execute-service/src/process_variant.rs b/execute-service/src/process_variant.rs index 810d83e..d1a175c 100644 --- a/execute-service/src/process_variant.rs +++ b/execute-service/src/process_variant.rs @@ -74,7 +74,7 @@ impl ProcessVariant { let (_, mut trace) = process.execute::(function_authorization, rng)?; // Prepare the trace. - trace.prepare(query.clone())?; + trace.prepare(&query.clone())?; // Compute the proof and construct the execution. let execution = trace.prove_execution::(&locator, VarunaVersion::V2, rng)?; @@ -83,7 +83,7 @@ impl ProcessVariant { let (_, mut trace) = process.execute::(fee_authorization, rng)?; // Prepare the trace. - trace.prepare(query)?; + trace.prepare(&query)?; // Compute the proof and construct the fee. let fee = trace.prove_fee::(VarunaVersion::V2, rng)?;