Skip to content

Commit

Permalink
Merge branch 'main' of github.com:0xPolygonMiden/miden-client into mF…
Browse files Browse the repository at this point in the history
…ragaBA-split-input-output-note-structs
  • Loading branch information
mFragaBA committed Mar 13, 2024
2 parents e3a6c1f + 3c8316f commit b5574b1
Show file tree
Hide file tree
Showing 30 changed files with 829 additions and 627 deletions.
25 changes: 10 additions & 15 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,31 +17,26 @@ required-features = ["integration"]

[features]
concurrent = [
"miden_lib/concurrent",
"objects/concurrent",
"miden_tx/concurrent",
"miden-lib/concurrent",
"miden-objects/concurrent",
"miden-tx/concurrent",
]
default = ["std"]
integration = ["testing", "concurrent", "uuid"]
mock = []
std = ["crypto/std", "objects/std"]
testing = ["objects/testing", "miden_lib/testing"]
mock = ["miden-objects/testing"]
std = ["miden-objects/std"]
testing = ["miden-objects/testing", "miden-lib/testing"]

[dependencies]
async-trait = { version = "0.1" }
clap = { version = "4.3", features = ["derive"] }
comfy-table = "7.1.0"
crypto = { package = "miden-crypto", version = "0.8", default-features = false }
figment = { version = "0.10", features = ["toml", "env"] }
lazy_static = "1.4.0"
miden_lib = { package = "miden-lib", git = "https://github.com/0xPolygonMiden/miden-base", branch = "main", default-features = false }
miden_node_store = { package = "miden-node-store", git = "https://github.com/0xPolygonMiden/miden-node.git", branch = "main" }
miden_node_proto = { package = "miden-node-proto", git = "https://github.com/0xPolygonMiden/miden-node.git", branch = "main", default-features = false }
miden_tx = { package = "miden-tx", git = "https://github.com/0xPolygonMiden/miden-base", branch = "main", default-features = false }
mock = { package = "miden-mock", git = "https://github.com/0xPolygonMiden/miden-base", branch = "main" }
objects = { package = "miden-objects", git = "https://github.com/0xPolygonMiden/miden-base", branch = "main", features = [
"serde",
] }
miden-lib = { version= "0.1", default-features = false }
miden-node-proto = { version= "0.1", default-features = false }
miden-tx = { version= "0.1", default-features = false }
miden-objects = { version = "0.1", features = ["serde"] }
rand = { version = "0.8.5" }
rusqlite = { version = "0.30.0", features = ["bundled"] }
rusqlite_migration = { version = "1.0" }
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ node:
rm -rf miden-node/miden-store.sqlite3 miden-node/miden-store.sqlite3-wal miden-node/miden-store.sqlite3-shm
rm -rf miden-node/accounts
rm -f miden-node/genesis.dat
cd miden-node && cargo run --release $(NODE_BINARY) $(NODE_FEATURES_TESTING) -- make-genesis --inputs-path node/genesis.toml
cd miden-node && cargo run $(NODE_BINARY) $(NODE_FEATURES_TESTING) -- make-genesis --inputs-path node/genesis.toml

start-node: node
cd miden-node && cargo run --release $(NODE_BINARY) $(NODE_FEATURES_TESTING) -- start --config node/miden-node.toml
cd miden-node && cargo run $(NODE_BINARY) $(NODE_FEATURES_TESTING) -- start --config node/miden-node.toml

