diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 25543e21f6..c404b2c1b7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -67,6 +67,28 @@ jobs: - name: make - build-wasm run: make build-wasm + build-no-std: + name: Build bare metal (no_std) + runs-on: warp-ubuntu-latest-x64-8x + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + persist-credentials: false + - name: Cleanup large tools for build space + uses: ./.github/actions/cleanup-runner + - name: Install Rust toolchain + run: | + rustup update --no-self-update + rustup target add thumbv7em-none-eabihf + - name: Add Rust Cache + uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 + with: + shared-key: rust-release + prefix-key: ${{ env.RUST_CACHE_KEY }} + save-if: false + - name: make - build-no-std + run: make build-no-std + install: name: Install ${{ matrix.target }} runs-on: ubuntu-latest diff --git a/CHANGELOG.md b/CHANGELOG.md index c888c20734..77884ec5f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ * [FIX][rust] Refreshed tracked input notes after transport imports so the same sync detects their consumption ([#2453](https://github.com/0xMiden/rust-sdk/pull/2453)). * [FIX][rust] A private note fetched from the Note Transport Layer whose nullifier is already on chain is now imported as consumed instead of committed, so `get_consumable_notes` no longer reports notes the node will reject ([#2453](https://github.com/0xMiden/rust-sdk/pull/2453)). +* [FIX][rust] `miden-client` no longer pulls the `tonic` runtime into builds without the `tonic` or `std` features, restoring support for bare-metal `no_std` targets. * [FIX][rust] Added validation of cached transaction encryption keys during deserialization. Unsupported encryption schemes and empty or oversized key IDs are rejected before reading the key ID bytes ([#2411](https://github.com/0xMiden/rust-sdk/pull/2411)). * [FIX][cli] `notes --list consumable` now respects the `--account-id` filter ([#2449](https://github.com/0xMiden/rust-sdk/pull/2449)). * [store] Simplified `SqliteStore::get_setting` to take `&Connection` directly without opening an unnecessary transaction ([#2449](https://github.com/0xMiden/rust-sdk/pull/2449)). diff --git a/Cargo.lock b/Cargo.lock index 4309716253..0b472c7a93 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2598,7 +2598,6 @@ dependencies = [ "rand 0.10.2", "rand_chacha 0.10.0", "serde", - "serde_json", "tempfile", "thiserror", "tokio", diff --git a/Makefile b/Makefile index d70be36700..5800eb6011 100644 --- a/Makefile +++ b/Makefile @@ -182,6 +182,10 @@ build: ## Build the CLI binary, client library and tests binary in release mode build-wasm: ## Build the client library for wasm32 with no_std (no default features) cargo build --package miden-client --target wasm32-unknown-unknown --no-default-features --locked +.PHONY: build-no-std +build-no-std: ## Build the client library for a bare-metal no_std target + cargo build --package miden-client --target thumbv7em-none-eabihf --no-default-features --locked + # --- Check --------------------------------------------------------------------------------------- .PHONY: check diff --git a/crates/rust-client/Cargo.toml b/crates/rust-client/Cargo.toml index 162649d758..7234e628e4 100644 --- a/crates/rust-client/Cargo.toml +++ b/crates/rust-client/Cargo.toml @@ -52,16 +52,24 @@ dap = ["dep:miden-debug", "std"] default = ["std"] std = [ "concurrent", + "dep:chrono", "dep:tempfile", "dep:tokio", + "dep:tonic-prost", + "futures/std", + "hex/std", "miden-agglayer/std", "miden-assembly-syntax/std", "miden-processor/std", "miden-protocol/std", "miden-tx/std", + "rand/std", + "rand/thread_rng", + "serde/std", "tonic/tls-native-roots", "tonic/tls-ring", "tonic/transport", + "tracing/std", ] testing = [ "dep:miden-testing", @@ -71,7 +79,12 @@ testing = [ "miden-standards/testing", "miden-tx/testing", ] -tonic = [] +tonic = [ + "dep:tonic", + "dep:tonic-health", + "dep:tonic-prost", + "dep:tonic-web-wasm-client", +] [dependencies] # Miden dependencies @@ -88,28 +101,27 @@ miden-tx-batch = { workspace = true } # External dependencies anyhow = { workspace = true } async-trait = { workspace = true } -chrono = { workspace = true } -futures = { version = "0.3" } -hex = { workspace = true } +chrono = { optional = true, workspace = true } +futures = { default-features = false, features = ["alloc", "async-await"], version = "0.3" } +hex = { default-features = false, features = ["alloc"], workspace = true } prost = { features = ["derive"], workspace = true } prost-types = { version = "0.14" } -rand = { workspace = true } -serde = { workspace = true } -serde_json = { workspace = true } +rand = { default-features = false, workspace = true } +serde = { default-features = false, features = ["derive"], workspace = true } tempfile = { optional = true, workspace = true } thiserror = { workspace = true } tokio = { features = ["sync", "time"], optional = true, workspace = true } -tonic = { features = ["codegen"], workspace = true } -tonic-health = { version = "0.14" } -tonic-prost = { version = "0.14" } -tracing = { workspace = true } +tonic = { features = ["codegen"], optional = true, workspace = true } +tonic-health = { optional = true, version = "0.14" } +tonic-prost = { optional = true, version = "0.14" } +tracing = { default-features = false, workspace = true } uuid = { features = ["js", "serde", "v4"], optional = true, workspace = true } [target.'cfg(target_arch = "wasm32")'.dependencies] getrandom = { features = ["wasm_js"], version = "0.3" } gloo-timers = { features = ["futures"], version = "0.3" } tokio = { default-features = false, features = ["sync"], version = "1.48" } -tonic-web-wasm-client = { default-features = false, version = "0.9" } +tonic-web-wasm-client = { default-features = false, optional = true, version = "0.9" } [build-dependencies] miden-node-proto-build = { workspace = true } diff --git a/crates/rust-client/build.rs b/crates/rust-client/build.rs index 0f87ecd6ba..70498342c3 100644 --- a/crates/rust-client/build.rs +++ b/crates/rust-client/build.rs @@ -46,6 +46,11 @@ fn main() -> miette::Result<()> { Ok(()) } +/// Whether transport-capable client bindings are needed for a no-std build. +fn tonic_feature_enabled() -> bool { + std::env::var_os("CARGO_FEATURE_TONIC").is_some() +} + // REMOTE PROVER CLIENT PROTO CODEGEN // =============================================================================================== @@ -61,6 +66,7 @@ fn compile_tonic_remote_prover_proto(out_dir: &Path) -> miette::Result<()> { // The `nostd` bindings target `wasm32`, where the transport is provided by // `tonic-web-wasm-client`, so tonic's own transport codegen must be disabled. tonic_prost_build::configure() + .build_client(tonic_feature_enabled()) .build_transport(false) .build_server(false) .out_dir(&nostd_out) @@ -98,6 +104,7 @@ fn compile_tonic_note_transport_proto(out_dir: &Path) -> miette::Result<()> { // Generate the header of the user facing server from its proto file tonic_prost_build::configure() + .build_client(tonic_feature_enabled()) .build_transport(false) .build_server(false) .out_dir(&nostd_out) @@ -136,6 +143,7 @@ fn compile_tonic_client_proto(out_dir: &Path) -> miette::Result<()> { // Generate the header of the user facing server from its proto file tonic_prost_build::configure() + .build_client(tonic_feature_enabled()) .build_transport(false) .build_server(false) .out_dir(&nostd_out) diff --git a/crates/rust-client/src/builder.rs b/crates/rust-client/src/builder.rs index 3541e8ae9c..134b494803 100644 --- a/crates/rust-client/src/builder.rs +++ b/crates/rust-client/src/builder.rs @@ -474,9 +474,17 @@ where let rng = if let Some(user_rng) = self.rng { user_rng } else { - let mut seed_rng = rand::rng(); - let coin_seed: [u64; 4] = seed_rng.random(); - Box::new(RandomCoin::new(coin_seed.map(Felt::new_unchecked).into())) + #[cfg(not(feature = "std"))] + return Err(ClientError::ClientInitializationError( + "RNG is required in no_std builds. Call `.rng(...)`.".into(), + )); + + #[cfg(feature = "std")] + { + let mut seed_rng = rand::rng(); + let coin_seed: [u64; 4] = seed_rng.random(); + Box::new(RandomCoin::new(coin_seed.map(Felt::new_unchecked).into())) + } }; let tx_prover: Arc = diff --git a/crates/rust-client/src/store/note_record/input_note_record/states/mod.rs b/crates/rust-client/src/store/note_record/input_note_record/states/mod.rs index dbbb71ffb7..d204f2c684 100644 --- a/crates/rust-client/src/store/note_record/input_note_record/states/mod.rs +++ b/crates/rust-client/src/store/note_record/input_note_record/states/mod.rs @@ -1,6 +1,7 @@ -use alloc::string::ToString; +use alloc::string::{String, ToString}; use core::fmt::{self, Display}; +#[cfg(feature = "std")] use chrono::{Local, TimeZone}; use miden_protocol::account::AccountId; use miden_protocol::block::{BlockHeader, BlockNumber}; @@ -308,17 +309,7 @@ impl Display for InputNoteState { write!( f, "Processing (submitted at {} by account {})", - submission_data.submitted_at.map_or("?".to_string(), |submitted_at| { - Local - .timestamp_opt( - i64::try_from(submitted_at) - .expect("i64::MAX as timestamp is year 2262"), - 0, - ) - .single() - .expect("timestamp should be valid") - .to_string() - }), + submission_data.submitted_at.map_or("?".to_string(), format_timestamp), submission_data.consumer_account ) }, @@ -355,6 +346,23 @@ impl Display for InputNoteState { } } +#[cfg(feature = "std")] +fn format_timestamp(timestamp: u64) -> String { + Local + .timestamp_opt( + i64::try_from(timestamp).expect("i64::MAX as timestamp is year 2262"), + 0, + ) + .single() + .expect("timestamp should be valid") + .to_string() +} + +#[cfg(not(feature = "std"))] +fn format_timestamp(timestamp: u64) -> String { + timestamp.to_string() +} + pub trait NoteStateHandler { fn metadata(&self) -> Option<&NoteMetadata>;