Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
* [BREAKING] Introduced named storage slots, changed `FilesystemKeystore` to not be generic over RNG ([#1626](https://github.com/0xMiden/miden-client/pull/1626)).
* Added `submit_new_transaction_with_prover` to the Rust client and `submitNewTransactionWithProver` to the WebClient([#1622](https://github.com/0xMiden/miden-client/pull/1622)).
* [BREAKING] Modified JS binding for `AccountComponent::compile` which now takes an `AccountComponentCode` built with the newly added binding `CodeBuilder::compile_account_component_code` ([#1627](https://github.com/0xMiden/miden-client/pull/1627)).
* [BREAKING] Refactored the `NoteScreener` API ([#1630](https://github.com/0xMiden/miden-client/pull/1630)).

## 0.12.5 (2025-12-01)

Expand Down
20 changes: 19 additions & 1 deletion bin/miden-cli/src/commands/notes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use miden_client::auth::TransactionAuthenticator;
use miden_client::note::{
Note,
NoteConsumability,
NoteConsumptionStatus,
NoteInputs,
NoteMetadata,
WellKnownNote,
Expand Down Expand Up @@ -395,14 +396,31 @@ where
table.add_row(vec![
note.id().to_hex(),
relevance.0.to_string(),
relevance.1.to_string(),
note_consumption_status_type(&relevance.1),
]);
}
}

println!("{table}");
}

fn note_consumption_status_type(note_consumption_status: &NoteConsumptionStatus) -> String {
match note_consumption_status {
NoteConsumptionStatus::Consumable => "Consumable".to_string(),
NoteConsumptionStatus::ConsumableAfter(block_number) => {
format!("Consumable after block {block_number}")
},
NoteConsumptionStatus::ConsumableWithAuthorization => {
"Consumable with authorization".to_string()
},
NoteConsumptionStatus::UnconsumableConditions => {
"Unconsumable due to conditions".to_string()
},
NoteConsumptionStatus::NeverConsumable(error) => format!("Never consumable: {error}"),
}
.to_string()
}

fn note_record_type(note_record_metadata: Option<&NoteMetadata>) -> String {
match note_record_metadata {
Some(metadata) => match metadata.note_type() {
Expand Down
3 changes: 2 additions & 1 deletion crates/rust-client/src/note/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,13 @@ pub use miden_protocol::note::{
pub use miden_protocol::transaction::ToInputNoteCommitments;
pub use miden_standards::note::utils::{build_p2id_recipient, build_swap_tag};
pub use miden_standards::note::{
NoteConsumptionStatus,
WellKnownNote,
create_p2id_note,
create_p2ide_note,
create_swap_note,
};
pub use note_screener::{NoteConsumability, NoteRelevance, NoteScreener, NoteScreenerError};
pub use note_screener::{NoteConsumability, NoteScreener, NoteScreenerError};
pub use note_update_tracker::{
InputNoteUpdate,
NoteUpdateTracker,
Expand Down
49 changes: 13 additions & 36 deletions crates/rust-client/src/note/note_screener.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use alloc::boxed::Box;
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::fmt;

use async_trait::async_trait;
use miden_protocol::account::{Account, AccountId};
Expand All @@ -20,29 +19,11 @@ use crate::store::{InputNoteRecord, NoteFilter, Store, StoreError};
use crate::sync::{NoteUpdateAction, OnNoteReceived};
use crate::transaction::{InputNote, TransactionRequestBuilder, TransactionRequestError};

/// Describes the relevance of a note based on the screening.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum NoteRelevance {
/// The note can be consumed in the client's current block.
Now,
/// The note can be consumed after the block with the specified number.
After(u32),
}

/// Represents the consumability of a note by a specific account.
///
/// The tuple contains the account ID that may consume the note and the moment it will become
/// relevant.
pub type NoteConsumability = (AccountId, NoteRelevance);

impl fmt::Display for NoteRelevance {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
NoteRelevance::Now => write!(f, "Now"),
NoteRelevance::After(height) => write!(f, "After block {height}"),
}
}
}
pub type NoteConsumability = (AccountId, NoteConsumptionStatus);

/// Provides functionality for testing whether a note is relevant to the client or not.
///
Expand Down Expand Up @@ -115,7 +96,7 @@ where
&self,
account: &Account,
note: &Note,
) -> Result<Option<NoteRelevance>, NoteScreenerError> {
) -> Result<Option<NoteConsumptionStatus>, NoteScreenerError> {
let transaction_request =
TransactionRequestBuilder::new().build_consume_notes(vec![note.id()])?;

Expand All @@ -142,27 +123,23 @@ where
)
.await?;

let result = match note_consumption_check {
NoteConsumptionStatus::ConsumableAfter(block_number) => {
Some(NoteRelevance::After(block_number.as_u32()))
},
NoteConsumptionStatus::Consumable
| NoteConsumptionStatus::ConsumableWithAuthorization => Some(NoteRelevance::Now),
// NOTE: NoteConsumptionStatus::UnconsumableConditions means that state-related context
// does not allow for consumption, so don't keep for now. In the next
// version, we should be more careful about this
NoteConsumptionStatus::UnconsumableConditions
| NoteConsumptionStatus::NeverConsumable(_) => None,
};
Ok(result)
if matches!(
note_consumption_check,
NoteConsumptionStatus::NeverConsumable(..)
| NoteConsumptionStatus::UnconsumableConditions
) {
return Ok(None);
}

Ok(Some(note_consumption_check))
}

/// Special relevance check for P2IDE notes. It checks if the sender account can consume and
/// recall the note.
fn check_p2ide_recall_consumability(
note: &Note,
account_id: &AccountId,
) -> Result<Option<NoteRelevance>, NoteScreenerError> {
) -> Result<Option<NoteConsumptionStatus>, NoteScreenerError> {
let note_inputs = note.inputs().values();
// TODO: this needs to be removed (see note screener refactor issue)

Expand All @@ -178,7 +155,7 @@ where
})?;

if sender == *account_id {
Ok(Some(NoteRelevance::After(recall_height)))
Ok(Some(NoteConsumptionStatus::ConsumableAfter(recall_height.into())))
} else {
Ok(None)
}
Expand Down
33 changes: 22 additions & 11 deletions crates/testing/miden-client-tests/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use miden_client::auth::{
};
use miden_client::builder::ClientBuilder;
use miden_client::keystore::FilesystemKeyStore;
use miden_client::note::{BlockNumber, NoteId, NoteRelevance};
use miden_client::note::{BlockNumber, NoteId};
use miden_client::rpc::{ACCOUNT_ID_LIMIT, NOTE_TAG_LIMIT, NodeRpcClient};
use miden_client::store::input_note_states::ConsumedAuthenticatedLocalNoteState;
use miden_client::store::{
Expand Down Expand Up @@ -108,7 +108,7 @@ use miden_protocol::{EMPTY_WORD, Felt, ONE, Word, ZERO};
use miden_standards::account::faucets::BasicFungibleFaucet;
use miden_standards::account::interface::AccountInterfaceError;
use miden_standards::account::wallets::BasicWallet;
use miden_standards::note::{WellKnownNote, utils};
use miden_standards::note::{NoteConsumptionStatus, WellKnownNote, utils};
use miden_standards::testing::mock_account::MockAccountExt;
use miden_standards::testing::note::NoteBuilder;
use miden_testing::{MockChain, MockChainBuilder, TxContextInput};
Expand Down Expand Up @@ -1488,20 +1488,31 @@ async fn get_consumable_notes() {

// Check that the note is only consumable after block 100 for the account that sent the
// transaction
let from_account_relevance = relevant_accounts
let from_account_relevance = &relevant_accounts
.iter()
.find(|relevance| relevance.0 == from_account_id)
.unwrap()
.1;
assert_eq!(from_account_relevance, NoteRelevance::After(100));
match from_account_relevance {
NoteConsumptionStatus::ConsumableAfter(value) => {
assert_eq!(value, &(100u32.into()));
},
_ => panic!("Unexpected NoteConsumptionStatus"),
}

// Check that the note is always consumable for the account that received the transaction
let to_account_relevance = relevant_accounts
let to_account_relevance = &relevant_accounts
.iter()
.find(|relevance| relevance.0 == to_account_id)
.unwrap()
.1;
assert_eq!(to_account_relevance, NoteRelevance::Now);

match to_account_relevance {
NoteConsumptionStatus::Consumable
| NoteConsumptionStatus::ConsumableAfter(..)
| NoteConsumptionStatus::ConsumableWithAuthorization => {},
_ => panic!("Unexpected NoteConsumptionStatus"),
}
}

#[tokio::test]
Expand Down Expand Up @@ -2120,25 +2131,25 @@ const BUMP_MAP_CODE: &str = r#"
pub proc bump_map_item
# map key
push.{map_key}

# push slot_id_prefix, slot_id_suffix for the map slot
push.MAP_SLOT[0..2]

exec.::miden::protocol::active_account::get_map_item
add.1
push.{map_key}

# push slot_id_prefix, slot_id_suffix for the map slot
push.MAP_SLOT[0..2]
exec.::miden::protocol::native_account::set_map_item
dropw
# => [OLD_VALUE]

dupw

# push slot_id_prefix, slot_id_suffix for the map slot
push.MAP_SLOT[0..2]

# Set a new item each time as the value keeps changing
exec.::miden::protocol::native_account::set_map_item
dropw dropw
Expand Down
1 change: 1 addition & 0 deletions crates/web-client/js/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export {
NoteAndArgsArray,
NoteAssets,
NoteConsumability,
NoteConsumptionStatus,
NoteDetails,
NoteDetailsAndTag,
NoteDetailsAndTagArray,
Expand Down
90 changes: 72 additions & 18 deletions crates/web-client/src/models/consumable_note_record.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,70 @@
use miden_client::note::{NoteConsumability as NativeNoteConsumability, NoteRelevance};
use miden_client::note::{
NoteConsumability as NativeNoteConsumability,
NoteConsumptionStatus as NativeNoteConsumptionStatus,
};
use miden_client::store::InputNoteRecord as NativeInputNoteRecord;
use wasm_bindgen::prelude::*;

use super::account_id::AccountId;
use super::input_note_record::InputNoteRecord;

/// Describes if a note could be consumed under a specific conditions: target account state and
/// block height.
#[derive(Clone)]
#[wasm_bindgen]
pub struct NoteConsumptionStatus(NativeNoteConsumptionStatus);

#[wasm_bindgen]
impl NoteConsumptionStatus {
/// Constructs a `NoteConsumptionStatus` that is consumable.
#[wasm_bindgen(js_name = "consumable")]
pub fn consumable() -> Self {
Self(NativeNoteConsumptionStatus::Consumable)
}

/// Constructs a `NoteConsumptionStatus` that is consumable with authorization.
#[wasm_bindgen(js_name = "consumableWithAuthorization")]
pub fn consumable_with_authorization() -> Self {
Self(NativeNoteConsumptionStatus::ConsumableWithAuthorization)
}

/// Constructs a `NoteConsumptionStatus` that is consumable after a specific block height.
#[wasm_bindgen(js_name = "consumableAfter")]
pub fn consumable_after(block_height: u32) -> Self {
Self(NativeNoteConsumptionStatus::ConsumableAfter(block_height.into()))
}

/// Constructs a `NoteConsumptionStatus` that is never consumable.
#[wasm_bindgen(js_name = "neverConsumable")]
pub fn never_consumable(err: String) -> Self {
Self(NativeNoteConsumptionStatus::NeverConsumable(err.into()))
}

/// Constructs a `NoteConsumptionStatus` that is unconsumable due to conditions.
#[wasm_bindgen(js_name = "unconsumableConditions")]
pub fn unconsumable_conditions() -> Self {
Self(NativeNoteConsumptionStatus::UnconsumableConditions)
}

/// Returns the block number at which the note can be consumed.
/// Returns None if the note is already consumable or never possible
#[wasm_bindgen(js_name = "consumableAfterBlock")]
pub fn consumable_after_block(&self) -> Option<u32> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should return BlockNumber here

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be wrong, but I think that we are using u32 to send block numbers in the web client, right?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I don't think there is a BlockNumber JS model struct. Maybe we can add one but it doesn't seem too critical.

match self.0 {
NativeNoteConsumptionStatus::ConsumableAfter(block_height) => {
Some(block_height.as_u32())
},
_ => None,
}
}
}

impl From<NativeNoteConsumptionStatus> for NoteConsumptionStatus {
fn from(native_note_consumption_status: NativeNoteConsumptionStatus) -> Self {
NoteConsumptionStatus(native_note_consumption_status)
}
}

/// Input note record annotated with consumption conditions.
#[derive(Clone)]
#[wasm_bindgen]
Expand All @@ -13,23 +73,23 @@ pub struct ConsumableNoteRecord {
note_consumability: Vec<NoteConsumability>,
}

#[derive(Clone, Copy)]
#[derive(Clone)]
#[wasm_bindgen]
pub struct NoteConsumability {
account_id: AccountId,

// The block number after which the note can be consumed,
// if None then the note can be consumed immediately
consumable_after_block: Option<u32>,
// The status of the note, consumable immediately,
// after a certain block number, etc.
consumption_status: NoteConsumptionStatus,
}

#[wasm_bindgen]
impl NoteConsumability {
pub(crate) fn new(
account_id: AccountId,
consumable_after_block: Option<u32>,
consumption_status: NoteConsumptionStatus,
) -> NoteConsumability {
NoteConsumability { account_id, consumable_after_block }
NoteConsumability { account_id, consumption_status }
}

/// Returns the account that can consume the note.
Expand All @@ -38,10 +98,10 @@ impl NoteConsumability {
self.account_id
}

/// Returns the block number after which the note becomes consumable (if any).
#[wasm_bindgen(js_name = "consumableAfterBlock")]
pub fn consumable_after_block(&self) -> Option<u32> {
self.consumable_after_block
/// Returns the consumption status of the note.
#[wasm_bindgen(js_name = "consumptionStatus")]
pub fn consumption_status(&self) -> NoteConsumptionStatus {
self.consumption_status.clone()
}
}

Expand Down Expand Up @@ -87,12 +147,6 @@ impl From<(NativeInputNoteRecord, Vec<NativeNoteConsumability>)> for ConsumableN

impl From<NativeNoteConsumability> for NoteConsumability {
fn from(note_consumability: NativeNoteConsumability) -> Self {
NoteConsumability::new(
note_consumability.0.into(),
match note_consumability.1 {
NoteRelevance::After(block) => Some(block),
NoteRelevance::Now => None,
},
)
NoteConsumability::new(note_consumability.0.into(), note_consumability.1.into())
}
}
Loading