kill-node:
pkill miden-node
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ After creating the note with the minted asset, the regular account can now consu
miden-client tx new consume-notes <regular-account-ID-A> <input-note-ID>
```

This will consume the input note identified by its ID, which you can get by listing them as explained in the previous step. Note that you can consume more than one note in a single transaction.
This will consume the input note identified by its ID, which you can get by listing them as explained in the previous step. Note that you can consume more than one note in a single transaction. Additionally, it's possible to provide just a prefix of a note's ID. For example, instead of `miden-client tx new consume-notes <regular-account-ID-A> 0x70b7ecba1db44c3aa75e87a3394de95463cc094d7794b706e02a9228342faeb0` you can do `miden-client tx new consume-notes <regular-account-ID-A> 0x70b7ec`.

You will now be able to see the asset in the account's vault by running:

Expand Down
3 changes: 3 additions & 0 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ Once an account gets created with the `new` command, it will be automatically st
| `export` | Export input note data to a binary file | -e |
| `import` | Import input note data from a binary file | -i |

For `show` subcommand, you can also provide a partial ID instead of the full ID. So instead of `miden-client input-notes show 0x70b7ecba1db44c3aa75e87a3394de95463cc094d7794b706e02a9228342faeb0` you can do `miden-client input-notes show 0x70b7ec`

### `tags` command

| Command | Description | Aliases |
Expand Down Expand Up @@ -73,3 +75,4 @@ You can list them with their respective commands.
| `mint <TARGET ACCOUNT ID> <FAUCET ID> <AMOUNT>` | Creates a note that contains a specific amount tokens minted by a faucet, that the target Account ID can consume|
| `consume-notes <ACCOUNT ID> [NOTES]` | Account ID consumes a list of notes, specified by their Note ID |

For `consume-notes` subcommand, you can also provide a partial ID instead of the full ID for each note. So instead of `miden-client consume-notes <some-account-id> 0x70b7ecba1db44c3aa75e87a3394de95463cc094d7794b706e02a9228342faeb0 0x80b7ecba1db44c3aa75e87a3394de95463cc094d7794b706e02a9228342faeb0` you can do `miden-client consume-notes <some-account-id> 0x70b7ecb 0x80b7ecb`
14 changes: 7 additions & 7 deletions src/cli/account.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
use clap::Parser;
use comfy_table::{presets, Attribute, Cell, ContentArrangement, Table};
use crypto::{
dsa::rpo_falcon512::KeyPair,
utils::{bytes_to_hex_string, Deserializable, Serializable},
ZERO,
};
use miden_client::{
client::{accounts, rpc::NodeRpcClient, Client},
store::Store,
};

use miden_tx::DataStore;
use objects::{
use miden_objects::{
accounts::{AccountData, AccountId, AccountStorage, AccountType, StorageSlotType},
assets::{Asset, TokenSymbol},
crypto::dsa::rpo_falcon512::KeyPair,
ZERO,
};
use miden_tx::{
utils::{bytes_to_hex_string, Deserializable, Serializable},
DataStore,
};
use std::{fs, path::PathBuf};
use tracing::info;
Expand Down
109 changes: 85 additions & 24 deletions src/cli/input_notes.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
use super::{Client, Parser};
use crate::cli::create_dynamic_table;
use crate::cli::{create_dynamic_table, get_note_with_id_prefix};
use clap::ValueEnum;
use comfy_table::{presets, Attribute, Cell, ContentArrangement, Table};
use crypto::utils::{Deserializable, Serializable};
use miden_client::{
client::rpc::NodeRpcClient,
store::{InputNoteRecord, NoteFilter as ClientNoteFilter, Store},
};
use miden_tx::DataStore;
use objects::{
use miden_objects::{
notes::{NoteId, NoteInputs, NoteScript},
Digest,
};
use miden_tx::{
utils::{Deserializable, Serializable},
DataStore,
};
use std::{
fs::File,
io::{Read, Write},
Expand Down Expand Up @@ -182,11 +184,8 @@ fn show_input_note<N: NodeRpcClient, S: Store, D: DataStore>(
show_vault: bool,
show_inputs: bool,
) -> Result<(), String> {
let note_id = Digest::try_from(note_id)
.map_err(|err| format!("Failed to parse input note with ID: {}", err))?
.into();

let input_note_record = client.get_input_note(note_id)?;
let input_note_record =
get_note_with_id_prefix(&client, &note_id).map_err(|err| err.to_string())?;

// print note summary
print_notes_summary(core::iter::once(&input_note_record))?;
Expand Down Expand Up @@ -296,16 +295,19 @@ where

#[cfg(test)]
mod tests {
use crate::cli::input_notes::{export_note, import_note};
use crate::cli::{
get_note_with_id_prefix,
input_notes::{export_note, import_note},
};

use miden_client::{
config::{ClientConfig, Endpoint},
mock::{MockClient, MockDataStore, MockRpcApi},
errors::NoteIdPrefixFetchError,
mock::{mock_full_chain_mmr_and_notes, mock_notes, MockClient, MockDataStore, MockRpcApi},
store::{sqlite_store::SqliteStore, InputNoteRecord},
};
use mock::mock::{
account::MockAccountType, notes::AssetPreservationStatus, transaction::mock_inputs,
};
use miden_lib::transaction::TransactionKernel;

use std::env::temp_dir;
use uuid::Uuid;

Expand All @@ -328,20 +330,17 @@ mod tests {
let mut client = MockClient::new(
MockRpcApi::new(&Endpoint::default().to_string()),
store,
MockDataStore::new(),
MockDataStore::default(),
)
.unwrap();

// generate test data
let transaction_inputs = mock_inputs(
MockAccountType::StandardExisting,
AssetPreservationStatus::Preserved,
);
let assembler = TransactionKernel::assembler();
let (consumed_notes, created_notes) = mock_notes(&assembler);
let (_, commited_notes, _, _) = mock_full_chain_mmr_and_notes(consumed_notes);

let committed_note: InputNoteRecord =
transaction_inputs.input_notes().get_note(0).clone().into();
let pending_note =
InputNoteRecord::from(transaction_inputs.input_notes().get_note(1).note().clone());
let committed_note: InputNoteRecord = commited_notes.first().unwrap().clone().into();
let pending_note = InputNoteRecord::from(created_notes.first().unwrap().clone());

client.import_input_note(committed_note.clone()).unwrap();
client.import_input_note(pending_note.clone()).unwrap();
Expand Down Expand Up @@ -388,7 +387,7 @@ mod tests {
let mut client = MockClient::new(
MockRpcApi::new(&Endpoint::default().to_string()),
store,
MockDataStore::new(),
MockDataStore::default(),
)
.unwrap();

Expand All @@ -403,4 +402,66 @@ mod tests {

assert_eq!(imported_pending_note_record.id(), pending_note.id());
}

#[tokio::test]
async fn get_input_note_with_prefix() {
// generate test client
let mut path = temp_dir();
path.push(Uuid::new_v4().to_string());
let client_config = ClientConfig::new(
path.into_os_string()
.into_string()
.unwrap()
.try_into()
.unwrap(),
Endpoint::default().into(),
);

let store = SqliteStore::new((&client_config).into()).unwrap();

let mut client = MockClient::new(
MockRpcApi::new(&Endpoint::default().to_string()),
store,
MockDataStore::default(),
)
.unwrap();

// Ensure we get an error if no note is found
let non_existent_note_id = "0x123456";
assert_eq!(
get_note_with_id_prefix(&client, non_existent_note_id),
Err(NoteIdPrefixFetchError::NoMatch(
non_existent_note_id.to_string()
))
);

// generate test data
let assembler = TransactionKernel::assembler();
let (consumed_notes, created_notes) = mock_notes(&assembler);
let (_, notes, _, _) = mock_full_chain_mmr_and_notes(consumed_notes);

let committed_note: InputNoteRecord = notes.first().unwrap().clone().into();
let pending_note = InputNoteRecord::from(created_notes.first().unwrap().clone());

client.import_input_note(committed_note.clone()).unwrap();
client.import_input_note(pending_note.clone()).unwrap();
assert!(pending_note.inclusion_proof().is_none());
assert!(committed_note.inclusion_proof().is_some());

// Check that we can fetch Both notes
let note = get_note_with_id_prefix(&client, &committed_note.id().to_hex()).unwrap();
assert_eq!(note.id(), committed_note.id());

let note = get_note_with_id_prefix(&client, &pending_note.id().to_hex()).unwrap();
assert_eq!(note.id(), pending_note.id());

// Check that we get an error if many match
let note_id_with_many_matches = "0x";
assert_eq!(
get_note_with_id_prefix(&client, note_id_with_many_matches),
Err(NoteIdPrefixFetchError::MultipleMatches(
note_id_with_many_matches.to_string()
))
);
}
}
56 changes: 53 additions & 3 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ use figment::{
Figment,
};
use miden_client::{
client::Client, config::ClientConfig, errors::ClientError, store::sqlite_store::SqliteStore,
client::{rpc::NodeRpcClient, Client},
config::ClientConfig,
errors::{ClientError, NoteIdPrefixFetchError},
store::{sqlite_store::SqliteStore, InputNoteRecord, NoteFilter as ClientNoteFilter, Store},
};

#[cfg(feature = "mock")]
Expand All @@ -21,6 +24,7 @@ use miden_client::mock::MockRpcApi;
use miden_client::client::rpc::TonicRpcClient;
#[cfg(not(feature = "mock"))]
use miden_client::store::data_store::SqliteDataStore;
use miden_tx::DataStore;

mod account;
mod info;
Expand Down Expand Up @@ -93,8 +97,11 @@ impl Cli {
};

#[cfg(feature = "mock")]
let client: MockClient =
Client::new(MockRpcApi::new(&rpc_endpoint), store, MockDataStore::new())?;
let client: MockClient = Client::new(
MockRpcApi::new(&rpc_endpoint),
store,
MockDataStore::default(),
)?;

// Execute cli command
match &self.action {
Expand Down Expand Up @@ -146,3 +153,46 @@ pub fn create_dynamic_table(headers: &[&str]) -> Table {

table
}

/// Returns all client's notes whose ID starts with `note_id_prefix`
///
/// # Errors
///
/// - Returns [NoteIdPrefixFetchError::NoMatch] if we were unable to find any note where
/// `note_id_prefix` is a prefix of its id.
/// - Returns [NoteIdPrefixFetchError::MultipleMatches] if there were more than one note found
/// where `note_id_prefix` is a prefix of its id.
pub(crate) fn get_note_with_id_prefix<N: NodeRpcClient, S: Store, D: DataStore>(
client: &Client<N, S, D>,
note_id_prefix: &str,
) -> Result<InputNoteRecord, NoteIdPrefixFetchError> {
let input_note_records = client
.get_input_notes(ClientNoteFilter::All)
.map_err(|err| {
tracing::error!("Error when fetching all notes from the store: {err}");
NoteIdPrefixFetchError::NoMatch(note_id_prefix.to_string())
})?
.into_iter()
.filter(|note_record| note_record.id().to_hex().starts_with(note_id_prefix))
.collect::<Vec<_>>();

if input_note_records.is_empty() {
return Err(NoteIdPrefixFetchError::NoMatch(note_id_prefix.to_string()));
}
if input_note_records.len() > 1 {
let input_note_record_ids = input_note_records
.iter()
.map(|input_note_record| input_note_record.id())
.collect::<Vec<_>>();
tracing::error!(
"Multiple notes found for the prefix {}: {:?}",
note_id_prefix,
input_note_record_ids
);
return Err(NoteIdPrefixFetchError::MultipleMatches(
note_id_prefix.to_string(),
));
}

Ok(input_note_records[0].clone())
}
Loading

0 comments on commit b5574b1

Please sign in to comment.