diff --git a/CHANGELOG.md b/CHANGELOG.md index b47774c24..b9b38ba6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog * Standardised CI and Makefile across Miden repositories (#367) +* Remove client dependency from faucet (#368). ## 0.3.0 (2024-05-15) diff --git a/Cargo.lock b/Cargo.lock index 8b5d8c35b..156d114a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -720,18 +720,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" -[[package]] -name = "comfy-table" -version = "7.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b34115915337defe99b2aff5c2ce6771e5fbc4079f4b506301f5cf394c8452f7" -dependencies = [ - "crossterm", - "strum", - "strum_macros", - "unicode-width", -] - [[package]] name = "constant_time_eq" version = "0.3.0" @@ -804,28 +792,6 @@ version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" -[[package]] -name = "crossterm" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" -dependencies = [ - "bitflags 2.5.0", - "crossterm_winapi", - "libc", - "parking_lot", - "winapi", -] - -[[package]] -name = "crossterm_winapi" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" -dependencies = [ - "winapi", -] - [[package]] name = "crypto-common" version = "0.1.6" @@ -1571,33 +1537,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "miden-client" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "105df69459e5d5a164ed2bb7794a561bafee5eac3462ec2281bf829181f9307c" -dependencies = [ - "async-trait", - "clap", - "comfy-table", - "figment", - "lazy_static", - "miden-lib", - "miden-node-proto 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "miden-objects", - "miden-tx", - "rand", - "rusqlite", - "rusqlite_migration", - "serde", - "serde_json", - "tokio", - "toml", - "tonic", - "tracing", - "tracing-subscriber", -] - [[package]] name = "miden-core" version = "0.9.1" @@ -1622,7 +1561,6 @@ dependencies = [ "num-complex", "rand", "rand_core", - "serde", "sha3", "winter-crypto", "winter-math", @@ -1639,22 +1577,23 @@ dependencies = [ "async-mutex", "derive_more", "figment", - "miden-client", "miden-lib", - "miden-node-proto 0.3.0", - "miden-node-utils 0.3.0", + "miden-node-proto", + "miden-node-utils", "miden-objects", + "miden-tx", + "rand", "rand_chacha", "serde", "thiserror", + "tonic", "tracing", ] [[package]] name = "miden-lib" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba8c32dda69e31d901eb7c8cefa4d2c45eac00710360206a5bf331a1ffb2fb42" +version = "0.4.0" +source = "git+https://github.com/0xPolygonMiden/miden-base.git?branch=next#b2dee3633df1159702c50f6274e4277e10f827e1" dependencies = [ "miden-assembly", "miden-objects", @@ -1672,7 +1611,7 @@ dependencies = [ "miden-node-block-producer", "miden-node-rpc", "miden-node-store", - "miden-node-utils 0.3.0", + "miden-node-utils", "miden-objects", "rand_chacha", "serde", @@ -1689,10 +1628,10 @@ dependencies = [ "figment", "itertools", "miden-air", - "miden-node-proto 0.3.0", + "miden-node-proto", "miden-node-store", "miden-node-test-macro", - "miden-node-utils 0.3.0", + "miden-node-utils", "miden-objects", "miden-processor", "miden-stdlib", @@ -1713,7 +1652,7 @@ name = "miden-node-proto" version = "0.3.0" dependencies = [ "hex", - "miden-node-utils 0.3.0", + "miden-node-utils", "miden-objects", "miette", "proptest", @@ -1725,24 +1664,6 @@ dependencies = [ "tonic-build", ] -[[package]] -name = "miden-node-proto" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cad7cb698a5e3cd387eca9380c0c6da1eea262b9527967b6eb755da3699b5b0" -dependencies = [ - "hex", - "miden-node-utils 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "miden-objects", - "miette", - "prost", - "prost-build", - "protox", - "thiserror", - "tonic", - "tonic-build", -] - [[package]] name = "miden-node-rpc" version = "0.3.0" @@ -1751,9 +1672,9 @@ dependencies = [ "figment", "hex", "miden-node-block-producer", - "miden-node-proto 0.3.0", + "miden-node-proto", "miden-node-store", - "miden-node-utils 0.3.0", + "miden-node-utils", "miden-objects", "miden-tx", "prost", @@ -1775,8 +1696,8 @@ dependencies = [ "figment", "hex", "miden-lib", - "miden-node-proto 0.3.0", - "miden-node-utils 0.3.0", + "miden-node-proto", + "miden-node-utils", "miden-objects", "once_cell", "prost", @@ -1815,35 +1736,17 @@ dependencies = [ "tracing-subscriber", ] -[[package]] -name = "miden-node-utils" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72178203f905caebaa6d33e44a8f45b9b8c2a3d98f7b40fffb83b7fd47cb2869" -dependencies = [ - "anyhow", - "figment", - "itertools", - "miden-objects", - "serde", - "thiserror", - "tonic", - "tracing", - "tracing-subscriber", -] - [[package]] name = "miden-objects" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5150d3c6c6ad5db90c9bef39870aaa8e4fea0fab95c9db0173708d7ce4ee34b" +version = "0.4.0" +source = "git+https://github.com/0xPolygonMiden/miden-base.git?branch=next#b2dee3633df1159702c50f6274e4277e10f827e1" dependencies = [ "miden-assembly", "miden-core", "miden-crypto", "miden-processor", "miden-verifier", - "serde", + "rand", "winter-rand-utils", ] @@ -1882,9 +1785,8 @@ dependencies = [ [[package]] name = "miden-tx" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a927829fd121ed73695e2ec472d222db0884a12f46c8f8d97cf0a82c8d4e0a" +version = "0.4.0" +source = "git+https://github.com/0xPolygonMiden/miden-base.git?branch=next#b2dee3633df1159702c50f6274e4277e10f827e1" dependencies = [ "miden-lib", "miden-objects", @@ -2794,25 +2696,6 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "strum" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" - -[[package]] -name = "strum_macros" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.66", -] - [[package]] name = "supports-color" version = "3.0.0" @@ -3696,7 +3579,6 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c36d2a04b4f79f2c8c6945aab6545b7310a0cd6ae47b9210750400df6775a04" dependencies = [ - "serde", "winter-utils", ] diff --git a/Cargo.toml b/Cargo.toml index 26842d319..4d8f2be7d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ readme = "README.md" [workspace.dependencies] miden-air = { version = "0.9", default-features = false } -miden-lib = { version = "0.3" } +miden-lib = { package = "miden-lib", git = "https://github.com/0xPolygonMiden/miden-base.git", branch = "next" } miden-node-block-producer = { path = "crates/block-producer", version = "0.3" } miden-node-faucet = { path = "bin/faucet", version = "0.3" } miden-node-proto = { path = "crates/proto", version = "0.3" } @@ -33,10 +33,11 @@ miden-node-rpc = { path = "crates/rpc", version = "0.3" } miden-node-store = { path = "crates/store", version = "0.3" } miden-node-test-macro = { path = "crates/test-macro" } miden-node-utils = { path = "crates/utils", version = "0.3" } -miden-objects = { version = "0.3" } +miden-objects = { package = "miden-objects", git = "https://github.com/0xPolygonMiden/miden-base.git", branch = "next" } miden-processor = { version = "0.9" } miden-stdlib = { version = "0.9", default-features = false } -miden-tx = { version = "0.3" } +miden-tx = { package = "miden-tx", git = "https://github.com/0xPolygonMiden/miden-base.git", branch = "next" } thiserror = { version = "1.0" } +tonic = { version = "0.11" } tracing = { version = "0.1" } tracing-subscriber = { version = "0.3", features = ["fmt", "json", "env-filter"] } diff --git a/bin/faucet/Cargo.toml b/bin/faucet/Cargo.toml index f6cc6df53..22ebfe1b5 100644 --- a/bin/faucet/Cargo.toml +++ b/bin/faucet/Cargo.toml @@ -14,7 +14,7 @@ repository.workspace = true [features] # Makes `make-genesis` subcommand run faster. Is only suitable for testing. # INFO: Make sure that all your components have matching features for them to function. -testing = ["miden-client/testing"] +testing = ["miden-objects/testing", "miden-lib/testing"] [dependencies] actix-cors = "0.7.0" @@ -23,12 +23,14 @@ actix-web = "4" async-mutex = "1.4.0" derive_more = "0.99.17" figment = { version = "0.10", features = ["toml", "env"] } -miden-client = { version = "0.3", features = ["concurrent"] } -miden-lib = { version = "0.3" } +miden-lib = { workspace = true, features = ["concurrent"] } miden-node-proto = { workspace = true } miden-node-utils = { workspace = true } -miden-objects = { version = "0.3" } +miden-objects = { workspace = true , features = ["concurrent"] } +miden-tx = { workspace = true, features = ["concurrent"] } +rand = { version = "0.8.5" } rand_chacha = "0.3" serde = { version = "1.0", features = ["derive"] } -tracing = { workspace = true } thiserror = { workspace = true } +tonic = { workspace = true } +tracing = { workspace = true } diff --git a/bin/faucet/src/client.rs b/bin/faucet/src/client.rs new file mode 100644 index 000000000..9242160b6 --- /dev/null +++ b/bin/faucet/src/client.rs @@ -0,0 +1,332 @@ +use std::{cell::RefCell, rc::Rc, time::Duration}; + +use miden_lib::{ + accounts::faucets::create_basic_fungible_faucet, notes::create_p2id_note, AuthScheme, +}; +use miden_node_proto::generated::{ + requests::{GetBlockHeaderByNumberRequest, SubmitProvenTransactionRequest}, + rpc::api_client::ApiClient, +}; +use miden_objects::{ + accounts::{Account, AccountDelta, AccountId, AccountStorageType, AuthSecretKey}, + assembly::{ModuleAst, ProgramAst}, + assets::{FungibleAsset, TokenSymbol}, + crypto::{ + dsa::rpo_falcon512::SecretKey, + merkle::{MmrPeaks, PartialMmr}, + rand::RpoRandomCoin, + }, + notes::{Note, NoteId, NoteType}, + transaction::{ChainMmr, ExecutedTransaction, InputNotes, TransactionArgs}, + vm::AdviceMap, + BlockHeader, Felt, Word, +}; +use miden_tx::{ + auth::BasicAuthenticator, utils::Serializable, DataStore, DataStoreError, ProvingOptions, + TransactionExecutor, TransactionInputs, TransactionProver, +}; +use rand::{rngs::StdRng, thread_rng, Rng}; +use rand_chacha::{rand_core::SeedableRng, ChaCha20Rng}; +use tonic::transport::Channel; + +use crate::{config::FaucetConfig, errors::FaucetError}; + +pub const DISTRIBUTE_FUNGIBLE_ASSET_SCRIPT: &str = + include_str!("transaction_scripts/distribute_fungible_asset.masm"); + +// FAUCET CLIENT +// ================================================================================================ + +/// Basic client that handles execution, proving and submiting of mint transactions +/// for the faucet. +pub struct FaucetClient { + rpc_api: ApiClient, + executor: TransactionExecutor>, + data_store: FaucetDataStore, + id: AccountId, + rng: RpoRandomCoin, +} + +unsafe impl Send for FaucetClient {} +unsafe impl Sync for FaucetClient {} + +impl FaucetClient { + pub async fn new(config: FaucetConfig) -> Result { + let (rpc_api, root_block_header, root_chain_mmr) = + initialize_faucet_client(config.clone()).await?; + + let (faucet_account, account_seed, secret) = build_account(config.clone())?; + let faucet_account = Rc::new(RefCell::new(faucet_account)); + let id = faucet_account.borrow().id(); + + let data_store = FaucetDataStore::new( + faucet_account.clone(), + account_seed, + root_block_header, + root_chain_mmr, + ); + let authenticator = BasicAuthenticator::::new(&[( + secret.public_key().into(), + AuthSecretKey::RpoFalcon512(secret), + )]); + let mut executor = + TransactionExecutor::new(data_store.clone(), Some(Rc::new(authenticator))); + + executor + .load_account(id) + .map_err(|err| FaucetError::InternalServerError(err.to_string()))?; + + let mut rng = thread_rng(); + let coin_seed: [u64; 4] = rng.gen(); + let rng = RpoRandomCoin::new(coin_seed.map(Felt::new)); + Ok(Self { data_store, rpc_api, executor, id, rng }) + } + + /// Executes a mint transaction for the target account. + /// + /// Returns the executed transaction and the expected output note. + pub fn execute_mint_transaction( + &mut self, + target_account_id: AccountId, + is_private_note: bool, + asset_amount: u64, + ) -> Result<(ExecutedTransaction, Note), FaucetError> { + let asset = FungibleAsset::new(self.id, asset_amount) + .map_err(|err| FaucetError::InternalServerError(err.to_string()))?; + + let note_type = if is_private_note { + NoteType::OffChain + } else { + NoteType::Public + }; + + let output_note = create_p2id_note( + self.id, + target_account_id, + vec![asset.into()], + note_type, + &mut self.rng, + ) + .map_err(|err| FaucetError::InternalServerError(err.to_string()))?; + + let transaction_args = + build_transaction_arguments(&output_note, &self.executor, note_type, asset)?; + + let executed_tx = self + .executor + .execute_transaction(self.id, 0, &[], transaction_args) + .map_err(|err| { + FaucetError::InternalServerError(format!("Failed to execute transaction: {}", err)) + })?; + + Ok((executed_tx, output_note)) + } + + /// Proves and submits the executed transaction to the node. + pub async fn prove_and_submit_transaction( + &mut self, + executed_tx: ExecutedTransaction, + ) -> Result<(), FaucetError> { + let transaction_prover = TransactionProver::new(ProvingOptions::default()); + + let delta = executed_tx.account_delta().clone(); + + let proven_transaction = + transaction_prover.prove_transaction(executed_tx).map_err(|err| { + FaucetError::InternalServerError(format!("Failed to prove transaction: {}", err)) + })?; + + let request = SubmitProvenTransactionRequest { + transaction: proven_transaction.to_bytes(), + }; + + self.rpc_api + .submit_proven_transaction(request) + .await + .map_err(|err| FaucetError::InternalServerError(err.to_string()))?; + + self.data_store.update_faucet_account(&delta).map_err(|err| { + FaucetError::InternalServerError(format!("Failed to update account: {}", err)) + })?; + + Ok(()) + } + + pub fn get_faucet_id(&self) -> AccountId { + self.id + } +} + +#[derive(Clone)] +pub struct FaucetDataStore { + faucet_account: Rc>, + seed: Word, + block_header: BlockHeader, + chain_mmr: ChainMmr, +} + +// FAUCET DATA STORE +// ================================================================================================ + +impl FaucetDataStore { + pub fn new( + faucet_account: Rc>, + seed: Word, + root_block_header: BlockHeader, + root_chain_mmr: ChainMmr, + ) -> Self { + Self { + faucet_account, + seed, + block_header: root_block_header, + chain_mmr: root_chain_mmr, + } + } + + /// Updates the stored faucet account with the provided delta. + fn update_faucet_account(&mut self, delta: &AccountDelta) -> Result<(), FaucetError> { + self.faucet_account + .borrow_mut() + .apply_delta(delta) + .map_err(|err| FaucetError::InternalServerError(err.to_string())) + } +} + +impl DataStore for FaucetDataStore { + fn get_transaction_inputs( + &self, + account_id: AccountId, + _block_ref: u32, + _notes: &[NoteId], + ) -> Result { + let account = self.faucet_account.borrow(); + if account_id != account.id() { + return Err(DataStoreError::AccountNotFound(account_id)); + } + + let empty_input_notes = + InputNotes::new(Vec::new()).map_err(DataStoreError::InvalidTransactionInput)?; + + TransactionInputs::new( + account.clone(), + account.is_new().then_some(self.seed), + self.block_header, + self.chain_mmr.clone(), + empty_input_notes, + ) + .map_err(DataStoreError::InvalidTransactionInput) + } + + fn get_account_code(&self, account_id: AccountId) -> Result { + let account = self.faucet_account.borrow(); + if account_id != account.id() { + return Err(DataStoreError::AccountNotFound(account_id)); + } + + let module_ast = account.code().module().clone(); + Ok(module_ast) + } +} + +// HELPER FUNCTIONS +// ================================================================================================ + +/// Builds a new faucet account with the provided configuration. +/// +/// Returns the created account, its seed, and the secret key used to sign transactions. +fn build_account(config: FaucetConfig) -> Result<(Account, Word, SecretKey), FaucetError> { + let token_symbol = TokenSymbol::new(config.token_symbol.as_str()) + .map_err(|err| FaucetError::AccountCreationError(err.to_string()))?; + + let seed: [u8; 32] = [0; 32]; + + // Instantiate keypair and authscheme + let mut rng = ChaCha20Rng::from_seed(seed); + let secret = SecretKey::with_rng(&mut rng); + let auth_scheme = AuthScheme::RpoFalcon512 { pub_key: secret.public_key() }; + + let (faucet_account, account_seed) = create_basic_fungible_faucet( + seed, + token_symbol, + config.decimals, + Felt::try_from(config.max_supply) + .map_err(|err| FaucetError::InternalServerError(err.to_string()))?, + AccountStorageType::OffChain, + auth_scheme, + ) + .map_err(|err| FaucetError::AccountCreationError(err.to_string()))?; + + Ok((faucet_account, account_seed, secret)) +} + +/// Initializes the faucet client by connecting to the node and fetching the root block header. +pub async fn initialize_faucet_client( + config: FaucetConfig, +) -> Result<(ApiClient, BlockHeader, ChainMmr), FaucetError> { + let endpoint = tonic::transport::Endpoint::try_from(config.node_url.clone()) + .map_err(|_| FaucetError::InternalServerError("Failed to connect to node.".to_string()))? + .timeout(Duration::from_millis(config.timeout_ms)); + + let mut rpc_api = ApiClient::connect(endpoint) + .await + .map_err(|err| FaucetError::InternalServerError(err.to_string()))?; + + let request = GetBlockHeaderByNumberRequest { + block_num: Some(0), + include_mmr_proof: Some(true), + }; + let response = rpc_api.get_block_header_by_number(request).await.map_err(|err| { + FaucetError::InternalServerError(format!("Failed to get block header: {}", err)) + })?; + let root_block_header = response.into_inner().block_header.unwrap(); + + let root_block_header: BlockHeader = root_block_header.try_into().map_err(|err| { + FaucetError::InternalServerError(format!("Failed to parse block header: {}", err)) + })?; + + let root_chain_mmr = ChainMmr::new( + PartialMmr::from_peaks( + MmrPeaks::new(0, Vec::new()).expect("Empty MmrPeak should be valid"), + ), + Vec::new(), + ) + .expect("Empty ChainMmr should be valid"); + + Ok((rpc_api, root_block_header, root_chain_mmr)) +} + +/// Builds transaction arguments for the mint transaction. +fn build_transaction_arguments( + output_note: &Note, + executor: &TransactionExecutor>, + note_type: NoteType, + asset: FungibleAsset, +) -> Result { + let recipient = output_note + .recipient() + .digest() + .iter() + .map(|x| x.as_int().to_string()) + .collect::>() + .join("."); + + let tag = output_note.metadata().tag().inner(); + + let script = ProgramAst::parse( + &DISTRIBUTE_FUNGIBLE_ASSET_SCRIPT + .replace("{recipient}", &recipient) + .replace("{note_type}", &Felt::new(note_type as u64).to_string()) + .replace("{tag}", &Felt::new(tag.into()).to_string()) + .replace("{amount}", &Felt::new(asset.amount()).to_string()), + ) + .expect("shipped MASM is well-formed"); + + let script = executor.compile_tx_script(script, vec![], vec![]).map_err(|err| { + FaucetError::InternalServerError(format!("Failed to compile script: {}", err)) + })?; + + let mut transaction_args = TransactionArgs::new(Some(script), None, AdviceMap::new()); + transaction_args.extend_expected_output_notes(vec![output_note.clone()]); + + Ok(transaction_args) +} diff --git a/bin/faucet/src/config.rs b/bin/faucet/src/config.rs index 8ddec75b5..ccfca65f6 100644 --- a/bin/faucet/src/config.rs +++ b/bin/faucet/src/config.rs @@ -15,6 +15,8 @@ pub struct FaucetConfig { pub endpoint: Endpoint, /// Node RPC gRPC endpoint in the format `http://[:]`. pub node_url: String, + /// Timeout for RPC requests in milliseconds + pub timeout_ms: u64, /// Location to store database files pub database_filepath: PathBuf, /// Possible options on the amount of asset that should be dispered on each faucet request diff --git a/bin/faucet/src/errors.rs b/bin/faucet/src/errors.rs index f95e89ce8..4e167e855 100644 --- a/bin/faucet/src/errors.rs +++ b/bin/faucet/src/errors.rs @@ -3,7 +3,6 @@ use actix_web::{ http::{header::ContentType, StatusCode}, HttpResponse, }; -use miden_client::errors::ClientError; use thiserror::Error; #[derive(Debug, Error)] @@ -20,12 +19,6 @@ pub enum FaucetError { #[error("Server has encountered an internal error: {0}")] InternalServerError(String), - #[error("Database has encountered an error: {0}")] - DatabaseError(String), - - #[error("Failed to sync state: {0}")] - SyncError(ClientError), - #[error("Failed to create Miden account: {0}")] AccountCreationError(String), } @@ -37,9 +30,7 @@ impl error::ResponseError for FaucetError { FaucetError::BadRequest(msg) => msg.to_string(), FaucetError::ConfigurationError(msg) => msg.to_string(), FaucetError::InternalServerError(msg) => msg.to_string(), - FaucetError::SyncError(msg) => msg.to_string(), FaucetError::AccountCreationError(msg) => msg.to_string(), - FaucetError::DatabaseError(msg) => msg.to_string(), }; HttpResponse::build(self.status_code()) diff --git a/bin/faucet/src/handlers.rs b/bin/faucet/src/handlers.rs index 9b83a6b43..8f3eb77f3 100644 --- a/bin/faucet/src/handlers.rs +++ b/bin/faucet/src/handlers.rs @@ -1,21 +1,13 @@ use actix_web::{get, http::header, post, web, HttpResponse, Result}; -use miden_client::{ - client::transactions::transaction_request::TransactionTemplate, store::InputNoteRecord, -}; use miden_objects::{ accounts::AccountId, - assets::FungibleAsset, - notes::{NoteId, NoteType}, - transaction::OutputNote, + notes::{NoteDetails, NoteFile, NoteId}, utils::serde::Serializable, }; use serde::{Deserialize, Serialize}; use tracing::info; -use crate::{ - errors::FaucetError, - utils::{build_client, FaucetState}, -}; +use crate::{errors::FaucetError, state::FaucetState}; #[derive(Deserialize)] struct FaucetRequest { @@ -34,7 +26,7 @@ struct FaucetMetadataReponse { pub async fn get_metadata(state: web::Data) -> HttpResponse { let response = FaucetMetadataReponse { id: state.id.to_string(), - asset_amount_options: state.faucet_config.asset_amount_options.clone(), + asset_amount_options: state.config.asset_amount_options.clone(), }; HttpResponse::Ok().json(response) @@ -51,64 +43,34 @@ pub async fn get_tokens( ); // Check that the amount is in the asset amount options - if !state.faucet_config.asset_amount_options.contains(&req.asset_amount) { + if !state.config.asset_amount_options.contains(&req.asset_amount) { return Err(FaucetError::BadRequest("Invalid asset amount.".to_string()).into()); } - let client_config = state.faucet_config.clone(); - let mut client = build_client(client_config.database_filepath, &client_config.node_url)?; + let mut client = state.client.lock().await; // Receive and hex user account id let target_account_id = AccountId::from_hex(req.account_id.as_str()) .map_err(|err| FaucetError::BadRequest(err.to_string()))?; - // Instantiate asset - let asset = FungibleAsset::new(state.id, req.asset_amount) - .map_err(|err| FaucetError::InternalServerError(err.to_string()))?; - - // Instantiate note type - let note_type = if req.is_private_note { - NoteType::OffChain - } else { - NoteType::Public - }; - - // Instantiate transaction template - let tx_template = TransactionTemplate::MintFungibleAsset(asset, target_account_id, note_type); - - // Instantiate transaction request - let tx_request = client - .build_transaction_request(tx_template) - .map_err(|err| FaucetError::InternalServerError(err.to_string()))?; - - // Run transaction executor & execute transaction - let tx_result = client - .new_transaction(tx_request) - .map_err(|err| FaucetError::InternalServerError(err.to_string()))?; - - // Get created notes from transaction result - let created_notes = tx_result.created_notes().clone(); + // Execute transaction + info!("Executing mint transaction for account."); + let (executed_tx, created_note) = client.execute_mint_transaction( + target_account_id, + req.is_private_note, + req.asset_amount, + )?; // Run transaction prover & send transaction to node - client - .submit_transaction(tx_result) - .await - .map_err(|err| FaucetError::InternalServerError(err.to_string()))?; + info!("Proving and submitting transaction."); + client.prove_and_submit_transaction(executed_tx).await?; - let note_id: NoteId; + let note_id: NoteId = created_note.id(); + let note_details = + NoteDetails::new(created_note.assets().clone(), created_note.recipient().clone()); // Serialize note into bytes - let bytes = match created_notes.get_note(0) { - OutputNote::Full(note) => { - note_id = note.id(); - InputNoteRecord::from(note.clone()).to_bytes() - }, - OutputNote::Header(_) => { - return Err( - FaucetError::InternalServerError("Failed to generate note.".to_string()).into() - ) - }, - }; + let bytes = NoteFile::NoteDetails(note_details).to_bytes(); info!("A new note has been created: {}", note_id); diff --git a/bin/faucet/src/main.rs b/bin/faucet/src/main.rs index f0a0fa0a5..11706465f 100644 --- a/bin/faucet/src/main.rs +++ b/bin/faucet/src/main.rs @@ -1,7 +1,8 @@ +mod client; mod config; mod errors; mod handlers; -mod utils; +mod state; use std::path::PathBuf; @@ -13,12 +14,12 @@ use actix_web::{ }; use errors::FaucetError; use miden_node_utils::config::load_config; +use state::FaucetState; use tracing::info; use crate::{ config::FaucetConfig, handlers::{get_metadata, get_tokens}, - utils::build_faucet_state, }; // CONSTANTS @@ -40,7 +41,7 @@ async fn main() -> Result<(), FaucetError> { .extract() .map_err(|err| FaucetError::ConfigurationError(err.to_string()))?; - let faucet_state = build_faucet_state(config.clone()).await?; + let faucet_state = FaucetState::new(config.clone()).await?; info!(target: COMPONENT, %config, "Initializing server"); diff --git a/bin/faucet/src/state.rs b/bin/faucet/src/state.rs new file mode 100644 index 000000000..73b14d946 --- /dev/null +++ b/bin/faucet/src/state.rs @@ -0,0 +1,32 @@ +use std::sync::Arc; + +use async_mutex::Mutex; +use miden_objects::accounts::AccountId; +use tracing::info; + +use crate::{client::FaucetClient, config::FaucetConfig, errors::FaucetError}; + +// FAUCET STATE +// ================================================================================================ + +/// Stores the client and aditional information needed to handle requests. +/// +/// The state is passed to every mint transaction request so the client is +/// shared between handler threads. +#[derive(Clone)] +pub struct FaucetState { + pub id: AccountId, + pub client: Arc>, + pub config: FaucetConfig, +} + +impl FaucetState { + pub async fn new(config: FaucetConfig) -> Result { + let client = FaucetClient::new(config.clone()).await?; + let id = client.get_faucet_id(); + let client = Arc::new(Mutex::new(client)); + info!("Faucet initialization successful, account id: {}", id); + + Ok(FaucetState { client, id, config }) + } +} diff --git a/bin/faucet/src/static/index.js b/bin/faucet/src/static/index.js index 732cf4cbd..d069678c4 100644 --- a/bin/faucet/src/static/index.js +++ b/bin/faucet/src/static/index.js @@ -74,13 +74,12 @@ document.addEventListener('DOMContentLoaded', function () { noteIdElem.textContent = noteId; accountIdElem.textContent = accountId; info.style.display = 'block'; - loading.style.display = 'none'; } catch (error) { console.error('Error:', error); errorMessage.textContent = 'Failed to receive tokens. Please try again.'; errorMessage.style.display = 'block'; } - + loading.style.display = 'none'; privateButton.disabled = false; publicButton.disabled = false; } diff --git a/bin/faucet/src/transaction_scripts/distribute_fungible_asset.masm b/bin/faucet/src/transaction_scripts/distribute_fungible_asset.masm new file mode 100644 index 000000000..d26a77558 --- /dev/null +++ b/bin/faucet/src/transaction_scripts/distribute_fungible_asset.masm @@ -0,0 +1,13 @@ +use.miden::contracts::faucets::basic_fungible->faucet +use.miden::contracts::auth::basic->auth_tx + +begin + push.{recipient} + push.{note_type} + push.{tag} + push.{amount} + call.faucet::distribute + + call.auth_tx::auth_tx_rpo_falcon512 + dropw dropw +end diff --git a/bin/faucet/src/utils.rs b/bin/faucet/src/utils.rs deleted file mode 100644 index 1ce54e43d..000000000 --- a/bin/faucet/src/utils.rs +++ /dev/null @@ -1,133 +0,0 @@ -use std::{path::PathBuf, rc::Rc}; - -use miden_client::{ - client::{ - get_random_coin, rpc::TonicRpcClient, store_authenticator::StoreAuthenticator, Client, - }, - config::{Endpoint, RpcConfig, StoreConfig}, - store::sqlite_store::SqliteStore, -}; -use miden_lib::{accounts::faucets::create_basic_fungible_faucet, AuthScheme}; -use miden_objects::{ - accounts::{Account, AccountId, AccountStorageType, AuthSecretKey}, - assets::TokenSymbol, - crypto::{dsa::rpo_falcon512::SecretKey, rand::RpoRandomCoin}, - Felt, -}; -use rand_chacha::{rand_core::SeedableRng, ChaCha20Rng}; -use tracing::info; - -use crate::{config::FaucetConfig, errors::FaucetError}; - -pub type FaucetClient = Client< - TonicRpcClient, - RpoRandomCoin, - SqliteStore, - StoreAuthenticator, ->; - -#[derive(Clone)] -pub struct FaucetState { - pub id: AccountId, - pub faucet_config: FaucetConfig, -} - -/// Instatiantes the Miden faucet -pub async fn build_faucet_state(config: FaucetConfig) -> Result { - let mut client = build_client(config.database_filepath.clone(), &config.node_url)?; - - let faucet_account = create_fungible_faucet( - &config.token_symbol, - &config.decimals, - &config.max_supply, - &mut client, - )?; - - // Sync client - client.sync_state().await.map_err(FaucetError::SyncError)?; - - info!("Faucet initialization successful, account id: {}", faucet_account.id()); - - Ok(FaucetState { - id: faucet_account.id(), - faucet_config: config, - }) -} - -/// Instantiates the Miden client -pub fn build_client( - database_filepath: PathBuf, - node_url: &str, -) -> Result { - let database_filepath_os_string = database_filepath.into_os_string(); - let database_filepath = match database_filepath_os_string.into_string() { - Ok(string) => string, - Err(e) => { - return Err(FaucetError::DatabaseError(format!( - "Failed to read database filepath: {:?}", - e - ))) - }, - }; - - // Setup store - let store_config = StoreConfig { - database_filepath: database_filepath.clone(), - }; - let store = SqliteStore::new(store_config) - .map_err(|err| FaucetError::DatabaseError(err.to_string()))?; - - let store = Rc::new(store); - - // Setup the tonic rpc client - let endpoint = Endpoint::try_from(node_url).map_err(|err| { - FaucetError::ConfigurationError(format!("Error parsing RPC endpoint: {}", err)) - })?; - let rpc_config = RpcConfig { endpoint, ..Default::default() }; - let api = TonicRpcClient::new(&rpc_config); - - // Setup the rng - let rng = get_random_coin(); - let authenticator = StoreAuthenticator::new_with_rng(store.clone(), rng); - - info!("Successfully built client"); - - // Setup the client - Ok(Client::new(api, rng, store, authenticator, false)) -} - -/// Creates a Miden fungible faucet from arguments -pub fn create_fungible_faucet( - token_symbol: &str, - decimals: &u8, - max_supply: &u64, - client: &mut FaucetClient, -) -> Result { - let token_symbol = TokenSymbol::new(token_symbol) - .map_err(|err| FaucetError::AccountCreationError(err.to_string()))?; - - // Instantiate seed - let seed: [u8; 32] = [0; 32]; - - // Instantiate keypair and authscheme - let mut rng = ChaCha20Rng::from_seed(seed); - let secret = SecretKey::with_rng(&mut rng); - let auth_scheme = AuthScheme::RpoFalcon512 { pub_key: secret.public_key() }; - - let (account, account_seed) = create_basic_fungible_faucet( - seed, - token_symbol, - *decimals, - Felt::try_from(*max_supply) - .map_err(|err| FaucetError::InternalServerError(err.to_string()))?, - AccountStorageType::OffChain, - auth_scheme, - ) - .map_err(|err| FaucetError::AccountCreationError(err.to_string()))?; - - client - .insert_account(&account, Some(account_seed), &AuthSecretKey::RpoFalcon512(secret)) - .map_err(|err| FaucetError::DatabaseError(err.to_string()))?; - - Ok(account) -} diff --git a/config/miden-faucet.toml b/config/miden-faucet.toml index d97aa6e50..514ed98aa 100644 --- a/config/miden-faucet.toml +++ b/config/miden-faucet.toml @@ -1,6 +1,7 @@ endpoint = { host = "localhost", port = 8080 } database_filepath = "store.sqlite3" node_url = "http://localhost:57291" +timeout_ms = 10000 # Data used to construct the faucet account of the faucet asset_amount_options = [100, 500, 1000] diff --git a/crates/block-producer/Cargo.toml b/crates/block-producer/Cargo.toml index ee7d0c085..a999b2d67 100644 --- a/crates/block-producer/Cargo.toml +++ b/crates/block-producer/Cargo.toml @@ -29,7 +29,7 @@ serde = { version = "1.0", features = ["derive"] } thiserror = { workspace = true } tokio = { version = "1.29", features = ["rt-multi-thread", "net", "macros", "sync", "time"] } toml = { version = "0.8" } -tonic = { version = "0.11" } +tonic = { workspace = true } tracing = { workspace = true } tracing-subscriber = { workspace = true } diff --git a/crates/proto/Cargo.toml b/crates/proto/Cargo.toml index 5e672e94d..d07de7364 100644 --- a/crates/proto/Cargo.toml +++ b/crates/proto/Cargo.toml @@ -17,7 +17,7 @@ miden-node-utils = { workspace = true } miden-objects = { workspace = true } prost = { version = "0.12" } thiserror = { workspace = true } -tonic = { version = "0.11" } +tonic = { workspace = true } [dev-dependencies] proptest = { version = "1.2" } diff --git a/crates/rpc/Cargo.toml b/crates/rpc/Cargo.toml index 9dab9e920..5c93475c2 100644 --- a/crates/rpc/Cargo.toml +++ b/crates/rpc/Cargo.toml @@ -25,7 +25,7 @@ prost = { version = "0.12" } serde = { version = "1.0", features = ["derive"] } tokio = { version = "1.29", features = ["rt-multi-thread", "net", "macros"] } toml = { version = "0.8" } -tonic = { version = "0.11" } +tonic = { workspace = true } tonic-web = { version = "0.11" } tracing = { workspace = true } tracing-subscriber = { workspace = true } diff --git a/crates/store/Cargo.toml b/crates/store/Cargo.toml index 96b1bd086..e0048e47a 100644 --- a/crates/store/Cargo.toml +++ b/crates/store/Cargo.toml @@ -28,7 +28,7 @@ serde = { version = "1.0", features = ["derive"] } thiserror = { workspace = true } tokio = { version = "1.29", features = ["fs", "net", "macros", "rt-multi-thread"] } toml = { version = "0.8" } -tonic = { version = "0.11" } +tonic = { workspace = true } tracing = { workspace = true } tracing-subscriber = { workspace = true } diff --git a/crates/store/src/db/tests.rs b/crates/store/src/db/tests.rs index 4d7366221..485a8d3ff 100644 --- a/crates/store/src/db/tests.rs +++ b/crates/store/src/db/tests.rs @@ -227,7 +227,7 @@ fn test_sql_public_account_details() { .unwrap(), ); - let mut account = Account::new( + let mut account = Account::from_parts( account_id, AssetVault::new(&[ Asset::Fungible(FungibleAsset::new(fungible_faucet_id, 150).unwrap()), diff --git a/crates/store/src/state.rs b/crates/store/src/state.rs index 9781ad2a3..3daf60940 100644 --- a/crates/store/src/state.rs +++ b/crates/store/src/state.rs @@ -226,7 +226,7 @@ impl State { .map(|(note_index, note)| { let details = match note { OutputNote::Full(note) => Some(note.to_bytes()), - OutputNote::Header(_) => None, + OutputNote::Header(_) | OutputNote::Partial(_) => None, }; let merkle_path = note_tree diff --git a/crates/utils/Cargo.toml b/crates/utils/Cargo.toml index c3850b273..382d4ed79 100644 --- a/crates/utils/Cargo.toml +++ b/crates/utils/Cargo.toml @@ -18,7 +18,7 @@ itertools = { version = "0.12" } miden-objects = { workspace = true } serde = { version = "1.0", features = ["derive"] } thiserror = { workspace = true } -tonic = { version = "0.11" } +tonic = { workspace = true } tracing = { workspace = true } tracing-forest = { version = "0.1", optional = true, features = ["chrono"] } tracing-subscriber = { workspace = true }