diff --git a/Rust/Cargo.toml b/Rust/Cargo.toml new file mode 100644 index 0000000..4d42055 --- /dev/null +++ b/Rust/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "iroha_2_examples" +version = "0.1.0" +edition = "2021" + +[dependencies] +iroha = { "git" = "https://github.com/hyperledger/iroha.git", branch = "iroha2-dev" } +iroha_crypto = { "git" = "https://github.com/hyperledger/iroha.git", branch = "iroha2-dev" } +iroha_client = { "git" = "https://github.com/hyperledger/iroha.git", branch = "iroha2-dev" } +iroha_config = { "git" = "https://github.com/hyperledger/iroha.git", branch = "iroha2-dev" } +iroha_data_model = { "git" = "https://github.com/hyperledger/iroha.git", branch = "iroha2-dev" } +iroha_genesis = { "git" = "https://github.com/hyperledger/iroha.git", branch = "iroha2-dev" } +test_network = { "git" = "https://github.com/hyperledger/iroha.git", branch = "iroha2-dev" } + +eyre = "0.6.8" + +serde = { version = "1.0.151", default-features = false } +serde_json = { version = "1.0.91", default-features = false } + +tokio = "1.23.0" diff --git a/Rust/README.md b/Rust/README.md new file mode 100644 index 0000000..d8c2dd5 --- /dev/null +++ b/Rust/README.md @@ -0,0 +1,19 @@ +# Rust examples for Iroha 2 + +This directory contains the examples from the [Rust tutorial](https://hyperledger.github.io/iroha-2-docs/guide/rust.html#_2-configuring-iroha-2). + +## Running the examples + +To run the examples, you need to install [`cargo-nextest`](https://nexte.st/) first. + +```bash +cargo install cargo-nextest +``` + +After it is installed, type: + +```bash +cargo nextest run +``` + +You'll Cargo install the packages that are needed for the tests and the test code will run. \ No newline at end of file diff --git a/Rust/config.json b/Rust/config.json new file mode 100644 index 0000000..88dfd17 --- /dev/null +++ b/Rust/config.json @@ -0,0 +1,21 @@ +{ + "PUBLIC_KEY": "ed01207233BFC89DCBD68C19FDE6CE6158225298EC1131B6A130D1AEB454C1AB5183C0", + "PRIVATE_KEY": { + "digest_function": "ed25519", + "payload": "9ac47abf59b356e0bd7dcbbbb4dec080e302156a48ca907e47cb6aea1d32719e7233bfc89dcbd68c19fde6ce6158225298ec1131b6a130d1aeb454c1ab5183c0" + }, + "ACCOUNT_ID": "alice@wonderland", + "BASIC_AUTH": { + "web_login": "mad_hatter", + "password": "ilovetea" + }, + "TORII_API_URL": "http://127.0.0.1:8080/", + "TORII_TELEMETRY_URL": "http://127.0.0.1:8180/", + "TRANSACTION_TIME_TO_LIVE_MS": 100000, + "TRANSACTION_STATUS_TIMEOUT_MS": 15000, + "TRANSACTION_LIMITS": { + "max_instruction_number": 4096, + "max_wasm_size_bytes": 4194304 + }, + "ADD_TRANSACTION_NONCE": false +} diff --git a/Rust/examples/client_account_definition.rs b/Rust/examples/client_account_definition.rs new file mode 100644 index 0000000..55ead08 --- /dev/null +++ b/Rust/examples/client_account_definition.rs @@ -0,0 +1,28 @@ +use eyre::{Error}; + +fn main() { + account_definition_test() + .expect("Account definition example is expected to work correctly"); + + println!("Account definition example works!"); +} + +fn account_definition_test() -> Result<(), Error> { + // #region account_definition_comparison + use iroha_data_model::prelude::AccountId; + + // Create an `iroha_data_model::AccountId` instance + // with a DomainId instance and a Domain ID for an account + let longhand_account_id = AccountId::new("white_rabbit".parse()?, "looking_glass".parse()?); + let account_id: AccountId = "white_rabbit@looking_glass" + .parse() + .expect("Valid, because the string contains no whitespace, has a single '@' character and is not empty after"); + + // Check that two ways to define an account match + assert_eq!(account_id, longhand_account_id); + + // #endregion account_definition_comparison + + // Finish the test successfully + Ok(()) +} diff --git a/Rust/examples/client_account_registration.rs b/Rust/examples/client_account_registration.rs new file mode 100644 index 0000000..f400c28 --- /dev/null +++ b/Rust/examples/client_account_registration.rs @@ -0,0 +1,68 @@ +use std::fs::File; +use eyre::{Error, WrapErr}; +use iroha_config::client::Configuration; + +fn main() { + // #region rust_config_load + let config_loc = "../configs/client/config.json"; + let file = File::open(config_loc) + .wrap_err("Unable to load the configuration file at `.....`") + .expect("Config file is loading normally."); + let config: Configuration = serde_json::from_reader(file) + .wrap_err("Failed to parse `../configs/client/config.json`") + .expect("Verified in tests"); + // #endregion rust_config_load + + account_registration_test(&config) + .expect("Account registration example is expected to work correctly"); + + println!("Account registration example works!"); +} + +fn account_registration_test(config: &Configuration) -> Result<(), Error> { + // #region register_account_crates + use iroha_client::client::Client; + use iroha_crypto::KeyPair; + use iroha_data_model::{ + metadata::UnlimitedMetadata, + prelude::{Account, AccountId, InstructionBox, RegisterBox}, + }; + // #endregion register_account_crates + + // Create an Iroha client + let iroha_client: Client = Client::new(&config)?; + + // #region register_account_create + // Create an AccountId instance by providing the account and domain name + let account_id: AccountId = "white_rabbit@looking_glass" + .parse() + .expect("Valid, because the string contains no whitespace, has a single '@' character and is not empty after"); + // #endregion register_account_create + + // TODO: consider getting a key from white_rabbit + // Generate a new public key for a new account + let (public_key, _) = KeyPair::generate() + .expect("Failed to generate KeyPair") + .into(); + + // #region register_account_generate + // Generate a new account + let create_account = RegisterBox::new(Account::new(account_id, [public_key])); + // #endregion register_account_generate + + // #region register_account_prepare_tx + // Prepare a transaction using the + // Account's RegisterBox + let metadata = UnlimitedMetadata::new(); + let instructions: Vec = vec![create_account.into()]; + let tx = iroha_client.build_transaction(instructions, metadata)?; + // #endregion register_account_prepare_tx + + // #region register_account_submit_tx + // Submit a prepared account registration transaction + iroha_client.submit_transaction(&tx)?; + // #endregion register_account_submit_tx + + // Finish the test successfully + Ok(()) +} diff --git a/Rust/examples/client_asset_burning.rs b/Rust/examples/client_asset_burning.rs new file mode 100644 index 0000000..5071cce --- /dev/null +++ b/Rust/examples/client_asset_burning.rs @@ -0,0 +1,78 @@ +use std::fs::File; +use eyre::{Error, WrapErr}; +use iroha_config::client::Configuration; + +fn main() { + // #region rust_config_load + let config_loc = "../configs/client/config.json"; + let file = File::open(config_loc) + .wrap_err("Unable to load the configuration file at `.....`") + .expect("Config file is loading normally."); + let config: Configuration = serde_json::from_reader(file) + .wrap_err("Failed to parse `../configs/client/config.json`") + .expect("Verified in tests"); + // #endregion rust_config_load + + asset_burning_test(&config) + .expect("Asset burning example is expected to work correctly"); + + println!("Asset burning example works!"); +} + +fn asset_burning_test(config: &Configuration) -> Result<(), Error> { + // #region burn_asset_crates + use std::str::FromStr; + + use iroha_client::client::Client; + use iroha_data_model::{ + prelude::{AccountId, AssetDefinitionId, AssetId, BurnBox, ToValue}, + IdBox, + }; + // #endregion burn_asset_crates + + // Create an Iroha client + let iroha_client: Client = Client::new(&config)?; + + // #region burn_asset_define_asset_account + // Define the instances of an Asset and Account + let roses = AssetDefinitionId::from_str("rose#wonderland") + .expect("Valid, because the string contains no whitespace, has a single '#' character and is not empty after"); + let alice: AccountId = "alice@wonderland".parse() + .expect("Valid, because the string contains no whitespace, has a single '@' character and is not empty after"); + // #endregion burn_asset_define_asset_account + + // #region burn_asset_burn + // Burn the Asset instance + let burn_roses = BurnBox::new( + 10_u32.to_value(), + IdBox::AssetId(AssetId::new(roses, alice)), + ); + // #endregion burn_asset_burn + + // #region burn_asset_submit_tx + iroha_client + .submit(burn_roses) + .wrap_err("Failed to submit transaction")?; + // #endregion burn_asset_submit_tx + + // #region burn_asset_burn_alt + // Burn the Asset instance (alternate syntax). + // The syntax is `asset_name#asset_domain#account_name@account_domain`, + // or `roses.to_string() + "#" + alice.to_string()`. + // The `##` is a short-hand for the rose `which belongs to the same domain as the account + // to which it belongs to. + let burn_roses_alt = BurnBox::new( + 10_u32.to_value(), + IdBox::AssetId("rose##alice@wonderland".parse()?), + ); + // #endregion burn_asset_burn_alt + + // #region burn_asset_submit_tx_alt + iroha_client + .submit(burn_roses_alt) + .wrap_err("Failed to submit transaction")?; + // #endregion burn_asset_submit_tx_alt + + // Finish the test successfully + Ok(()) +} diff --git a/Rust/examples/client_asset_minting.rs b/Rust/examples/client_asset_minting.rs new file mode 100644 index 0000000..c9ef997 --- /dev/null +++ b/Rust/examples/client_asset_minting.rs @@ -0,0 +1,78 @@ +use std::fs::File; +use eyre::{Error, WrapErr}; +use iroha_config::client::Configuration; + +fn main() { + // #region rust_config_load + let config_loc = "../configs/client/config.json"; + let file = File::open(config_loc) + .wrap_err("Unable to load the configuration file at `.....`") + .expect("Config file is loading normally."); + let config: Configuration = serde_json::from_reader(file) + .wrap_err("Failed to parse `../configs/client/config.json`") + .expect("Verified in tests"); + // #endregion rust_config_load + + asset_minting_test(&config) + .expect("Asset minting example is expected to work correctly"); + + println!("Asset minting example works!"); +} + +fn asset_minting_test(config: &Configuration) -> Result<(), Error> { + // #region mint_asset_crates + use std::str::FromStr; + + use iroha_client::client::Client; + use iroha_data_model::{ + prelude::{AccountId, AssetDefinitionId, AssetId, MintBox, ToValue}, + IdBox, + }; + // #endregion mint_asset_crates + + // Create an Iroha client + let iroha_client: Client = Client::new(&config)?; + + // Define the instances of an Asset and Account + // #region mint_asset_define_asset_account + let roses = AssetDefinitionId::from_str("rose#wonderland") + .expect("Valid, because the string contains no whitespace, has a single '#' character and is not empty after"); + let alice: AccountId = "alice@wonderland".parse() + .expect("Valid, because the string contains no whitespace, has a single '@' character and is not empty after"); + // #endregion mint_asset_define_asset_account + + // Mint the Asset instance + // #region mint_asset_mint + let mint_roses = MintBox::new( + 42_u32.to_value(), + IdBox::AssetId(AssetId::new(roses, alice)), + ); + // #endregion mint_asset_mint + + // #region mint_asset_submit_tx + iroha_client + .submit(mint_roses) + .wrap_err("Failed to submit transaction")?; + // #endregion mint_asset_submit_tx + + // #region mint_asset_mint_alt + // Mint the Asset instance (alternate syntax). + // The syntax is `asset_name#asset_domain#account_name@account_domain`, + // or `roses.to_string() + "#" + alice.to_string()`. + // The `##` is a short-hand for the rose `which belongs to the same domain as the account + // to which it belongs to. + let mint_roses_alt = MintBox::new( + 10_u32.to_value(), + IdBox::AssetId("rose##alice@wonderland".parse()?), + ); + // #endregion mint_asset_mint_alt + + // #region mint_asset_submit_tx_alt + iroha_client + .submit(mint_roses_alt) + .wrap_err("Failed to submit transaction")?; + // #endregion mint_asset_submit_tx_alt + + // Finish the test successfully + Ok(()) +} diff --git a/Rust/examples/client_asset_registration.rs b/Rust/examples/client_asset_registration.rs new file mode 100644 index 0000000..a016a2f --- /dev/null +++ b/Rust/examples/client_asset_registration.rs @@ -0,0 +1,69 @@ +use std::fs::File; +use eyre::{Error, WrapErr}; +use iroha_config::client::Configuration; +use iroha_data_model::TryToValue; + +fn main() { + // #region rust_config_load + let config_loc = "../configs/client/config.json"; + let file = File::open(config_loc) + .wrap_err("Unable to load the configuration file at `.....`") + .expect("Config file is loading normally."); + let config: Configuration = serde_json::from_reader(file) + .wrap_err("Failed to parse `../configs/client/config.json`") + .expect("Verified in tests"); + // #endregion rust_config_load + + asset_registration_test(&config) + .expect("Asset registration example is expected to work correctly"); + + println!("Asset registration example works!"); +} + +fn asset_registration_test(config: &Configuration) -> Result<(), Error> { + // #region register_asset_crates + use std::str::FromStr as _; + + use iroha_client::client::Client; + use iroha_data_model::prelude::{ + AccountId, AssetDefinition, AssetDefinitionId, AssetId, IdBox, MintBox, RegisterBox, + }; + // #endregion register_asset_crates + + // Create an Iroha client + let iroha_client: Client = Client::new(&config)?; + + // #region register_asset_create_asset + // Create an asset + let asset_def_id = AssetDefinitionId::from_str("time#looking_glass") + .expect("Valid, because the string contains no whitespace, has a single '#' character and is not empty after"); + // #endregion register_asset_create_asset + + // #region register_asset_init_submit + // Initialise the registration time + let register_time = + RegisterBox::new(AssetDefinition::fixed(asset_def_id.clone()).mintable_once()); + + // Submit a registration time + iroha_client.submit(register_time)?; + // #endregion register_asset_init_submit + + // Create an account using the previously defined asset + let account_id: AccountId = "white_rabbit@looking_glass" + .parse() + .expect("Valid, because the string contains no whitespace, has a single '@' character and is not empty after"); + + // #region register_asset_mint_submit + // Create a MintBox using a previous asset and account + let mint = MintBox::new( + 12.34_f64.try_to_value()?, + IdBox::AssetId(AssetId::new(asset_def_id, account_id)), + ); + + // Submit a minting transaction + iroha_client.submit_all([mint])?; + // #endregion register_asset_mint_submit + + // Finish the test successfully + Ok(()) +} diff --git a/Rust/examples/client_domain_registration.rs b/Rust/examples/client_domain_registration.rs new file mode 100644 index 0000000..544b878 --- /dev/null +++ b/Rust/examples/client_domain_registration.rs @@ -0,0 +1,64 @@ +use std::fs::File; +use eyre::{Error, WrapErr}; +use iroha_config::client::Configuration; + +fn main() { + // #region rust_config_load + let config_loc = "../configs/client/config.json"; + let file = File::open(config_loc) + .wrap_err("Unable to load the configuration file at `.....`") + .expect("Config file is loading normally."); + let config: Configuration = serde_json::from_reader(file) + .wrap_err("Failed to parse `../configs/client/config.json`") + .expect("Verified in tests"); + // #endregion rust_config_load + + domain_registration_test(&config) + .expect("Domain registration example is expected to work correctly"); + + println!("Domain registration example works!"); +} + +fn domain_registration_test(config: &Configuration) -> Result<(), Error> { + // #region domain_register_example_crates + use iroha_client::client::Client; + use iroha_data_model::{ + metadata::UnlimitedMetadata, + prelude::{Domain, DomainId, InstructionBox, RegisterBox}, + }; + // #endregion domain_register_example_crates + + // #region domain_register_example_create_domain + // Create a domain Id + let looking_glass: DomainId = "looking_glass".parse()?; + // #endregion domain_register_example_create_domain + + // #region domain_register_example_create_isi + // Create an ISI + let create_looking_glass = RegisterBox::new(Domain::new(looking_glass)); + // #endregion domain_register_example_create_isi + + // #region rust_client_create + // Create an Iroha client + let iroha_client: Client = Client::new(&config)?; + // #endregion rust_client_create + + // #region domain_register_example_prepare_tx + // Prepare a transaction + let metadata = UnlimitedMetadata::default(); + let instructions: Vec = vec![create_looking_glass.into()]; + let tx = iroha_client + .build_transaction(instructions, metadata) + .wrap_err("Error building a domain registration transaction")?; + // #endregion domain_register_example_prepare_tx + + // #region domain_register_example_submit_tx + // Submit a prepared domain registration transaction + iroha_client + .submit_transaction(&tx) + .wrap_err("Failed to submit transaction")?; + // #endregion domain_register_example_submit_tx + + // Finish the test successfully + Ok(()) +} diff --git a/Rust/examples/client_json_config.rs b/Rust/examples/client_json_config.rs new file mode 100644 index 0000000..3372595 --- /dev/null +++ b/Rust/examples/client_json_config.rs @@ -0,0 +1,29 @@ +use std::fs::File; +use eyre::{Error, WrapErr}; +use iroha_config::client::Configuration; + +fn main() { + // #region rust_config_load + let config_loc = "../configs/client/config.json"; + let file = File::open(config_loc) + .wrap_err("Unable to load the configuration file at `.....`") + .expect("Config file is loading normally."); + let config: Configuration = serde_json::from_reader(file) + .wrap_err("Failed to parse `../configs/client/config.json`") + .expect("Verified in tests"); + // #endregion rust_config_load + + json_config_client_test(&config) + .expect("JSON config client example is expected to work correctly"); + + println!("JSON client configuration test passed successfully!"); +} + +fn json_config_client_test(config: &Configuration) -> Result<(), Error> { + use iroha_client::client::Client; + + // Initialise a client with a provided config + let _current_client: Client = Client::new(&config)?; + + Ok(()) +} diff --git a/Rust/examples/million_accounts_genesis.rs b/Rust/examples/million_accounts_genesis.rs new file mode 100644 index 0000000..374e1b1 --- /dev/null +++ b/Rust/examples/million_accounts_genesis.rs @@ -0,0 +1,85 @@ +use std::{thread, time::Duration}; + +use iroha::samples::{construct_validator, get_config}; +use iroha_data_model::prelude::*; +use iroha_genesis::{GenesisNetwork, RawGenesisBlock, RawGenesisBlockBuilder}; +use test_network::{ + get_key_pair, wait_for_genesis_committed, Peer as TestPeer, PeerBuilder, TestRuntime, +}; +use tokio::runtime::Runtime; + +fn generate_genesis(num_domains: u32) -> RawGenesisBlock { + let mut builder = RawGenesisBlockBuilder::new(); + + let key_pair = get_key_pair(); + for i in 0_u32..num_domains { + builder = builder + .domain(format!("wonderland-{i}").parse().expect("Valid")) + .account( + format!("Alice-{i}").parse().expect("Valid"), + key_pair.public_key().clone(), + ) + .asset( + format!("xor-{i}").parse().expect("Valid"), + AssetValueType::Quantity, + ) + .finish_domain(); + } + + builder + .validator( + construct_validator("../default_validator").expect("Failed to construct validator"), + ) + .build() +} + +fn main_genesis() { + let mut peer = ::new().expect("Failed to create peer"); + let configuration = get_config( + std::iter::once(peer.id.clone()).collect(), + Some(get_key_pair()), + ); + let rt = Runtime::test(); + let genesis = GenesisNetwork::from_configuration( + generate_genesis(1_000_000_u32), + Some(&configuration.genesis), + ) + .expect("genesis creation failed"); + + let builder = PeerBuilder::new() + .with_into_genesis(genesis) + .with_configuration(configuration); + + // This only submits the genesis. It doesn't check if the accounts + // are created, because that check is 1) not needed for what the + // test is actually for, 2) incredibly slow, making this sort of + // test impractical, 3) very likely to overflow memory on systems + // with less than 16GiB of free memory. + rt.block_on(builder.start_with_peer(&mut peer)); +} + +fn create_million_accounts_directly() { + let (_rt, _peer, test_client) = ::new().start_with_runtime(); + wait_for_genesis_committed(&vec![test_client.clone()], 0); + for i in 0_u32..1_000_000_u32 { + let domain_id: DomainId = format!("wonderland-{i}").parse().expect("Valid"); + let normal_account_id = AccountId::new( + format!("bob-{i}").parse().expect("Valid"), + domain_id.clone(), + ); + let create_domain = RegisterBox::new(Domain::new(domain_id)); + let create_account = RegisterBox::new(Account::new(normal_account_id.clone(), [])); + if test_client + .submit_all([create_domain, create_account]) + .is_err() + { + thread::sleep(Duration::from_millis(100)); + } + } + thread::sleep(Duration::from_secs(1000)); +} + +fn main() { + create_million_accounts_directly(); + main_genesis(); +} diff --git a/Rust/src/main.rs b/Rust/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/Rust/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +}