diff --git a/.github/workflows/iroha2-dev-pr.yml b/.github/workflows/iroha2-dev-pr.yml index b451f9a0d06..adc6832830a 100644 --- a/.github/workflows/iroha2-dev-pr.yml +++ b/.github/workflows/iroha2-dev-pr.yml @@ -36,6 +36,9 @@ jobs: - name: Check schema.json if: always() run: ./scripts/tests/consistency.sh schema + - name: Check CommandLineHelp.md + if: always() + run: ./scripts/tests/consistency.sh cli-help - name: Check Docker Compose configurations if: always() run: ./scripts/tests/consistency.sh docker-compose diff --git a/Cargo.lock b/Cargo.lock index 65c495e40e1..b035950d4a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -757,6 +757,15 @@ dependencies = [ "clap_derive", ] +[[package]] +name = "clap-markdown" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ebc67e6266e14f8b31541c2f204724fa2ac7ad5c17d6f5908fbb92a60f42cff" +dependencies = [ + "clap", +] + [[package]] name = "clap_builder" version = "4.5.15" @@ -2959,6 +2968,7 @@ name = "iroha_cli" version = "2.0.0-rc.1.0" dependencies = [ "clap", + "clap-markdown", "color-eyre", "derive_more", "erased-serde", diff --git a/crates/iroha/src/lib.rs b/crates/iroha/src/lib.rs index 02c3553fb1d..fe29f02d3fd 100644 --- a/crates/iroha/src/lib.rs +++ b/crates/iroha/src/lib.rs @@ -5,7 +5,7 @@ pub mod config; pub mod http; mod http_default; pub mod query; -mod secrecy; +pub mod secrecy; pub use iroha_crypto as crypto; pub use iroha_data_model as data_model; diff --git a/crates/iroha/src/secrecy.rs b/crates/iroha/src/secrecy.rs index cdba906f476..ed6f5f10499 100644 --- a/crates/iroha/src/secrecy.rs +++ b/crates/iroha/src/secrecy.rs @@ -1,12 +1,15 @@ +//! Types for representing securely printable secrets. use std::fmt; use derive_more::Constructor; use serde::{Deserialize, Serialize, Serializer}; +/// SecretString epresents a sensitive secret string. #[derive(Clone, Deserialize, Constructor)] pub struct SecretString(String); impl SecretString { + /// Returns underlying secret string pub fn expose_secret(&self) -> &str { &self.0 } diff --git a/crates/iroha_cli/Cargo.toml b/crates/iroha_cli/Cargo.toml index f6ba6beeae0..f60f901c3a0 100644 --- a/crates/iroha_cli/Cargo.toml +++ b/crates/iroha_cli/Cargo.toml @@ -34,6 +34,7 @@ thiserror = { workspace = true } error-stack = { workspace = true, features = ["eyre"] } eyre = { workspace = true } clap = { workspace = true, features = ["derive"] } +clap-markdown = "0.1.4" humantime = { workspace = true } json5 = { workspace = true } serde = { workspace = true } diff --git a/crates/iroha_cli/CommandLineHelp.md b/crates/iroha_cli/CommandLineHelp.md new file mode 100644 index 00000000000..5001dc51369 --- /dev/null +++ b/crates/iroha_cli/CommandLineHelp.md @@ -0,0 +1,1463 @@ +# Command-Line Help for `iroha` + +This document contains the help content for the `iroha` command-line program. + +**Command Overview:** + +* [`iroha`↴](#iroha) +* [`iroha domain`↴](#iroha-domain) +* [`iroha domain list`↴](#iroha-domain-list) +* [`iroha domain list all`↴](#iroha-domain-list-all) +* [`iroha domain list filter`↴](#iroha-domain-list-filter) +* [`iroha domain get`↴](#iroha-domain-get) +* [`iroha domain register`↴](#iroha-domain-register) +* [`iroha domain unregister`↴](#iroha-domain-unregister) +* [`iroha domain transfer`↴](#iroha-domain-transfer) +* [`iroha domain meta`↴](#iroha-domain-meta) +* [`iroha domain meta get`↴](#iroha-domain-meta-get) +* [`iroha domain meta set`↴](#iroha-domain-meta-set) +* [`iroha domain meta remove`↴](#iroha-domain-meta-remove) +* [`iroha account`↴](#iroha-account) +* [`iroha account role`↴](#iroha-account-role) +* [`iroha account role list`↴](#iroha-account-role-list) +* [`iroha account role grant`↴](#iroha-account-role-grant) +* [`iroha account role revoke`↴](#iroha-account-role-revoke) +* [`iroha account permission`↴](#iroha-account-permission) +* [`iroha account permission list`↴](#iroha-account-permission-list) +* [`iroha account permission grant`↴](#iroha-account-permission-grant) +* [`iroha account permission revoke`↴](#iroha-account-permission-revoke) +* [`iroha account list`↴](#iroha-account-list) +* [`iroha account list all`↴](#iroha-account-list-all) +* [`iroha account list filter`↴](#iroha-account-list-filter) +* [`iroha account get`↴](#iroha-account-get) +* [`iroha account register`↴](#iroha-account-register) +* [`iroha account unregister`↴](#iroha-account-unregister) +* [`iroha account meta`↴](#iroha-account-meta) +* [`iroha account meta get`↴](#iroha-account-meta-get) +* [`iroha account meta set`↴](#iroha-account-meta-set) +* [`iroha account meta remove`↴](#iroha-account-meta-remove) +* [`iroha asset`↴](#iroha-asset) +* [`iroha asset definition`↴](#iroha-asset-definition) +* [`iroha asset definition list`↴](#iroha-asset-definition-list) +* [`iroha asset definition list all`↴](#iroha-asset-definition-list-all) +* [`iroha asset definition list filter`↴](#iroha-asset-definition-list-filter) +* [`iroha asset definition get`↴](#iroha-asset-definition-get) +* [`iroha asset definition register`↴](#iroha-asset-definition-register) +* [`iroha asset definition unregister`↴](#iroha-asset-definition-unregister) +* [`iroha asset definition transfer`↴](#iroha-asset-definition-transfer) +* [`iroha asset definition meta`↴](#iroha-asset-definition-meta) +* [`iroha asset definition meta get`↴](#iroha-asset-definition-meta-get) +* [`iroha asset definition meta set`↴](#iroha-asset-definition-meta-set) +* [`iroha asset definition meta remove`↴](#iroha-asset-definition-meta-remove) +* [`iroha asset get`↴](#iroha-asset-get) +* [`iroha asset list`↴](#iroha-asset-list) +* [`iroha asset list all`↴](#iroha-asset-list-all) +* [`iroha asset list filter`↴](#iroha-asset-list-filter) +* [`iroha asset mint`↴](#iroha-asset-mint) +* [`iroha asset burn`↴](#iroha-asset-burn) +* [`iroha asset transfer`↴](#iroha-asset-transfer) +* [`iroha asset transferkvs`↴](#iroha-asset-transferkvs) +* [`iroha asset getkv`↴](#iroha-asset-getkv) +* [`iroha asset setkv`↴](#iroha-asset-setkv) +* [`iroha asset removekv`↴](#iroha-asset-removekv) +* [`iroha peer`↴](#iroha-peer) +* [`iroha peer list`↴](#iroha-peer-list) +* [`iroha peer list all`↴](#iroha-peer-list-all) +* [`iroha peer register`↴](#iroha-peer-register) +* [`iroha peer unregister`↴](#iroha-peer-unregister) +* [`iroha events`↴](#iroha-events) +* [`iroha events state`↴](#iroha-events-state) +* [`iroha events transaction`↴](#iroha-events-transaction) +* [`iroha events block`↴](#iroha-events-block) +* [`iroha events trigger-execute`↴](#iroha-events-trigger-execute) +* [`iroha events trigger-complete`↴](#iroha-events-trigger-complete) +* [`iroha blocks`↴](#iroha-blocks) +* [`iroha multisig`↴](#iroha-multisig) +* [`iroha multisig list`↴](#iroha-multisig-list) +* [`iroha multisig list all`↴](#iroha-multisig-list-all) +* [`iroha multisig register`↴](#iroha-multisig-register) +* [`iroha multisig propose`↴](#iroha-multisig-propose) +* [`iroha multisig approve`↴](#iroha-multisig-approve) +* [`iroha query`↴](#iroha-query) +* [`iroha query stdin`↴](#iroha-query-stdin) +* [`iroha transaction`↴](#iroha-transaction) +* [`iroha transaction get`↴](#iroha-transaction-get) +* [`iroha transaction ping`↴](#iroha-transaction-ping) +* [`iroha transaction wasm`↴](#iroha-transaction-wasm) +* [`iroha transaction stdin`↴](#iroha-transaction-stdin) +* [`iroha role`↴](#iroha-role) +* [`iroha role permission`↴](#iroha-role-permission) +* [`iroha role permission list`↴](#iroha-role-permission-list) +* [`iroha role permission grant`↴](#iroha-role-permission-grant) +* [`iroha role permission revoke`↴](#iroha-role-permission-revoke) +* [`iroha role list`↴](#iroha-role-list) +* [`iroha role list all`↴](#iroha-role-list-all) +* [`iroha role register`↴](#iroha-role-register) +* [`iroha role unregister`↴](#iroha-role-unregister) +* [`iroha parameter`↴](#iroha-parameter) +* [`iroha parameter list`↴](#iroha-parameter-list) +* [`iroha parameter list all`↴](#iroha-parameter-list-all) +* [`iroha parameter set`↴](#iroha-parameter-set) +* [`iroha trigger`↴](#iroha-trigger) +* [`iroha trigger list`↴](#iroha-trigger-list) +* [`iroha trigger list all`↴](#iroha-trigger-list-all) +* [`iroha trigger get`↴](#iroha-trigger-get) +* [`iroha trigger register`↴](#iroha-trigger-register) +* [`iroha trigger unregister`↴](#iroha-trigger-unregister) +* [`iroha trigger meta`↴](#iroha-trigger-meta) +* [`iroha trigger meta get`↴](#iroha-trigger-meta-get) +* [`iroha trigger meta set`↴](#iroha-trigger-meta-set) +* [`iroha trigger meta remove`↴](#iroha-trigger-meta-remove) +* [`iroha executor`↴](#iroha-executor) +* [`iroha executor upgrade`↴](#iroha-executor-upgrade) +* [`iroha markdown-help`↴](#iroha-markdown-help) + +## `iroha` + +Iroha CLI Client provides an ability to interact with Iroha Peers Web API without direct network usage + +**Usage:** `iroha [OPTIONS] ` + +###### **Subcommands:** + +* `domain` — Read/Write domains +* `account` — Read/Write accounts +* `asset` — Read/Write assets +* `peer` — Read/Write peers +* `events` — Subscribe events: state changes, status of transactions/blocks/triggers +* `blocks` — Subscribe blocks +* `multisig` — Read/Write multisig accounts and transactions +* `query` — Read in general +* `transaction` — Read transactions, Write in general +* `role` — Read/Write roles +* `parameter` — Read/Write parameters +* `trigger` — TODO Read/Write triggers +* `executor` — Update executor +* `markdown-help` — Dump a markdown help of this CLI to stdout + +###### **Options:** + +* `-c`, `--config ` — Path to the configuration file + + Default value: `client.toml` +* `-v`, `--verbose` — More verbose output +* `-m`, `--metadata ` — Optional path to read a JSON5 file to attach transaction metadata +* `-a`, `--accumulate` — Whether to accumulate instructions into a single transaction: If specified, loads instructions from stdin, appends some, and returns them to stdout + + Usage: `echo "[]" | iroha -a domain register -i "domain" | iroha -a asset definition register -i "asset#domain" -t Numeric | iroha transaction stdin` + + + +## `iroha domain` + +Read/Write domains + +**Usage:** `iroha domain ` + +###### **Subcommands:** + +* `list` — List domain ids +* `get` — Read a single domain details +* `register` — Register domain +* `unregister` — Unregister domain +* `transfer` — Transfer domain +* `meta` — Read/Write metadata + + + +## `iroha domain list` + +List domain ids + +**Usage:** `iroha domain list ` + +###### **Subcommands:** + +* `all` — List all domain ids +* `filter` — Filter domains by given predicate + + + +## `iroha domain list all` + +List all domain ids + +**Usage:** `iroha domain list all` + + + +## `iroha domain list filter` + +Filter domains by given predicate + +**Usage:** `iroha domain list filter ` + +###### **Arguments:** + +* `` — Predicate for filtering given as JSON5 string + + + +## `iroha domain get` + +Read a single domain details + +**Usage:** `iroha domain get --id ` + +###### **Options:** + +* `-i`, `--id ` — Domain name as double-quoted string + + + +## `iroha domain register` + +Register domain + +**Usage:** `iroha domain register --id ` + +###### **Options:** + +* `-i`, `--id ` — Domain name as double-quoted string + + + +## `iroha domain unregister` + +Unregister domain + +**Usage:** `iroha domain unregister --id ` + +###### **Options:** + +* `-i`, `--id ` — Domain name as double-quoted string + + + +## `iroha domain transfer` + +Transfer domain + +**Usage:** `iroha domain transfer --id --from --to ` + +###### **Options:** + +* `-i`, `--id ` — Domain name as double-quoted string +* `-f`, `--from ` — Account from which to transfer, in form "multihash@domain" +* `-t`, `--to ` — Account to which to transfer, in form "multihash@domain" + + + +## `iroha domain meta` + +Read/Write metadata + +**Usage:** `iroha domain meta ` + +###### **Subcommands:** + +* `get` — Read a value from a key-value store +* `set` — Create or update an entry in a key-value store, with a value constructed from a JSON5 stdin +* `remove` — Delete an entry from a key-value store + + + +## `iroha domain meta get` + +Read a value from a key-value store + +**Usage:** `iroha domain meta get --id --key ` + +###### **Options:** + +* `-i`, `--id ` +* `-k`, `--key ` + + + +## `iroha domain meta set` + +Create or update an entry in a key-value store, with a value constructed from a JSON5 stdin + +**Usage:** `iroha domain meta set --id --key ` + +###### **Options:** + +* `-i`, `--id ` +* `-k`, `--key ` + + + +## `iroha domain meta remove` + +Delete an entry from a key-value store + +**Usage:** `iroha domain meta remove --id --key ` + +###### **Options:** + +* `-i`, `--id ` +* `-k`, `--key ` + + + +## `iroha account` + +Read/Write accounts + +**Usage:** `iroha account ` + +###### **Subcommands:** + +* `role` — Read/Write account roles +* `permission` — Read/Write account permissions +* `list` — List account ids +* `get` — Read a single account details +* `register` — Register account +* `unregister` — Unregister account +* `meta` — Read/Write metadata + + + +## `iroha account role` + +Read/Write account roles + +**Usage:** `iroha account role ` + +###### **Subcommands:** + +* `list` — List account role ids +* `grant` — Grant account role +* `revoke` — Revoke account role + + + +## `iroha account role list` + +List account role ids + +**Usage:** `iroha account role list --id ` + +###### **Options:** + +* `-i`, `--id ` — Account in form "multihash@domain" + + + +## `iroha account role grant` + +Grant account role + +**Usage:** `iroha account role grant --id --role ` + +###### **Options:** + +* `-i`, `--id ` — Account in form "multihash@domain" +* `-r`, `--role ` — Role name as double-quoted string + + + +## `iroha account role revoke` + +Revoke account role + +**Usage:** `iroha account role revoke --id --role ` + +###### **Options:** + +* `-i`, `--id ` — Account in form "multihash@domain" +* `-r`, `--role ` — Role name as double-quoted string + + + +## `iroha account permission` + +Read/Write account permissions + +**Usage:** `iroha account permission ` + +###### **Subcommands:** + +* `list` — List account permissions +* `grant` — Grant account permission constructed from a JSON5 stdin +* `revoke` — Revoke account permission constructed from a JSON5 stdin + + + +## `iroha account permission list` + +List account permissions + +**Usage:** `iroha account permission list --id ` + +###### **Options:** + +* `-i`, `--id ` — Account in form "multihash@domain" + + + +## `iroha account permission grant` + +Grant account permission constructed from a JSON5 stdin + +**Usage:** `iroha account permission grant --id ` + +###### **Options:** + +* `-i`, `--id ` — Account in form "multihash@domain" + + + +## `iroha account permission revoke` + +Revoke account permission constructed from a JSON5 stdin + +**Usage:** `iroha account permission revoke --id ` + +###### **Options:** + +* `-i`, `--id ` — Account in form "multihash@domain" + + + +## `iroha account list` + +List account ids + +**Usage:** `iroha account list ` + +###### **Subcommands:** + +* `all` — List all account ids +* `filter` — Filter accounts by given predicate + + + +## `iroha account list all` + +List all account ids + +**Usage:** `iroha account list all` + + + +## `iroha account list filter` + +Filter accounts by given predicate + +**Usage:** `iroha account list filter ` + +###### **Arguments:** + +* `` — Predicate for filtering given as JSON5 string + + + +## `iroha account get` + +Read a single account details + +**Usage:** `iroha account get --id ` + +###### **Options:** + +* `-i`, `--id ` — Account in form "multihash@domain" + + + +## `iroha account register` + +Register account + +**Usage:** `iroha account register --id ` + +###### **Options:** + +* `-i`, `--id ` — Account in form "multihash@domain" + + + +## `iroha account unregister` + +Unregister account + +**Usage:** `iroha account unregister --id ` + +###### **Options:** + +* `-i`, `--id ` — Account in form "multihash@domain" + + + +## `iroha account meta` + +Read/Write metadata + +**Usage:** `iroha account meta ` + +###### **Subcommands:** + +* `get` — Read a value from a key-value store +* `set` — Create or update an entry in a key-value store, with a value constructed from a JSON5 stdin +* `remove` — Delete an entry from a key-value store + + + +## `iroha account meta get` + +Read a value from a key-value store + +**Usage:** `iroha account meta get --id --key ` + +###### **Options:** + +* `-i`, `--id ` +* `-k`, `--key ` + + + +## `iroha account meta set` + +Create or update an entry in a key-value store, with a value constructed from a JSON5 stdin + +**Usage:** `iroha account meta set --id --key ` + +###### **Options:** + +* `-i`, `--id ` +* `-k`, `--key ` + + + +## `iroha account meta remove` + +Delete an entry from a key-value store + +**Usage:** `iroha account meta remove --id --key ` + +###### **Options:** + +* `-i`, `--id ` +* `-k`, `--key ` + + + +## `iroha asset` + +Read/Write assets + +**Usage:** `iroha asset ` + +###### **Subcommands:** + +* `definition` — Read/Write asset definitions +* `get` — Read a single asset details +* `list` — List asset ids +* `mint` — Increase an amount of asset +* `burn` — Decrease an amount of asset +* `transfer` — Transfer an amount of asset between accounts +* `transferkvs` — Transfer a key-value store between accounts +* `getkv` — Read a value from a key-value store +* `setkv` — Create or update an entry in a key-value store, with a value constructed from a JSON5 stdin +* `removekv` — Delete an entry from a key-value store + + + +## `iroha asset definition` + +Read/Write asset definitions + +**Usage:** `iroha asset definition ` + +###### **Subcommands:** + +* `list` — List asset definition ids +* `get` — Read a single asset definition details +* `register` — Register asset definition +* `unregister` — Unregister asset definition +* `transfer` — Transfer asset definition +* `meta` — Read/Write metadata + + + +## `iroha asset definition list` + +List asset definition ids + +**Usage:** `iroha asset definition list ` + +###### **Subcommands:** + +* `all` — List all asset definition ids +* `filter` — Filter asset definitions by given predicate + + + +## `iroha asset definition list all` + +List all asset definition ids + +**Usage:** `iroha asset definition list all` + + + +## `iroha asset definition list filter` + +Filter asset definitions by given predicate + +**Usage:** `iroha asset definition list filter ` + +###### **Arguments:** + +* `` — Predicate for filtering given as JSON5 string + + + +## `iroha asset definition get` + +Read a single asset definition details + +**Usage:** `iroha asset definition get --id ` + +###### **Options:** + +* `-i`, `--id ` — Asset definition in form "asset#domain" + + + +## `iroha asset definition register` + +Register asset definition + +**Usage:** `iroha asset definition register [OPTIONS] --id --type ` + +###### **Options:** + +* `-i`, `--id ` — Asset definition in form "asset#domain" +* `-u`, `--unmintable` — Mintability of asset +* `-t`, `--type ` — Value type stored in asset + + + +## `iroha asset definition unregister` + +Unregister asset definition + +**Usage:** `iroha asset definition unregister --id ` + +###### **Options:** + +* `-i`, `--id ` — Asset definition in form "asset#domain" + + + +## `iroha asset definition transfer` + +Transfer asset definition + +**Usage:** `iroha asset definition transfer --id --from --to ` + +###### **Options:** + +* `-i`, `--id ` — Asset definition in form "asset#domain" +* `-f`, `--from ` — Account from which to transfer, in form "multihash@domain" +* `-t`, `--to ` — Account to which to transfer, in form "multihash@domain" + + + +## `iroha asset definition meta` + +Read/Write metadata + +**Usage:** `iroha asset definition meta ` + +###### **Subcommands:** + +* `get` — Read a value from a key-value store +* `set` — Create or update an entry in a key-value store, with a value constructed from a JSON5 stdin +* `remove` — Delete an entry from a key-value store + + + +## `iroha asset definition meta get` + +Read a value from a key-value store + +**Usage:** `iroha asset definition meta get --id --key ` + +###### **Options:** + +* `-i`, `--id ` +* `-k`, `--key ` + + + +## `iroha asset definition meta set` + +Create or update an entry in a key-value store, with a value constructed from a JSON5 stdin + +**Usage:** `iroha asset definition meta set --id --key ` + +###### **Options:** + +* `-i`, `--id ` +* `-k`, `--key ` + + + +## `iroha asset definition meta remove` + +Delete an entry from a key-value store + +**Usage:** `iroha asset definition meta remove --id --key ` + +###### **Options:** + +* `-i`, `--id ` +* `-k`, `--key ` + + + +## `iroha asset get` + +Read a single asset details + +**Usage:** `iroha asset get --id ` + +###### **Options:** + +* `-i`, `--id ` — Asset in form "asset##account@domain" or "asset#another_domain#account@domain" + + + +## `iroha asset list` + +List asset ids + +**Usage:** `iroha asset list ` + +###### **Subcommands:** + +* `all` — List all asset ids +* `filter` — Filter assets by given predicate + + + +## `iroha asset list all` + +List all asset ids + +**Usage:** `iroha asset list all` + + + +## `iroha asset list filter` + +Filter assets by given predicate + +**Usage:** `iroha asset list filter ` + +###### **Arguments:** + +* `` — Predicate for filtering given as JSON5 string + + + +## `iroha asset mint` + +Increase an amount of asset + +**Usage:** `iroha asset mint --id --quantity ` + +###### **Options:** + +* `-i`, `--id ` — Asset in form "asset##account@domain" or "asset#another_domain#account@domain" +* `-q`, `--quantity ` — Amount in an integer or decimal + + + +## `iroha asset burn` + +Decrease an amount of asset + +**Usage:** `iroha asset burn --id --quantity ` + +###### **Options:** + +* `-i`, `--id ` — Asset in form "asset##account@domain" or "asset#another_domain#account@domain" +* `-q`, `--quantity ` — Amount in an integer or decimal + + + +## `iroha asset transfer` + +Transfer an amount of asset between accounts + +**Usage:** `iroha asset transfer --id --to --quantity ` + +###### **Options:** + +* `-i`, `--id ` — Asset to transfer, in form "asset##account@domain" or "asset#another_domain#account@domain" +* `-t`, `--to ` — Account to which to transfer, in form "multihash@domain" +* `-q`, `--quantity ` — Amount to transfer, in an integer or decimal + + + +## `iroha asset transferkvs` + +Transfer a key-value store between accounts + +**Usage:** `iroha asset transferkvs --id --to ` + +###### **Options:** + +* `-i`, `--id ` — Asset to transfer, in form "asset##account@domain" or "asset#another_domain#account@domain" +* `-t`, `--to ` — Account to which to transfer, in form "multihash@domain" + + + +## `iroha asset getkv` + +Read a value from a key-value store + +**Usage:** `iroha asset getkv --id --key ` + +###### **Options:** + +* `-i`, `--id ` — Asset in form "asset##account@domain" or "asset#another_domain#account@domain" +* `-k`, `--key ` — Key for the value + + + +## `iroha asset setkv` + +Create or update an entry in a key-value store, with a value constructed from a JSON5 stdin + +**Usage:** `iroha asset setkv --id --key ` + +###### **Options:** + +* `-i`, `--id ` — Asset in form "asset##account@domain" or "asset#another_domain#account@domain" +* `-k`, `--key ` — Key for the value + + + +## `iroha asset removekv` + +Delete an entry from a key-value store + +**Usage:** `iroha asset removekv --id --key ` + +###### **Options:** + +* `-i`, `--id ` — Asset in form "asset##account@domain" or "asset#another_domain#account@domain" +* `-k`, `--key ` — Key for the value + + + +## `iroha peer` + +Read/Write peers + +**Usage:** `iroha peer ` + +###### **Subcommands:** + +* `list` — List registered peers expected to connect with each other +* `register` — Register peer +* `unregister` — Unregister peer + + + +## `iroha peer list` + +List registered peers expected to connect with each other + +**Usage:** `iroha peer list ` + +###### **Subcommands:** + +* `all` — List all registered peers + + + +## `iroha peer list all` + +List all registered peers + +**Usage:** `iroha peer list all` + + + +## `iroha peer register` + +Register peer + +**Usage:** `iroha peer register --key ` + +###### **Options:** + +* `-k`, `--key ` — Peer's public key in multihash + + + +## `iroha peer unregister` + +Unregister peer + +**Usage:** `iroha peer unregister --key ` + +###### **Options:** + +* `-k`, `--key ` — Peer's public key in multihash + + + +## `iroha events` + +Subscribe events: state changes, status of transactions/blocks/triggers + +**Usage:** `iroha events [OPTIONS] ` + +###### **Subcommands:** + +* `state` — Notify when world state has certain changes +* `transaction` — Notify when transaction passes certain processes +* `block` — Notify when block passes certain processes +* `trigger-execute` — Notify when trigger execution is ordered +* `trigger-complete` — Notify when trigger execution is completed + +###### **Options:** + +* `-t`, `--timeout ` — How long to listen for events ex. "1y 6M 2w 3d 12h 30m 30s 500ms" + + + +## `iroha events state` + +Notify when world state has certain changes + +**Usage:** `iroha events state` + + + +## `iroha events transaction` + +Notify when transaction passes certain processes + +**Usage:** `iroha events transaction` + + + +## `iroha events block` + +Notify when block passes certain processes + +**Usage:** `iroha events block` + + + +## `iroha events trigger-execute` + +Notify when trigger execution is ordered + +**Usage:** `iroha events trigger-execute` + + + +## `iroha events trigger-complete` + +Notify when trigger execution is completed + +**Usage:** `iroha events trigger-complete` + + + +## `iroha blocks` + +Subscribe blocks + +**Usage:** `iroha blocks [OPTIONS] ` + +###### **Arguments:** + +* `` — Block height from which to start streaming blocks + +###### **Options:** + +* `-t`, `--timeout ` — How long to listen for blocks ex. "1y 6M 2w 3d 12h 30m 30s 500ms" + + + +## `iroha multisig` + +Read/Write multisig accounts and transactions + +**Usage:** `iroha multisig ` + +###### **Subcommands:** + +* `list` — List pending multisig transactions relevant to you +* `register` — Register a multisig account +* `propose` — Propose a multisig transaction, constructed from instructions as a JSON5 stdin +* `approve` — Approve a multisig transaction + + + +## `iroha multisig list` + +List pending multisig transactions relevant to you + +**Usage:** `iroha multisig list ` + +###### **Subcommands:** + +* `all` — List all pending multisig transactions relevant to you + + + +## `iroha multisig list all` + +List all pending multisig transactions relevant to you + +**Usage:** `iroha multisig list all` + + + +## `iroha multisig register` + +Register a multisig account + +**Usage:** `iroha multisig register [OPTIONS] --account --quorum ` + +###### **Options:** + +* `-a`, `--account ` — ID of the multisig account to be registered +* `-s`, `--signatories ` — Signatories of the multisig account +* `-w`, `--weights ` — Relative weights of responsibility of respective signatories +* `-q`, `--quorum ` — Threshold of total weight at which the multisig is considered authenticated +* `-t`, `--transaction-ttl ` — Time-to-live of multisig transactions made by the multisig account ex. "1y 6M 2w 3d 12h 30m 30s 500ms" + + Default value: `1h` + + + +## `iroha multisig propose` + +Propose a multisig transaction, constructed from instructions as a JSON5 stdin + +**Usage:** `iroha multisig propose [OPTIONS] --account ` + +###### **Options:** + +* `-a`, `--account ` — Multisig authority of the multisig transaction +* `-t`, `--transaction-ttl ` — Time-to-live of multisig transactions that overrides to shorten the account default ex. "1y 6M 2w 3d 12h 30m 30s 500ms" + + + +## `iroha multisig approve` + +Approve a multisig transaction + +**Usage:** `iroha multisig approve --account --instructions-hash ` + +###### **Options:** + +* `-a`, `--account ` — Multisig authority of the multisig transaction +* `-i`, `--instructions-hash ` — Instructions to approve + + + +## `iroha query` + +Read in general + +**Usage:** `iroha query ` + +###### **Subcommands:** + +* `stdin` — Query constructed from a JSON5 stdin + + + +## `iroha query stdin` + +Query constructed from a JSON5 stdin + +**Usage:** `iroha query stdin` + + + +## `iroha transaction` + +Read transactions, Write in general + +**Usage:** `iroha transaction ` + +###### **Subcommands:** + +* `get` — Read a single transaction details +* `ping` — Empty transaction that just leaves a log message +* `wasm` — Transaction constructed from a Wasm executable input +* `stdin` — Transaction constructed from instructions as a JSON5 stdin + + + +## `iroha transaction get` + +Read a single transaction details + +**Usage:** `iroha transaction get --hash ` + +###### **Options:** + +* `-H`, `--hash ` — Transaction hash + + + +## `iroha transaction ping` + +Empty transaction that just leaves a log message + +**Usage:** `iroha transaction ping [OPTIONS] --msg ` + +###### **Options:** + +* `-l`, `--log-level ` — TRACE, DEBUG, INFO, WARN, ERROR: grows more noticeable in this order + + Default value: `INFO` +* `-m`, `--msg ` — Log message + + + +## `iroha transaction wasm` + +Transaction constructed from a Wasm executable input + +**Usage:** `iroha transaction wasm [OPTIONS]` + +###### **Options:** + +* `-p`, `--path ` — Specify a path to the Wasm file or skip this arg to read from stdin + + + +## `iroha transaction stdin` + +Transaction constructed from instructions as a JSON5 stdin + +**Usage:** `iroha transaction stdin` + + + +## `iroha role` + +Read/Write roles + +**Usage:** `iroha role ` + +###### **Subcommands:** + +* `permission` — Read/Write role permissions +* `list` — List role ids +* `register` — Register role and grant it to you registrant +* `unregister` — Unregister role + + + +## `iroha role permission` + +Read/Write role permissions + +**Usage:** `iroha role permission ` + +###### **Subcommands:** + +* `list` — List role permissions +* `grant` — Grant role permission constructed from a JSON5 stdin +* `revoke` — Revoke role permission constructed from a JSON5 stdin + + + +## `iroha role permission list` + +List role permissions + +**Usage:** `iroha role permission list --id ` + +###### **Options:** + +* `-i`, `--id ` — Role name as double-quoted string + + + +## `iroha role permission grant` + +Grant role permission constructed from a JSON5 stdin + +**Usage:** `iroha role permission grant --id ` + +###### **Options:** + +* `-i`, `--id ` — Role name as double-quoted string + + + +## `iroha role permission revoke` + +Revoke role permission constructed from a JSON5 stdin + +**Usage:** `iroha role permission revoke --id ` + +###### **Options:** + +* `-i`, `--id ` — Role name as double-quoted string + + + +## `iroha role list` + +List role ids + +**Usage:** `iroha role list ` + +###### **Subcommands:** + +* `all` — List all role ids + + + +## `iroha role list all` + +List all role ids + +**Usage:** `iroha role list all` + + + +## `iroha role register` + +Register role and grant it to you registrant + +**Usage:** `iroha role register --id ` + +###### **Options:** + +* `-i`, `--id ` — Role name as double-quoted string + + + +## `iroha role unregister` + +Unregister role + +**Usage:** `iroha role unregister --id ` + +###### **Options:** + +* `-i`, `--id ` — Role name as double-quoted string + + + +## `iroha parameter` + +Read/Write parameters + +**Usage:** `iroha parameter ` + +###### **Subcommands:** + +* `list` — List parameters +* `set` — Set parameter constructed from a JSON5 stdin + + + +## `iroha parameter list` + +List parameters + +**Usage:** `iroha parameter list ` + +###### **Subcommands:** + +* `all` — List all parameters + + + +## `iroha parameter list all` + +List all parameters + +**Usage:** `iroha parameter list all` + + + +## `iroha parameter set` + +Set parameter constructed from a JSON5 stdin + +**Usage:** `iroha parameter set` + + + +## `iroha trigger` + +TODO Read/Write triggers + +**Usage:** `iroha trigger ` + +###### **Subcommands:** + +* `list` — List trigger ids +* `get` — Read a single trigger details +* `register` — TODO Register trigger +* `unregister` — Unregister trigger +* `meta` — Read/Write metadata + + + +## `iroha trigger list` + +List trigger ids + +**Usage:** `iroha trigger list ` + +###### **Subcommands:** + +* `all` — List all trigger ids + + + +## `iroha trigger list all` + +List all trigger ids + +**Usage:** `iroha trigger list all` + + + +## `iroha trigger get` + +Read a single trigger details + +**Usage:** `iroha trigger get --id ` + +###### **Options:** + +* `-i`, `--id ` — Trigger name as double-quoted string + + + +## `iroha trigger register` + +TODO Register trigger + +**Usage:** `iroha trigger register` + + + +## `iroha trigger unregister` + +Unregister trigger + +**Usage:** `iroha trigger unregister --id ` + +###### **Options:** + +* `-i`, `--id ` — Trigger name as double-quoted string + + + +## `iroha trigger meta` + +Read/Write metadata + +**Usage:** `iroha trigger meta ` + +###### **Subcommands:** + +* `get` — Read a value from a key-value store +* `set` — Create or update an entry in a key-value store, with a value constructed from a JSON5 stdin +* `remove` — Delete an entry from a key-value store + + + +## `iroha trigger meta get` + +Read a value from a key-value store + +**Usage:** `iroha trigger meta get --id --key ` + +###### **Options:** + +* `-i`, `--id ` +* `-k`, `--key ` + + + +## `iroha trigger meta set` + +Create or update an entry in a key-value store, with a value constructed from a JSON5 stdin + +**Usage:** `iroha trigger meta set --id --key ` + +###### **Options:** + +* `-i`, `--id ` +* `-k`, `--key ` + + + +## `iroha trigger meta remove` + +Delete an entry from a key-value store + +**Usage:** `iroha trigger meta remove --id --key ` + +###### **Options:** + +* `-i`, `--id ` +* `-k`, `--key ` + + + +## `iroha executor` + +Update executor + +**Usage:** `iroha executor ` + +###### **Subcommands:** + +* `upgrade` — Upgrade executor + + + +## `iroha executor upgrade` + +Upgrade executor + +**Usage:** `iroha executor upgrade --path ` + +###### **Options:** + +* `-p`, `--path ` — Path to the compiled Wasm file + + + +## `iroha markdown-help` + +Dump a markdown help of this CLI to stdout + +**Usage:** `iroha markdown-help` + + + +
+ + + This document was generated automatically by + clap-markdown. + + diff --git a/crates/iroha_cli/README.md b/crates/iroha_cli/README.md index 57a50db8136..0c6d727b2ef 100644 --- a/crates/iroha_cli/README.md +++ b/crates/iroha_cli/README.md @@ -18,31 +18,7 @@ Alternatively, check out the [documentation](https://docs.iroha.tech/get-started ## Usage -Run Iroha Client CLI: - -``` -iroha [OPTIONS] -``` - -### Options - -| Option | Description | -| --------------------- | -------------------------------------------------- | -| -c, --config | Set a config file path (`config.json` by default). | - -### Subcommands - -| Command | Description | -| --------- | ------------------------------------------------------------------------------------------------------------------------------------------- | -| `account` | Execute commands related to accounts: register a new one, list all accounts, grant a permission to an account, list all account permissions | -| `asset` | Execute commands related to assets: register a new one, mint or transfer assets, get info about an asset, list all assets | -| `blocks` | Get block stream from Iroha peer | -| `domain` | Execute commands related to domains: register a new one, list all domains | -| `events` | Get event stream from Iroha peer | -| `json` | Submit multi-instructions or request query as JSON | -| `peer` | Execute commands related to peer administration and networking | -| `wasm` | Execute commands related to WASM | -| `help` | Print the help message for `iroha` and/or the current subcommand other than `help` subcommand | +See [Command-Line Help](CommandLineHelp.md). Refer to [Iroha Special Instructions](https://docs.iroha.tech/blockchain/instructions.html) for more information about Iroha instructions such as register, mint, grant, and so on. @@ -50,29 +26,12 @@ Refer to [Iroha Special Instructions](https://docs.iroha.tech/blockchain/instruc :grey_exclamation: All examples below are Unix-oriented. If you're working on Windows, we would highly encourage you to consider using WSL, as most documentation assumes a POSIX-like shell running on your system. Please be advised that the differences in the syntax may go beyond executing `iroha.exe` instead of `iroha`. -```bash -./iroha domain register --id="Soramitsu" -./iroha account register --id="ed01204A3C5A6B77BBE439969F95F0AA4E01AE31EC45A0D68C131B2C622751FCC5E3B6@Soramitsu" -./iroha asset register --id="XOR#Soramitsu" --type=Numeric -./iroha asset mint --account="ed01204A3C5A6B77BBE439969F95F0AA4E01AE31EC45A0D68C131B2C622751FCC5E3B6@Soramitsu" --asset="XOR#Soramitsu" --quantity=1010 -./iroha asset get --account="ed01204A3C5A6B77BBE439969F95F0AA4E01AE31EC45A0D68C131B2C622751FCC5E3B6@Soramitsu" --asset="XOR#Soramitsu" -``` - -In this section we will show you how to use Iroha CLI Client to do the following: - - - [Create new Domain](#create-new-domain) - - [Create new Account](#create-new-account) - - [Mint Asset to Account](#mint-asset-to-account) - - [Query Account Assets Quantity](#query-account-assets-quantity) - - [Execute WASM transaction](#execute-wasm-transaction) - - [Execute Multi-instruction Transactions](#execute-multi-instruction-transactions) - ### Create new Domain To create a domain, you need to specify the entity type first (`domain` in our case) and then the command (`register`) with a list of required parameters. For the `domain` entity, you only need to provide the `id` argument as a string that doesn't contain the `@` and `#` symbols. ```bash -./iroha domain register --id="Soramitsu" +./iroha domain register --id "Soramitsu" ``` ### Create new Account @@ -80,7 +39,7 @@ To create a domain, you need to specify the entity type first (`domain` in our c To create an account, specify the entity type (`account`) and the command (`register`). Then define the value of the `id` argument in "signatory@domain" format, where signatory is the account's public key in multihash representation: ```bash -./iroha account register --id="ed01204A3C5A6B77BBE439969F95F0AA4E01AE31EC45A0D68C131B2C622751FCC5E3B6@Soramitsu" +./iroha account register --id "ed01204A3C5A6B77BBE439969F95F0AA4E01AE31EC45A0D68C131B2C622751FCC5E3B6@Soramitsu" ``` ### Mint Asset to Account @@ -88,8 +47,8 @@ To create an account, specify the entity type (`account`) and the command (`regi To add assets to the account, you must first register an Asset Definition. Specify the `asset` entity and then use the `register` and `mint` commands respectively. Here is an example of adding Assets of the type `Quantity` to the account: ```bash -./iroha asset register --id="XOR#Soramitsu" --type=Numeric -./iroha asset mint --account="ed01204A3C5A6B77BBE439969F95F0AA4E01AE31EC45A0D68C131B2C622751FCC5E3B6@Soramitsu" --asset="XOR#Soramitsu" --quantity=1010 +./iroha asset register --id "XOR#Soramitsu" --type Numeric +./iroha asset mint --id "XOR##ed01204A3C5A6B77BBE439969F95F0AA4E01AE31EC45A0D68C131B2C622751FCC5E3B6@Soramitsu" --quantity 1010 ``` With this, you created `XOR#Soramitsu`, an asset of type `Numeric`, and then gave `1010` units of this asset to the account `ed01204A3C5A6B77BBE439969F95F0AA4E01AE31EC45A0D68C131B2C622751FCC5E3B6@Soramitsu`. @@ -99,7 +58,7 @@ With this, you created `XOR#Soramitsu`, an asset of type `Numeric`, and then gav You can use Query API to check that your instructions were applied and the _world_ is in the desired state. For example, to know how many units of a particular asset an account has, use `asset get` with the specified account and asset: ```bash -./iroha asset get --account="ed01204A3C5A6B77BBE439969F95F0AA4E01AE31EC45A0D68C131B2C622751FCC5E3B6@Soramitsu" --asset="XOR#Soramitsu" +./iroha asset get --id "XOR##ed01204A3C5A6B77BBE439969F95F0AA4E01AE31EC45A0D68C131B2C622751FCC5E3B6@Soramitsu" ``` This query returns the quantity of `XOR#Soramitsu` asset for the `ed01204A3C5A6B77BBE439969F95F0AA4E01AE31EC45A0D68C131B2C622751FCC5E3B6@Soramitsu` account. @@ -109,11 +68,11 @@ You can also filter based on either account, asset or domain id by using the fil Here are some examples of filtering: ```bash -# Filter domains by id +# FIXME Filter domains by id ./iroha domain list filter '{"Identifiable": {"Is": "wonderland"}}' -# Filter accounts by domain +# FIXME Filter accounts by domain ./iroha account list filter '{"Identifiable": {"EndsWith": "@wonderland"}}' -# Filter asset by domain +# FIXME Filter asset by domain ./iroha asset list filter '{"Or": [{"Identifiable": {"Contains": "#wonderland#"}}, {"And": [{"Identifiable": {"Contains": "##"}}, {"Identifiable": {"EndsWith": "@wonderland"}}]}]}' ``` @@ -122,13 +81,13 @@ Here are some examples of filtering: Use `--file` to specify a path to the WASM file: ```bash -./iroha wasm --file=/path/to/file.wasm +./iroha transaction wasm --file /path/to/file.wasm ``` Or skip `--file` to read WASM from standard input: ```bash -cat /path/to/file.wasm | ./iroha wasm +cat /path/to/file.wasm | ./iroha transaction wasm ``` These subcommands submit the provided wasm binary as an `Executable` to be executed outside a trigger context. @@ -137,14 +96,14 @@ These subcommands submit the provided wasm binary as an `Executable` to be execu The reference implementation of the Rust client, `iroha`, is often used for diagnosing problems in other implementations. -To test transactions in the JSON format (used in the genesis block and by other SDKs), pipe the transaction into the client and add the `json` subcommand to the arguments: +To test transactions in the JSON format (used in the genesis block and by other SDKs), pipe the transaction into the client and add the `transaction stdin` subcommand to the arguments: ```bash -cat /path/to/file.json | ./iroha json transaction +cat ./samples/instructions.json | ./iroha transaction stdin ``` ### Request arbitrary query ```bash -echo '{ "FindAllParameters": null }' | ./iroha --config client.toml json query +cat ./samples/query.json | ./iroha --config ./client.toml query stdin ``` diff --git a/crates/iroha_cli/samples/instructions.json b/crates/iroha_cli/samples/instructions.json new file mode 100644 index 00000000000..e37c550c192 --- /dev/null +++ b/crates/iroha_cli/samples/instructions.json @@ -0,0 +1,8 @@ +[ + { + "Log": { + "level": "INFO", + "msg": "Just leaving a log message" + } + } +] diff --git a/crates/iroha_cli/samples/parameter.json b/crates/iroha_cli/samples/parameter.json new file mode 100644 index 00000000000..947d0dc8532 --- /dev/null +++ b/crates/iroha_cli/samples/parameter.json @@ -0,0 +1,5 @@ +{ + "sumeragi": { + "block_time_ms": 2001 + } +} diff --git a/crates/iroha_cli/samples/query.json b/crates/iroha_cli/samples/query.json new file mode 100644 index 00000000000..eb3ad8c1785 --- /dev/null +++ b/crates/iroha_cli/samples/query.json @@ -0,0 +1,5 @@ +{ + "Singular": { + "FindParameters": null + } +} diff --git a/crates/iroha_cli/src/main.rs b/crates/iroha_cli/src/main.rs index 52ef99b42bd..345f1dc2993 100644 --- a/crates/iroha_cli/src/main.rs +++ b/crates/iroha_cli/src/main.rs @@ -1,174 +1,209 @@ //! Iroha client CLI +#![expect(clippy::doc_markdown)] + use std::{ - fs::{self, read as read_file}, - io::{stdin, stdout}, + fmt::Display, + fs, + io::{self, Read, Write}, path::PathBuf, - str::FromStr, time::Duration, }; use erased_serde::Serialize; use error_stack::{fmt::ColorMode, IntoReportCompat, ResultExt}; -use eyre::{eyre, Error, Result, WrapErr}; +use eyre::{eyre, Result, WrapErr}; use futures::TryStreamExt; use iroha::{client::Client, config::Config, data_model::prelude::*}; -use iroha_primitives::json::Json; use thiserror::Error; use tokio::runtime::Runtime; -/// Re-usable clap `--metadata ` (`-m`) argument. -/// Should be combined with `#[command(flatten)]` attr. -#[derive(clap::Args, Debug, Clone)] -pub struct MetadataArgs { - /// The JSON/JSON5 file with key-value metadata pairs - #[arg(short, long, value_name("PATH"), value_hint(clap::ValueHint::FilePath))] - metadata: Option, -} - -impl MetadataArgs { - fn load(self) -> Result { - let value: Option = self - .metadata - .map(|path| { - let content = fs::read_to_string(&path).wrap_err_with(|| { - eyre!("Failed to read the metadata file `{}`", path.display()) - })?; - let metadata: Metadata = json5::from_str(&content).wrap_err_with(|| { - eyre!( - "Failed to deserialize metadata from file `{}`", - path.display() - ) - })?; - Ok::<_, eyre::Report>(metadata) - }) - .transpose()?; - - Ok(value.unwrap_or_default()) - } -} - -/// Re-usable clap `--value ` (`-v`) argument. -/// Should be combined with `#[command(flatten)]` attr. -#[derive(clap::Args, Debug, Clone, PartialEq, Eq)] -pub struct MetadataValueArg { - /// Wrapper around `MetadataValue` to accept possible values and fallback to json. - /// - /// The following types are supported: - /// Numbers: decimal with optional point - /// Booleans: false/true - /// Objects: e.g. {"Vec":[{"String":"a"},{"String":"b"}]} - #[arg(short, long)] - value: Json, -} - -impl FromStr for MetadataValueArg { - type Err = Error; - - fn from_str(s: &str) -> Result { - Ok(MetadataValueArg { - value: Json::from_str(s)?, - }) - } -} - /// Iroha CLI Client provides an ability to interact with Iroha Peers Web API without direct network usage. #[derive(clap::Parser, Debug)] #[command(name = "iroha", version = concat!("version=", env!("CARGO_PKG_VERSION"), " git_commit_sha=", env!("VERGEN_GIT_SHA")), author)] struct Args { /// Path to the configuration file - #[arg(short, long, value_name("PATH"), value_hint(clap::ValueHint::FilePath))] - #[clap(default_value = "client.toml")] + #[arg(short, long, value_name("PATH"), default_value = "client.toml")] config: PathBuf, /// More verbose output #[arg(short, long)] verbose: bool, - /// Subcommands of client cli + /// Optional path to read a JSON5 file to attach transaction metadata + #[arg(short, long, value_name("PATH"))] + metadata: Option, + /// Whether to accumulate instructions into a single transaction: + /// If specified, loads instructions from stdin, appends some, and returns them to stdout + /// + /// Usage: + /// `echo "[]" | iroha -a domain register -i "domain" | iroha -a asset definition register -i "asset#domain" -t Numeric | iroha transaction stdin` + #[arg(short, long)] + accumulate: bool, + /// Commands #[command(subcommand)] - subcommand: Subcommand, + command: Command, } #[derive(clap::Subcommand, Debug)] -enum Subcommand { - /// The subcommand related to domains - #[clap(subcommand)] - Domain(domain::Args), - /// The subcommand related to accounts - #[clap(subcommand)] - Account(account::Args), - /// The subcommand related to assets - #[clap(subcommand)] - Asset(asset::Args), - /// The subcommand related to p2p networking - #[clap(subcommand)] - Peer(peer::Args), - /// The subcommand related to event streaming +enum Command { + /// Read/Write domains + #[command(subcommand)] + Domain(domain::Command), + /// Read/Write accounts + #[command(subcommand)] + Account(account::Command), + /// Read/Write assets + #[command(subcommand)] + Asset(asset::Command), + /// Read/Write peers + #[command(subcommand)] + Peer(peer::Command), + /// Subscribe events: state changes, status of transactions/blocks/triggers Events(events::Args), - /// The subcommand related to Wasm - Wasm(wasm::Args), - /// The subcommand related to block streaming + /// Subscribe blocks Blocks(blocks::Args), - /// The subcommand related to multi-instructions as Json or Json5 - Json(json::Args), - /// The subcommand related to multisig accounts and transactions - #[clap(subcommand)] - Multisig(multisig::Args), + /// Read/Write multisig accounts and transactions + #[command(subcommand)] + Multisig(multisig::Command), + /// Read in general + #[command(subcommand)] + Query(query::Command), + /// Read transactions, Write in general + #[command(subcommand)] + Transaction(transaction::Command), + /// Read/Write roles + #[command(subcommand)] + Role(role::Command), + /// Read/Write parameters + #[command(subcommand)] + Parameter(parameter::Command), + /// TODO Read/Write triggers + #[command(subcommand)] + Trigger(trigger::Command), + /// Update executor + #[command(subcommand)] + Executor(executor::Command), + /// Dump a markdown help of this CLI to stdout + MarkdownHelp(MarkdownHelp), } -/// Context inside which command is executed +/// Context inside which commands run trait RunContext { - /// Get access to configuration - fn configuration(&self) -> &Config; + fn config(&self) -> &Config; + + fn transaction_metadata(&self) -> Option<&Metadata>; + + fn accumulate_instructions(&self) -> bool; + + fn print_data(&mut self, data: &dyn Serialize) -> Result<()>; + + fn println(&mut self, data: impl Display) -> Result<()>; fn client_from_config(&self) -> Client { - Client::new(self.configuration().clone()) + Client::new(self.config().clone()) } - /// Serialize and print data + /// Submit instructions or dump them to stdout depending on the flag + fn finish(&mut self, instructions: impl Into) -> Result<()> { + if !self.accumulate_instructions() { + return self._submit(instructions); + } + let instructions = match instructions.into() { + Executable::Wasm(wasm) => return self._submit(wasm), + Executable::Instructions(instructions) => instructions, + }; + let mut acc: Vec = parse_json5_stdin()?; + acc.append(&mut instructions.into_vec()); + dump_json5_stdout(&acc) + } + + /// Combine instructions into a single transaction and submit it /// /// # Errors - /// - if serialization fails - /// - if printing fails - fn print_data(&mut self, data: &dyn Serialize) -> Result<()>; + /// + /// Fails if submitting over network fails + fn _submit(&mut self, instructions: impl Into) -> Result<()> { + let client = self.client_from_config(); + let transaction = client.build_transaction( + instructions, + self.transaction_metadata().cloned().unwrap_or_default(), + ); + + #[cfg(not(debug_assertions))] + let err_msg = "Failed to submit transaction"; + #[cfg(debug_assertions)] + let err_msg = format!("Failed to submit transaction {transaction:?}"); + + let hash = client + .submit_transaction_blocking(&transaction) + .wrap_err(err_msg)?; + + self.println("Transaction Submitted. Details:")?; + self.print_data(&transaction)?; + self.println("Hash:")?; + self.print_data(&hash)?; + + Ok(()) + } } struct PrintJsonContext { write: W, config: Config, + transaction_metadata: Option, + accumulate_instructions: bool, } impl RunContext for PrintJsonContext { - fn configuration(&self) -> &Config { + fn config(&self) -> &Config { &self.config } + fn transaction_metadata(&self) -> Option<&Metadata> { + self.transaction_metadata.as_ref() + } + + fn accumulate_instructions(&self) -> bool { + self.accumulate_instructions + } + + /// Serialize and print data + /// + /// # Errors + /// + /// - if serialization fails + /// - if printing fails fn print_data(&mut self, data: &dyn Serialize) -> Result<()> { writeln!(&mut self.write, "{}", serde_json::to_string_pretty(data)?)?; Ok(()) } + + fn println(&mut self, data: impl Display) -> Result<()> { + writeln!(&mut self.write, "{data}")?; + Ok(()) + } } -/// Runs subcommand -trait RunArgs { +/// Runs command +trait Run { /// Runs command /// /// # Errors /// if inner command errors - fn run(self, context: &mut dyn RunContext) -> Result<()>; + fn run(self, context: &mut C) -> Result<()>; } macro_rules! match_all { (($self:ident, $context:ident), { $($variants:path),* $(,)?}) => { match $self { - $($variants(variant) => RunArgs::run(variant, $context),)* + $($variants(variant) => Run::run(variant, $context),)* } }; } -impl RunArgs for Subcommand { - fn run(self, context: &mut dyn RunContext) -> Result<()> { - use Subcommand::*; - match_all!((self, context), { Domain, Account, Asset, Peer, Events, Wasm, Blocks, Json, Multisig }) +impl Run for Command { + fn run(self, context: &mut C) -> Result<()> { + use Command::*; + match_all!((self, context), { Domain, Account, Asset, Peer, Events, Blocks, Multisig, Query, Transaction, Role, Parameter, Trigger, Executor, MarkdownHelp }) } } @@ -178,24 +213,37 @@ enum MainError { Config, #[error("Failed to serialize config")] SerializeConfig, + #[error("Failed to get transaction metadata from file")] + TransactionMetadata, #[error("Failed to run the command")] - Subcommand, + Command, +} + +#[derive(clap::Args, Debug)] +struct MarkdownHelp; + +impl Run for MarkdownHelp { + fn run(self, _context: &mut C) -> Result<()> { + Ok(()) + } } fn main() -> error_stack::Result<(), MainError> { - let Args { - config: config_path, - subcommand, - verbose, - } = clap::Parser::parse(); + let args: Args = clap::Parser::parse(); + + if let Command::MarkdownHelp(_md) = args.command { + clap_markdown::print_help_markdown::(); + return Ok(()); + } error_stack::Report::set_color_mode(color_mode()); - let config = Config::load(config_path) + let config = Config::load(args.config) // FIXME: would be nice to NOT change the context, it's unnecessary .change_context(MainError::Config) .attach_printable("config path was set by `--config` argument")?; - if verbose { + + if args.verbose { eprintln!( "Configuration: {}", &serde_json::to_string_pretty(&config) @@ -205,13 +253,25 @@ fn main() -> error_stack::Result<(), MainError> { } let mut context = PrintJsonContext { - write: stdout(), + write: io::stdout(), config, + transaction_metadata: None, + accumulate_instructions: args.accumulate, }; - subcommand + if let Some(path) = args.metadata { + let str = fs::read_to_string(&path) + .change_context(MainError::TransactionMetadata) + .attach_printable("failed to read to string")?; + let metadata: Metadata = json5::from_str(&str) + .change_context(MainError::TransactionMetadata) + .attach_printable("failed to deserialize to metadata")?; + context.transaction_metadata = Some(metadata); + } + + args.command .run(&mut context) .into_report() - .map_err(|report| report.change_context(MainError::Subcommand))?; + .map_err(|report| report.change_context(MainError::Command))?; Ok(()) } @@ -226,74 +286,42 @@ fn color_mode() -> ColorMode { } } -/// Submit instruction with metadata to network. -/// -/// # Errors -/// Fails if submitting over network fails -#[allow(clippy::shadow_unrelated)] -fn submit( - instructions: impl Into, - metadata: Metadata, - context: &mut dyn RunContext, -) -> Result<()> { - let client = context.client_from_config(); - let instructions = instructions.into(); - let tx = client.build_transaction(instructions, metadata); - - #[cfg(not(debug_assertions))] - let err_msg = "Failed to submit transaction."; - #[cfg(debug_assertions)] - let err_msg = format!("Failed to submit transaction {tx:?}"); - let hash = client.submit_transaction_blocking(&tx).wrap_err(err_msg)?; - context.print_data(&hash)?; - - Ok(()) -} - mod filter { use iroha::data_model::query::dsl::CompoundPredicate; - use serde::Deserialize; use super::*; /// Filter for domain queries - #[derive(Clone, Debug, clap::Parser)] + #[derive(clap::Args, Debug)] pub struct DomainFilter { /// Predicate for filtering given as JSON5 string - #[clap(value_parser = parse_json5::>)] + #[arg(value_parser = parse_json5::>)] pub predicate: CompoundPredicate, } /// Filter for account queries - #[derive(Clone, Debug, clap::Parser)] + #[derive(clap::Args, Debug)] pub struct AccountFilter { /// Predicate for filtering given as JSON5 string - #[clap(value_parser = parse_json5::>)] + #[arg(value_parser = parse_json5::>)] pub predicate: CompoundPredicate, } /// Filter for asset queries - #[derive(Clone, Debug, clap::Parser)] + #[derive(clap::Args, Debug)] pub struct AssetFilter { /// Predicate for filtering given as JSON5 string - #[clap(value_parser = parse_json5::>)] + #[arg(value_parser = parse_json5::>)] pub predicate: CompoundPredicate, } /// Filter for asset definition queries - #[derive(Clone, Debug, clap::Parser)] + #[derive(clap::Args, Debug)] pub struct AssetDefinitionFilter { /// Predicate for filtering given as JSON5 string - #[clap(value_parser = parse_json5::>)] + #[arg(value_parser = parse_json5::>)] pub predicate: CompoundPredicate, } - - fn parse_json5(s: &str) -> Result - where - T: for<'a> Deserialize<'a>, - { - json5::from_str(s).map_err(|err| format!("Failed to deserialize filter from JSON5: {err}")) - } } mod events { @@ -302,53 +330,47 @@ mod events { use super::*; - #[derive(clap::Args, Debug, Clone, Copy)] + #[derive(clap::Args, Debug)] pub struct Args { - /// Wait timeout - #[clap(short, long, global = true)] + /// How long to listen for events ex. "1y 6M 2w 3d 12h 30m 30s 500ms" + #[arg(short, long, global = true)] timeout: Option, - #[clap(subcommand)] + #[command(subcommand)] command: Command, } - /// Get event stream from Iroha peer - #[derive(clap::Subcommand, Debug, Clone, Copy)] + #[derive(clap::Subcommand, Debug)] enum Command { - /// Gets block pipeline events - BlockPipeline, - /// Gets transaction pipeline events - TransactionPipeline, - /// Gets data events - Data, - /// Get execute trigger events - ExecuteTrigger, - /// Get trigger completed events - TriggerCompleted, - } - - impl RunArgs for Args { - fn run(self, context: &mut dyn RunContext) -> Result<()> { + /// Notify when world state has certain changes + State, + /// Notify when transaction passes certain processes + Transaction, + /// Notify when block passes certain processes + Block, + /// Notify when trigger execution is ordered + TriggerExecute, + /// Notify when trigger execution is completed + TriggerComplete, + } + + impl Run for Args { + fn run(self, context: &mut C) -> Result<()> { + use self::Command::*; let timeout: Option = self.timeout.map(Into::into); match self.command { - Command::TransactionPipeline => { - listen(TransactionEventFilter::default(), context, timeout) - } - Command::BlockPipeline => listen(BlockEventFilter::default(), context, timeout), - Command::Data => listen(DataEventFilter::Any, context, timeout), - Command::ExecuteTrigger => { - listen(ExecuteTriggerEventFilter::new(), context, timeout) - } - Command::TriggerCompleted => { - listen(TriggerCompletedEventFilter::new(), context, timeout) - } + State => listen(DataEventFilter::Any, context, timeout), + Transaction => listen(TransactionEventFilter::default(), context, timeout), + Block => listen(BlockEventFilter::default(), context, timeout), + TriggerExecute => listen(ExecuteTriggerEventFilter::new(), context, timeout), + TriggerComplete => listen(TriggerCompletedEventFilter::new(), context, timeout), } } } fn listen( filter: impl Into, - context: &mut dyn RunContext, + context: &mut impl RunContext, timeout: Option, ) -> Result<()> { let filter = filter.into(); @@ -356,23 +378,23 @@ mod events { if let Some(timeout) = timeout { eprintln!("Listening to events with filter: {filter:?} and timeout: {timeout:?}"); - let rt = Runtime::new().wrap_err("Failed to create runtime.")?; + let rt = Runtime::new().wrap_err("Failed to create runtime")?; rt.block_on(async { let mut stream = client .listen_for_events_async([filter]) .await - .expect("Failed to listen for events."); + .expect("Failed to listen for events"); while let Ok(event) = tokio::time::timeout(timeout, stream.try_next()).await { context.print_data(&event?)?; } - eprintln!("Timeout period has expired."); + eprintln!("Timeout period has expired"); Result::<()>::Ok(()) })?; } else { eprintln!("Listening to events with filter: {filter:?}"); client .listen_for_events([filter]) - .wrap_err("Failed to listen for events.")? + .wrap_err("Failed to listen for events")? .try_for_each(|event| context.print_data(&event?))?; } Ok(()) @@ -384,19 +406,18 @@ mod blocks { use super::*; - /// Get block stream from Iroha peer - #[derive(clap::Args, Debug, Clone, Copy)] + #[derive(clap::Args, Debug)] pub struct Args { /// Block height from which to start streaming blocks height: NonZeroU64, - /// Wait timeout - #[clap(short, long)] + /// How long to listen for blocks ex. "1y 6M 2w 3d 12h 30m 30s 500ms" + #[arg(short, long)] timeout: Option, } - impl RunArgs for Args { - fn run(self, context: &mut dyn RunContext) -> Result<()> { + impl Run for Args { + fn run(self, context: &mut C) -> Result<()> { let Args { height, timeout } = self; let timeout: Option = timeout.map(Into::into); listen(height, context, timeout) @@ -405,29 +426,29 @@ mod blocks { fn listen( height: NonZeroU64, - context: &mut dyn RunContext, + context: &mut impl RunContext, timeout: Option, ) -> Result<()> { let client = context.client_from_config(); if let Some(timeout) = timeout { eprintln!("Listening to blocks from height: {height} and timeout: {timeout:?}"); - let rt = Runtime::new().wrap_err("Failed to create runtime.")?; + let rt = Runtime::new().wrap_err("Failed to create runtime")?; rt.block_on(async { let mut stream = client .listen_for_blocks_async(height) .await - .expect("Failed to listen for blocks."); + .expect("Failed to listen for blocks"); while let Ok(event) = tokio::time::timeout(timeout, stream.try_next()).await { context.print_data(&event?)?; } - eprintln!("Timeout period has expired."); + eprintln!("Timeout period has expired"); Result::<()>::Ok(()) })?; } else { eprintln!("Listening to blocks from height: {height}"); client .listen_for_blocks(height) - .wrap_err("Failed to listen for blocks.")? + .wrap_err("Failed to listen for blocks")? .try_for_each(|event| context.print_data(&event?))?; } Ok(()) @@ -437,307 +458,281 @@ mod blocks { mod domain { use super::*; - /// Arguments for domain subcommand - #[derive(Debug, clap::Subcommand)] - pub enum Args { - /// Register domain - Register(Register), - /// List domains - #[clap(subcommand)] + #[derive(clap::Subcommand, Debug)] + pub enum Command { + /// List domain ids + #[command(subcommand)] List(List), + /// Read a single domain details + Get(Id), + /// Register domain + Register(Id), + /// Unregister domain + Unregister(Id), /// Transfer domain Transfer(Transfer), - /// Edit domain metadata - #[clap(subcommand)] - Metadata(metadata::Args), + /// Read/Write metadata + #[command(subcommand)] + Meta(metadata::domain::Command), } - impl RunArgs for Args { - fn run(self, context: &mut dyn RunContext) -> Result<()> { - match_all!((self, context), { Args::Register, Args::List, Args::Transfer, Args::Metadata, }) + impl Run for Command { + fn run(self, context: &mut C) -> Result<()> { + use self::Command::*; + match self { + List(cmd) => cmd.run(context), + Get(args) => { + let client = context.client_from_config(); + let entry = client + .query(FindDomains) + .filter_with(|entry| entry.id.eq(args.id)) + .execute_single() + .wrap_err("Failed to get domain")?; + context.print_data(&entry) + } + Register(args) => { + let instruction = + iroha::data_model::isi::Register::domain(Domain::new(args.id)); + context + .finish([instruction]) + .wrap_err("Failed to register domain") + } + Unregister(args) => { + let instruction = iroha::data_model::isi::Unregister::domain(args.id); + context + .finish([instruction]) + .wrap_err("Failed to unregister domain") + } + Transfer(args) => { + let instruction = + iroha::data_model::isi::Transfer::domain(args.from, args.id, args.to); + context + .finish([instruction]) + .wrap_err("Failed to transfer domain") + } + Meta(cmd) => cmd.run(context), + } } } - /// Add subcommand for domain - #[derive(Debug, clap::Args)] - pub struct Register { + #[derive(clap::Args, Debug)] + pub struct Transfer { /// Domain name as double-quoted string #[arg(short, long)] pub id: DomainId, - #[command(flatten)] - pub metadata: MetadataArgs, + /// Account from which to transfer, in form "multihash@domain" + #[arg(short, long)] + pub from: AccountId, + /// Account to which to transfer, in form "multihash@domain" + #[arg(short, long)] + pub to: AccountId, } - impl RunArgs for Register { - fn run(self, context: &mut dyn RunContext) -> Result<()> { - let Self { id, metadata } = self; - let create_domain = iroha::data_model::isi::Register::domain(Domain::new(id)); - submit([create_domain], metadata.load()?, context).wrap_err("Failed to create domain") - } + #[derive(clap::Args, Debug)] + pub struct Id { + /// Domain name as double-quoted string + #[arg(short, long)] + pub id: DomainId, } - /// List domains with this command - #[derive(clap::Subcommand, Debug, Clone)] + #[derive(clap::Subcommand, Debug)] pub enum List { - /// All domains + /// List all domain ids All, /// Filter domains by given predicate Filter(filter::DomainFilter), } - impl RunArgs for List { - fn run(self, context: &mut dyn RunContext) -> Result<()> { + impl Run for List { + fn run(self, context: &mut C) -> Result<()> { let client = context.client_from_config(); - - let query = client.query(FindDomains::new()); - + let query = client.query(FindDomains).select_with(|entry| entry.id); let query = match self { List::All => query, List::Filter(filter) => query.filter(filter.predicate), }; - - let result = query.execute_all().wrap_err("Failed to get all accounts")?; - context.print_data(&result)?; - - Ok(()) - } - } - - /// Transfer a domain between accounts - #[derive(Debug, clap::Args)] - pub struct Transfer { - /// Domain name as double-quited string - #[arg(short, long)] - pub id: DomainId, - /// Account from which to transfer (in form `name@domain_name`) - #[arg(short, long)] - pub from: AccountId, - /// Account to which to transfer (in form `name@domain_name`) - #[arg(short, long)] - pub to: AccountId, - #[command(flatten)] - pub metadata: MetadataArgs, - } - - impl RunArgs for Transfer { - fn run(self, context: &mut dyn RunContext) -> Result<()> { - let Self { - id, - from, - to, - metadata, - } = self; - let transfer_domain = iroha::data_model::isi::Transfer::domain(from, id, to); - submit([transfer_domain], metadata.load()?, context) - .wrap_err("Failed to transfer domain") + let ids = query.execute_all()?; + context.print_data(&ids) } } +} - mod metadata { - use iroha::data_model::domain::DomainId; - - use super::*; - - /// Edit domain subcommands - #[derive(Debug, Clone, clap::Subcommand)] - pub enum Args { - /// Set domain metadata - Set(Set), - /// Remove domain metadata - Remove(Remove), - } +mod account { + use std::fmt::Debug; - impl RunArgs for Args { - fn run(self, context: &mut dyn RunContext) -> Result<()> { - match_all!((self, context), { Args::Set, Args::Remove, }) - } - } + use super::*; - /// Set metadata into domain - #[derive(Debug, Clone, clap::Args)] - pub struct Set { - /// A domain id from which metadata is to be removed - #[arg(short, long)] - id: DomainId, - /// A key of metadata - #[arg(short, long)] - key: Name, - #[command(flatten)] - value: MetadataValueArg, - } + #[derive(clap::Subcommand, Debug)] + pub enum Command { + /// Read/Write account roles + #[command(subcommand)] + Role(RoleCommand), + /// Read/Write account permissions + #[command(subcommand)] + Permission(PermissionCommand), + /// List account ids + #[command(subcommand)] + List(List), + /// Read a single account details + Get(Id), + /// Register account + Register(Id), + /// Unregister account + Unregister(Id), + /// Read/Write metadata + #[command(subcommand)] + Meta(metadata::account::Command), + } - impl RunArgs for Set { - fn run(self, context: &mut dyn RunContext) -> Result<()> { - let Self { - id, - key, - value: MetadataValueArg { value }, - } = self; - let set_key_value = SetKeyValue::domain(id, key, value); - submit([set_key_value], Metadata::default(), context) - .wrap_err("Failed to submit Set instruction") + impl Run for Command { + fn run(self, context: &mut C) -> Result<()> { + use self::Command::*; + match self { + Role(cmd) => cmd.run(context), + Permission(cmd) => cmd.run(context), + List(cmd) => cmd.run(context), + Get(args) => { + let client = context.client_from_config(); + let entry = client + .query(FindAccounts) + .filter_with(|entry| entry.id.eq(args.id)) + .execute_single() + .wrap_err("Failed to get account")?; + context.print_data(&entry) + } + Register(args) => { + let instruction = + iroha::data_model::isi::Register::account(Account::new(args.id)); + context + .finish([instruction]) + .wrap_err("Failed to register account") + } + Unregister(args) => { + let instruction = iroha::data_model::isi::Unregister::account(args.id); + context + .finish([instruction]) + .wrap_err("Failed to unregister account") + } + Meta(cmd) => cmd.run(context), } } + } - /// Remove metadata into domain by key - #[derive(Debug, Clone, clap::Args)] - pub struct Remove { - /// A domain id from which metadata is to be removed - #[arg(short, long)] - id: DomainId, - /// A key of metadata - #[arg(short, long)] - key: Name, - } - - impl RunArgs for Remove { - fn run(self, context: &mut dyn RunContext) -> Result<()> { - let Self { id, key } = self; - let remove_key_value = RemoveKeyValue::domain(id, key); - submit([remove_key_value], Metadata::default(), context) - .wrap_err("Failed to submit Remove instruction") + #[derive(clap::Subcommand, Debug)] + pub enum RoleCommand { + /// List account role ids + List(Id), + /// Grant account role + Grant(IdRole), + /// Revoke account role + Revoke(IdRole), + } + + impl Run for RoleCommand { + fn run(self, context: &mut C) -> Result<()> { + use self::RoleCommand::*; + match self { + List(args) => { + let client = context.client_from_config(); + let roles = client + .query(FindRolesByAccountId::new(args.id)) + .execute_all()?; + context.print_data(&roles) + } + Grant(args) => { + let instruction = + iroha::data_model::isi::Grant::account_role(args.role, args.id); + context + .finish([instruction]) + .wrap_err("Failed to grant the role to the account") + } + Revoke(args) => { + let instruction = + iroha::data_model::isi::Revoke::account_role(args.role, args.id); + context + .finish([instruction]) + .wrap_err("Failed to revoke the role from the account") + } } } } -} - -mod account { - use std::fmt::Debug; - use super::{Permission as DataModelPermission, *}; - - /// subcommands for account subcommand #[derive(clap::Subcommand, Debug)] - pub enum Args { - /// Register account - Register(Register), - /// List accounts - #[command(subcommand)] - List(List), - /// Grant a permission to the account - Grant(Grant), - /// List all account permissions - ListPermissions(ListPermissions), - } - - impl RunArgs for Args { - fn run(self, context: &mut dyn RunContext) -> Result<()> { - match_all!((self, context), { - Args::Register, - Args::List, - Args::Grant, - Args::ListPermissions, - }) + pub enum PermissionCommand { + /// List account permissions + List(Id), + /// Grant account permission constructed from a JSON5 stdin + Grant(Id), + /// Revoke account permission constructed from a JSON5 stdin + Revoke(Id), + } + + impl Run for PermissionCommand { + fn run(self, context: &mut C) -> Result<()> { + use self::PermissionCommand::*; + match self { + List(args) => { + let client = context.client_from_config(); + let permissions = client + .query(FindPermissionsByAccountId::new(args.id)) + .execute_all()?; + context.print_data(&permissions) + } + Grant(args) => { + let permission: Permission = parse_json5_stdin()?; + let instruction = + iroha::data_model::isi::Grant::account_permission(permission, args.id); + context + .finish([instruction]) + .wrap_err("Failed to grant the permission to the account") + } + Revoke(args) => { + let permission: Permission = parse_json5_stdin()?; + let instruction = + iroha::data_model::isi::Revoke::account_permission(permission, args.id); + context + .finish([instruction]) + .wrap_err("Failed to revoke the permission from the account") + } + } } } - /// Register account #[derive(clap::Args, Debug)] - pub struct Register { - /// Id of account in form `name@domain_name` + pub struct Id { + /// Account in form "multihash@domain" #[arg(short, long)] - pub id: AccountId, - #[command(flatten)] - pub metadata: MetadataArgs, + id: AccountId, } - impl RunArgs for Register { - fn run(self, context: &mut dyn RunContext) -> Result<()> { - let Self { id, metadata } = self; - let create_account = iroha::data_model::isi::Register::account(Account::new(id)); - submit([create_account], metadata.load()?, context) - .wrap_err("Failed to register account") - } + #[derive(clap::Args, Debug)] + pub struct IdRole { + /// Account in form "multihash@domain" + #[arg(short, long)] + pub id: AccountId, + /// Role name as double-quoted string + #[arg(short, long)] + pub role: RoleId, } - /// List accounts with this command - #[derive(clap::Subcommand, Debug, Clone)] + #[derive(clap::Subcommand, Debug)] pub enum List { - /// All accounts + /// List all account ids All, /// Filter accounts by given predicate Filter(filter::AccountFilter), } - impl RunArgs for List { - fn run(self, context: &mut dyn RunContext) -> Result<()> { + impl Run for List { + fn run(self, context: &mut C) -> Result<()> { let client = context.client_from_config(); - - let query = client.query(FindAccounts::new()); - + let query = client.query(FindAccounts).select_with(|entry| entry.id); let query = match self { List::All => query, List::Filter(filter) => query.filter(filter.predicate), }; - - let result = query.execute_all().wrap_err("Failed to get all accounts")?; - context.print_data(&result)?; - - Ok(()) - } - } - - #[derive(clap::Args, Debug)] - pub struct Grant { - /// Account id - #[arg(short, long)] - pub id: AccountId, - /// The JSON/JSON5 file with a permission token - #[arg(short, long)] - pub permission: Permission, - #[command(flatten)] - pub metadata: MetadataArgs, - } - - /// [`DataModelPermission`] wrapper implementing [`FromStr`] - #[derive(Debug, Clone)] - pub struct Permission(DataModelPermission); - - impl FromStr for Permission { - type Err = Error; - - fn from_str(s: &str) -> Result { - let content = fs::read_to_string(s) - .wrap_err(format!("Failed to read the permission token file {}", &s))?; - let permission: DataModelPermission = json5::from_str(&content).wrap_err(format!( - "Failed to deserialize the permission token from file {}", - &s - ))?; - Ok(Self(permission)) - } - } - - impl RunArgs for Grant { - fn run(self, context: &mut dyn RunContext) -> Result<()> { - let Self { - id, - permission, - metadata, - } = self; - let grant = iroha::data_model::isi::Grant::account_permission(permission.0, id); - submit([grant], metadata.load()?, context) - .wrap_err("Failed to grant the permission to the account") - } - } - - /// List all account permissions - #[derive(clap::Args, Debug)] - pub struct ListPermissions { - /// Account id - #[arg(short, long)] - id: AccountId, - } - - impl RunArgs for ListPermissions { - fn run(self, context: &mut dyn RunContext) -> Result<()> { - let client = context.client_from_config(); - let find_all_permissions = FindPermissionsByAccountId::new(self.id); - let permissions = client - .query(find_all_permissions) - .execute_all() - .wrap_err("Failed to get all account permissions")?; - context.print_data(&permissions)?; - Ok(()) + let ids = query.execute_all()?; + context.print_data(&ids) } } } @@ -747,37 +742,105 @@ mod asset { use super::*; - /// Subcommand for dealing with asset #[derive(clap::Subcommand, Debug)] - pub enum Args { - /// Command for managing asset definitions - #[clap(subcommand)] - Definition(definition::Args), - /// Command for minting asset in existing Iroha account - Mint(Mint), - /// Command for burning asset in existing Iroha account - Burn(Burn), - /// Transfer asset between accounts - Transfer(Transfer), - /// Get info of asset - Get(Get), - /// List assets - #[clap(subcommand)] + pub enum Command { + /// Read/Write asset definitions + #[command(subcommand)] + Definition(definition::Command), + /// Read a single asset details + Get(Id), + /// List asset ids + #[command(subcommand)] List(List), - /// Get a value from a Store asset - GetKeyValue(GetKeyValue), - /// Set a key-value entry in a Store asset - SetKeyValue(SetKeyValue), - /// Remove a key-value entry from a Store asset - RemoveKeyValue(RemoveKeyValue), - } - - impl RunArgs for Args { - fn run(self, context: &mut dyn RunContext) -> Result<()> { - match_all!( - (self, context), - { Args::Definition, Args::Mint, Args::Burn, Args::Transfer, Args::Get, Args::List, Args::SetKeyValue, Args::RemoveKeyValue, Args::GetKeyValue} - ) + /// Increase an amount of asset + Mint(IdQuantity), + /// Decrease an amount of asset + Burn(IdQuantity), + /// Transfer an amount of asset between accounts + #[command(name = "transfer")] + TransferNumeric(TransferNumeric), + /// Transfer a key-value store between accounts + #[command(name = "transferkvs")] + TransferStore(TransferStore), + /// Read a value from a key-value store + #[command(name = "getkv")] + GetKeyValue(IdKey), + /// Create or update an entry in a key-value store, with a value constructed from a JSON5 stdin + #[command(name = "setkv")] + SetKeyValue(IdKey), + /// Delete an entry from a key-value store + #[command(name = "removekv")] + RemoveKeyValue(IdKey), + } + + impl Run for Command { + fn run(self, context: &mut C) -> Result<()> { + use self::Command::*; + match self { + Definition(cmd) => cmd.run(context), + Get(args) => { + let client = context.client_from_config(); + let entry = client + .query(FindAssets) + .filter_with(|entry| entry.id.eq(args.id)) + .execute_single() + .wrap_err("Failed to get asset")?; + context.print_data(&entry) + } + List(cmd) => cmd.run(context), + Mint(args) => { + let instruction = + iroha::data_model::isi::Mint::asset_numeric(args.quantity, args.id); + context + .finish([instruction]) + .wrap_err("Failed to mint numeric asset") + } + Burn(args) => { + let instruction = + iroha::data_model::isi::Burn::asset_numeric(args.quantity, args.id); + context + .finish([instruction]) + .wrap_err("Failed to burn numeric asset") + } + TransferNumeric(args) => { + let instruction = iroha::data_model::isi::Transfer::asset_numeric( + args.id, + args.quantity, + args.to, + ); + context + .finish([instruction]) + .wrap_err("Failed to transfer numeric asset") + } + TransferStore(args) => { + let instruction = + iroha::data_model::isi::Transfer::asset_store(args.id, args.to); + context + .finish([instruction]) + .wrap_err("Failed to transfer key-value store") + } + GetKeyValue(args) => { + let client = context.client_from_config(); + let value = client + .query(FindAssets) + .filter_with(|asset| asset.id.eq(args.id)) + .select_with(|asset| asset.value.store.key(args.key)) + .execute_single() + .wrap_err("Failed to get value")?; + context.print_data(&value) + } + SetKeyValue(args) => { + let value: Json = parse_json5_stdin()?; + let instruction = + iroha::data_model::isi::SetKeyValue::asset(args.id, args.key, value); + context.finish([instruction]) + } + RemoveKeyValue(args) => { + let instruction = + iroha::data_model::isi::RemoveKeyValue::asset(args.id, args.key); + context.finish([instruction]) + } + } } } @@ -786,30 +849,72 @@ mod asset { use super::*; - /// Subcommand for managing asset definitions #[derive(clap::Subcommand, Debug)] - pub enum Args { - /// Command for Registering a new asset - Register(Register), - /// List asset definitions - #[clap(subcommand)] + pub enum Command { + /// List asset definition ids + #[command(subcommand)] List(List), + /// Read a single asset definition details + Get(Id), + /// Register asset definition + Register(Register), + /// Unregister asset definition + Unregister(Id), + /// Transfer asset definition + Transfer(Transfer), + /// Read/Write metadata + #[command(subcommand)] + Meta(metadata::asset_definition::Command), } - impl RunArgs for Args { - fn run(self, context: &mut dyn RunContext) -> Result<()> { - match_all!( - (self, context), - { Args::Register, Args::List } - ) + impl Run for Command { + fn run(self, context: &mut C) -> Result<()> { + use self::Command::*; + match self { + List(cmd) => cmd.run(context), + Get(args) => { + let client = context.client_from_config(); + let entry = client + .query(FindAssetsDefinitions) + .filter_with(|entry| entry.id.eq(args.id)) + .execute_single() + .wrap_err("Failed to get asset definition")?; + context.print_data(&entry) + } + Register(args) => { + let mut entry = AssetDefinition::new(args.id, args.r#type); + if args.unmintable { + entry = entry.mintable_once(); + } + let instruction = iroha::data_model::isi::Register::asset_definition(entry); + context + .finish([instruction]) + .wrap_err("Failed to register asset") + } + Unregister(args) => { + let instruction = + iroha::data_model::isi::Unregister::asset_definition(args.id); + context + .finish([instruction]) + .wrap_err("Failed to unregister asset") + } + Transfer(args) => { + let instruction = iroha::data_model::isi::Transfer::asset_definition( + args.from, args.id, args.to, + ); + context + .finish([instruction]) + .wrap_err("Failed to transfer asset definition") + } + Meta(cmd) => cmd.run(context), + } } } - /// Register subcommand of asset #[derive(clap::Args, Debug)] pub struct Register { - /// Asset definition id for registering (in form of `asset#domain_name`) - #[arg(long)] + /// Asset definition in form "asset#domain" + #[arg(short, long)] pub id: AssetDefinitionId, /// Mintability of asset #[arg(short, long)] @@ -817,257 +922,120 @@ mod asset { /// Value type stored in asset #[arg(short, long)] pub r#type: AssetType, - #[command(flatten)] - pub metadata: MetadataArgs, } - impl RunArgs for Register { - fn run(self, context: &mut dyn RunContext) -> Result<()> { - let Self { - id: asset_id, - r#type, - unmintable, - metadata, - } = self; - let mut asset_definition = AssetDefinition::new(asset_id, r#type); - if unmintable { - asset_definition = asset_definition.mintable_once(); - } - let create_asset_definition = - iroha::data_model::isi::Register::asset_definition(asset_definition); - submit([create_asset_definition], metadata.load()?, context) - .wrap_err("Failed to register asset") - } + #[derive(clap::Args, Debug)] + pub struct Transfer { + /// Asset definition in form "asset#domain" + #[arg(short, long)] + pub id: AssetDefinitionId, + /// Account from which to transfer, in form "multihash@domain" + #[arg(short, long)] + pub from: AccountId, + /// Account to which to transfer, in form "multihash@domain" + #[arg(short, long)] + pub to: AccountId, } - /// List asset definitions with this command - #[derive(clap::Subcommand, Debug, Clone)] - pub enum List { - /// All asset definitions - All, - /// Filter asset definitions by given predicate - Filter(filter::AssetDefinitionFilter), + #[derive(clap::Args, Debug)] + pub struct Id { + /// Asset definition in form "asset#domain" + #[arg(short, long)] + pub id: AssetDefinitionId, } - impl RunArgs for List { - fn run(self, context: &mut dyn RunContext) -> Result<()> { - let client = context.client_from_config(); - - let query = client.query(FindAssetsDefinitions::new()); + #[derive(clap::Subcommand, Debug)] + pub enum List { + /// List all asset definition ids + All, + /// Filter asset definitions by given predicate + Filter(filter::AssetDefinitionFilter), + } + impl Run for List { + fn run(self, context: &mut C) -> Result<()> { + let client = context.client_from_config(); + let query = client + .query(FindAssetsDefinitions) + .select_with(|entry| entry.id); let query = match self { List::All => query, List::Filter(filter) => query.filter(filter.predicate), }; - - let result = query - .execute_all() - .wrap_err("Failed to get all asset definitions")?; - - context.print_data(&result)?; - Ok(()) + let ids = query.execute_all()?; + context.print_data(&ids) } } } - /// Command for minting asset in existing Iroha account #[derive(clap::Args, Debug)] - pub struct Mint { - /// Asset id for the asset (in form of `asset##account@domain_name`) - #[arg(long)] + pub struct TransferNumeric { + /// Asset to transfer, in form "asset##account@domain" or "asset#another_domain#account@domain" + #[arg(short, long)] pub id: AssetId, - /// Quantity to mint + /// Account to which to transfer, in form "multihash@domain" + #[arg(short, long)] + pub to: AccountId, + /// Amount to transfer, in an integer or decimal #[arg(short, long)] pub quantity: Numeric, - #[command(flatten)] - pub metadata: MetadataArgs, - } - - impl RunArgs for Mint { - fn run(self, context: &mut dyn RunContext) -> Result<()> { - let Self { - id: asset_id, - quantity, - metadata, - } = self; - let mint_asset = iroha::data_model::isi::Mint::asset_numeric(quantity, asset_id); - submit([mint_asset], metadata.load()?, context) - .wrap_err("Failed to mint asset of type `Numeric`") - } } - /// Command for minting asset in existing Iroha account #[derive(clap::Args, Debug)] - pub struct Burn { - /// Asset id for the asset (in form of `asset##account@domain_name`) - #[arg(long)] + pub struct TransferStore { + /// Asset to transfer, in form "asset##account@domain" or "asset#another_domain#account@domain" + #[arg(short, long)] pub id: AssetId, - /// Quantity to mint + /// Account to which to transfer, in form "multihash@domain" #[arg(short, long)] - pub quantity: Numeric, - #[command(flatten)] - pub metadata: MetadataArgs, - } - - impl RunArgs for Burn { - fn run(self, context: &mut dyn RunContext) -> Result<()> { - let Self { - id: asset_id, - quantity, - metadata, - } = self; - let burn_asset = iroha::data_model::isi::Burn::asset_numeric(quantity, asset_id); - submit([burn_asset], metadata.load()?, context) - .wrap_err("Failed to burn asset of type `Numeric`") - } + pub to: AccountId, } - /// Transfer asset between accounts #[derive(clap::Args, Debug)] - pub struct Transfer { - /// Account to which to transfer (in form `name@domain_name`) - #[arg(long)] - pub to: AccountId, - /// Asset id to transfer (in form like `asset##account@domain_name`) - #[arg(long)] - pub id: AssetId, - /// Quantity of asset as number + pub struct Id { + /// Asset in form "asset##account@domain" or "asset#another_domain#account@domain" #[arg(short, long)] - pub quantity: Numeric, - #[command(flatten)] - pub metadata: MetadataArgs, - } - - impl RunArgs for Transfer { - fn run(self, context: &mut dyn RunContext) -> Result<()> { - let Self { - to, - id: asset_id, - quantity, - metadata, - } = self; - let transfer_asset = - iroha::data_model::isi::Transfer::asset_numeric(asset_id, quantity, to); - submit([transfer_asset], metadata.load()?, context).wrap_err("Failed to transfer asset") - } + pub id: AssetId, } - /// Get info of asset #[derive(clap::Args, Debug)] - pub struct Get { - /// Asset id for the asset (in form of `asset##account@domain_name`) - #[arg(long)] + pub struct IdQuantity { + /// Asset in form "asset##account@domain" or "asset#another_domain#account@domain" + #[arg(short, long)] pub id: AssetId, + /// Amount in an integer or decimal + #[arg(short, long)] + pub quantity: Numeric, } - impl RunArgs for Get { - fn run(self, context: &mut dyn RunContext) -> Result<()> { - let Self { id: asset_id } = self; - let client = context.client_from_config(); - let asset = client - .query(FindAssets::new()) - .filter_with(|asset| asset.id.eq(asset_id)) - .execute_single() - .wrap_err("Failed to get asset.")?; - context.print_data(&asset)?; - Ok(()) - } + #[derive(clap::Args, Debug)] + pub struct IdKey { + /// Asset in form "asset##account@domain" or "asset#another_domain#account@domain" + #[arg(short, long)] + pub id: AssetId, + /// Key for the value + #[arg(short, long)] + pub key: Name, } - /// List assets with this command - #[derive(clap::Subcommand, Debug, Clone)] + #[derive(clap::Subcommand, Debug)] pub enum List { - /// All assets + /// List all asset ids All, /// Filter assets by given predicate Filter(filter::AssetFilter), } - impl RunArgs for List { - fn run(self, context: &mut dyn RunContext) -> Result<()> { + impl Run for List { + fn run(self, context: &mut C) -> Result<()> { let client = context.client_from_config(); - - let query = client.query(FindAssets::new()); - + let query = client.query(FindAssets).select_with(|entry| entry.id); let query = match self { List::All => query, List::Filter(filter) => query.filter(filter.predicate), }; - - let result = query.execute_all().wrap_err("Failed to get all accounts")?; - context.print_data(&result)?; - - Ok(()) - } - } - - #[derive(clap::Args, Debug)] - pub struct SetKeyValue { - /// Asset id for the Store asset (in form of `asset##account@domain_name`) - #[clap(long)] - pub id: AssetId, - /// The key for the store value - #[clap(long)] - pub key: Name, - #[command(flatten)] - pub value: MetadataValueArg, - } - - impl RunArgs for SetKeyValue { - fn run(self, context: &mut dyn RunContext) -> Result<()> { - let Self { - id: asset_id, - key, - value: MetadataValueArg { value }, - } = self; - - let set = iroha::data_model::isi::SetKeyValue::asset(asset_id, key, value); - submit([set], Metadata::default(), context)?; - Ok(()) - } - } - #[derive(clap::Args, Debug)] - pub struct RemoveKeyValue { - /// Asset id for the Store asset (in form of `asset##account@domain_name`) - #[clap(long)] - pub id: AssetId, - /// The key for the store value - #[clap(long)] - pub key: Name, - } - - impl RunArgs for RemoveKeyValue { - fn run(self, context: &mut dyn RunContext) -> Result<()> { - let Self { id: asset_id, key } = self; - let remove = iroha::data_model::isi::RemoveKeyValue::asset(asset_id, key); - submit([remove], Metadata::default(), context)?; - Ok(()) - } - } - - #[derive(clap::Args, Debug)] - pub struct GetKeyValue { - /// Asset id for the Store asset (in form of `asset##account@domain_name`) - #[clap(long)] - pub id: AssetId, - /// The key for the store value - #[clap(long)] - pub key: Name, - } - - impl RunArgs for GetKeyValue { - fn run(self, context: &mut dyn RunContext) -> Result<()> { - let Self { id: asset_id, key } = self; - let client = context.client_from_config(); - let asset = client - .query(FindAssets) - .filter_with(|asset| asset.id.eq(asset_id)) - .select_with(|asset| asset.value.store.key(key)) - .execute_single() - .wrap_err("Failed to get key-value")?; - - context.print_data(&asset)?; - Ok(()) + let ids = query.execute_all()?; + context.print_data(&ids) } } } @@ -1075,196 +1043,63 @@ mod asset { mod peer { use super::*; - /// Subcommand for dealing with peer #[derive(clap::Subcommand, Debug)] - pub enum Args { - /// Register subcommand of peer - Register(Box), - /// Unregister subcommand of peer - Unregister(Box), + pub enum Command { + /// List registered peers expected to connect with each other + #[command(subcommand)] + List(List), + /// Register peer + Register(Id), + /// Unregister peer + Unregister(Id), } - impl RunArgs for Args { - fn run(self, context: &mut dyn RunContext) -> Result<()> { + impl Run for Command { + fn run(self, context: &mut C) -> Result<()> { + use self::Command::*; match self { - Args::Register(register) => RunArgs::run(*register, context), - Args::Unregister(unregister) => RunArgs::run(*unregister, context), + List(cmd) => cmd.run(context), + Register(args) => { + let instruction = iroha::data_model::isi::Register::peer(args.key.into()); + context + .finish([instruction]) + .wrap_err("Failed to register peer") + } + Unregister(args) => { + let instruction = iroha::data_model::isi::Unregister::peer(args.key.into()); + context + .finish([instruction]) + .wrap_err("Failed to unregister peer") + } } } } - /// Register subcommand of peer - #[derive(clap::Args, Debug)] - pub struct Register { - /// Public key of the peer - #[arg(short, long)] - pub key: PublicKey, - #[command(flatten)] - pub metadata: MetadataArgs, + #[derive(clap::Subcommand, Debug)] + pub enum List { + /// List all registered peers + All, } - impl RunArgs for Register { - fn run(self, context: &mut dyn RunContext) -> Result<()> { - let Self { key, metadata } = self; - let register_peer = iroha::data_model::isi::Register::peer(key.into()); - submit([register_peer], metadata.load()?, context).wrap_err("Failed to register peer") + impl Run for List { + fn run(self, context: &mut C) -> Result<()> { + let client = context.client_from_config(); + let entries = client.query(FindPeers).execute_all()?; + context.print_data(&entries) } } - /// Unregister subcommand of peer #[derive(clap::Args, Debug)] - pub struct Unregister { - /// Public key of the peer + pub struct Id { + /// Peer's public key in multihash #[arg(short, long)] pub key: PublicKey, - #[command(flatten)] - pub metadata: MetadataArgs, - } - - impl RunArgs for Unregister { - fn run(self, context: &mut dyn RunContext) -> Result<()> { - let Self { key, metadata } = self; - let unregister_peer = iroha::data_model::isi::Unregister::peer(key.into()); - submit([unregister_peer], metadata.load()?, context) - .wrap_err("Failed to unregister peer") - } - } -} - -mod wasm { - use std::{io::Read, path::PathBuf}; - - use super::*; - - /// Subcommand for dealing with Wasm - #[derive(Debug, clap::Args)] - pub struct Args { - /// Specify a path to the Wasm file or skip this flag to read from stdin - #[arg(short, long)] - path: Option, - } - - impl RunArgs for Args { - fn run(self, context: &mut dyn RunContext) -> Result<()> { - let raw_data = if let Some(path) = self.path { - read_file(path).wrap_err("Failed to read a Wasm from the file into the buffer")? - } else { - let mut buf = Vec::::new(); - stdin() - .read_to_end(&mut buf) - .wrap_err("Failed to read a Wasm from stdin into the buffer")?; - buf - }; - - submit( - WasmSmartContract::from_compiled(raw_data), - Metadata::default(), - context, - ) - .wrap_err("Failed to submit a Wasm smart contract") - } - } -} - -mod json { - use std::io::{BufReader, Read as _}; - - use clap::Subcommand; - use iroha::data_model::query::AnyQueryBox; - - use super::*; - - /// Subcommand for submitting multi-instructions - #[derive(Clone, Copy, Debug, clap::Args)] - pub struct Args { - #[clap(subcommand)] - variant: Variant, - } - - #[derive(Clone, Copy, Debug, Subcommand)] - enum Variant { - Transaction, - Query, - } - - impl RunArgs for Args { - fn run(self, context: &mut dyn RunContext) -> Result<()> { - let mut reader = BufReader::new(stdin()); - let mut raw_content = Vec::new(); - reader.read_to_end(&mut raw_content)?; - - let string_content = String::from_utf8(raw_content)?; - - match self.variant { - Variant::Transaction => { - let instructions: Vec = json5::from_str(&string_content)?; - submit(instructions, Metadata::default(), context) - .wrap_err("Failed to submit parsed instructions") - } - Variant::Query => { - let client = Client::new(context.configuration().clone()); - let query: AnyQueryBox = json5::from_str(&string_content)?; - - match query { - AnyQueryBox::Singular(query) => { - let result = client - .query_single(query) - .wrap_err("Failed to query response")?; - - context.print_data(&result)?; - } - AnyQueryBox::Iterable(query) => { - // we can't really do type-erased iterable queries in a nice way right now... - use iroha::data_model::query::builder::QueryExecutor; - - let (mut accumulated_batch, _remaining_items, mut continue_cursor) = - client.start_query(query)?; - - while let Some(cursor) = continue_cursor { - let (next_batch, _remaining_items, next_continue_cursor) = - ::continue_query(cursor)?; - - accumulated_batch.extend(next_batch); - continue_cursor = next_continue_cursor; - } - - // for efficiency reasons iroha encodes query results in a columnar format, - // so we need to transpose the batch to get the format that is more natural for humans - let mut batches = vec![Vec::new(); accumulated_batch.len()]; - for batch in accumulated_batch { - // downcast to json and extract the actual array - // dynamic typing is just easier to use here than introducing a bunch of new types only for iroha_cli - let batch = serde_json::to_value(batch)?; - let serde_json::Value::Object(batch) = batch else { - panic!("Expected the batch serialization to be a JSON object"); - }; - let (_ty, batch) = batch - .into_iter() - .next() - .expect("Expected the batch to have exactly one key"); - let serde_json::Value::Array(batch_vec) = batch else { - panic!("Expected the batch payload to be a JSON array"); - }; - for (target, value) in batches.iter_mut().zip(batch_vec) { - target.push(value); - } - } - - context.print_data(&batches)?; - } - } - - Ok(()) - } - } - } } } mod multisig { use std::{ collections::BTreeMap, - io::{BufReader, Read as _}, num::{NonZeroU16, NonZeroU64}, time::{Duration, SystemTime}, }; @@ -1276,27 +1111,26 @@ mod multisig { use super::*; - /// Arguments for multisig subcommand - #[derive(Debug, clap::Subcommand)] - pub enum Args { + #[derive(clap::Subcommand, Debug)] + pub enum Command { + /// List pending multisig transactions relevant to you + #[command(subcommand)] + List(List), /// Register a multisig account Register(Register), - /// Propose a multisig transaction, with `Vec` stdin + /// Propose a multisig transaction, constructed from instructions as a JSON5 stdin Propose(Propose), /// Approve a multisig transaction Approve(Approve), - /// List pending multisig transactions relevant to you - #[clap(subcommand)] - List(List), } - impl RunArgs for Args { - fn run(self, context: &mut dyn RunContext) -> Result<()> { - match_all!((self, context), { Args::Register, Args::Propose, Args::Approve, Args::List }) + impl Run for Command { + fn run(self, context: &mut C) -> Result<()> { + use self::Command::*; + match_all!((self, context), { List, Register, Propose, Approve }) } } - /// Args to register a multisig account - #[derive(Debug, clap::Args)] + #[derive(clap::Args, Debug)] pub struct Register { /// ID of the multisig account to be registered #[arg(short, long)] @@ -1310,7 +1144,7 @@ mod multisig { /// Threshold of total weight at which the multisig is considered authenticated #[arg(short, long)] pub quorum: u16, - /// Time-to-live of multisig transactions made by the multisig account + /// Time-to-live of multisig transactions made by the multisig account ex. "1y 6M 2w 3d 12h 30m 30s 500ms" #[arg(short, long, default_value_t = default_transaction_ttl())] pub transaction_ttl: humantime::Duration, } @@ -1319,12 +1153,12 @@ mod multisig { std::time::Duration::from_millis(DEFAULT_MULTISIG_TTL_MS).into() } - impl RunArgs for Register { - fn run(self, context: &mut dyn RunContext) -> Result<()> { + impl Run for Register { + fn run(self, context: &mut C) -> Result<()> { if self.signatories.len() != self.weights.len() { return Err(eyre!("signatories and weights must be equal in length")); } - let register_multisig_account = MultisigRegister::new( + let instruction = MultisigRegister::new( self.account, MultisigSpec::new( self.signatories.into_iter().zip(self.weights).collect(), @@ -1338,31 +1172,25 @@ mod multisig { ), ); - submit([register_multisig_account], Metadata::default(), context) + context + .finish([instruction]) .wrap_err("Failed to register multisig account") } } - /// Args to propose a multisig transaction - #[derive(Debug, clap::Args)] + #[derive(clap::Args, Debug)] pub struct Propose { /// Multisig authority of the multisig transaction #[arg(short, long)] pub account: AccountId, - /// Time-to-live of multisig transactions that overrides to shorten the account default + /// Time-to-live of multisig transactions that overrides to shorten the account default ex. "1y 6M 2w 3d 12h 30m 30s 500ms" #[arg(short, long)] pub transaction_ttl: Option, } - impl RunArgs for Propose { - fn run(self, context: &mut dyn RunContext) -> Result<()> { - let instructions: Vec = { - let mut reader = BufReader::new(stdin()); - let mut raw_content = Vec::new(); - reader.read_to_end(&mut raw_content)?; - let string_content = String::from_utf8(raw_content)?; - json5::from_str(&string_content)? - }; + impl Run for Propose { + fn run(self, context: &mut C) -> Result<()> { + let instructions: Vec = parse_json5_stdin()?; let transaction_ttl_ms = self.transaction_ttl.map(|duration| { duration .as_millis() @@ -1378,13 +1206,13 @@ mod multisig { let propose_multisig_transaction = MultisigPropose::new(self.account, instructions, transaction_ttl_ms); - submit([propose_multisig_transaction], Metadata::default(), context) + context + .finish([propose_multisig_transaction]) .wrap_err("Failed to propose transaction") } } - /// Args to approve a multisig transaction - #[derive(Debug, clap::Args)] + #[derive(clap::Args, Debug)] pub struct Approve { /// Multisig authority of the multisig transaction #[arg(short, long)] @@ -1394,25 +1222,25 @@ mod multisig { pub instructions_hash: ProposalKey, } - impl RunArgs for Approve { - fn run(self, context: &mut dyn RunContext) -> Result<()> { + impl Run for Approve { + fn run(self, context: &mut C) -> Result<()> { let approve_multisig_transaction = MultisigApprove::new(self.account, self.instructions_hash); - submit([approve_multisig_transaction], Metadata::default(), context) + context + .finish([approve_multisig_transaction]) .wrap_err("Failed to approve transaction") } } - /// List pending multisig transactions relevant to you - #[derive(clap::Subcommand, Debug, Clone)] + #[derive(clap::Subcommand, Debug)] pub enum List { - /// All pending multisig transactions relevant to you + /// List all pending multisig transactions relevant to you All, } - impl RunArgs for List { - fn run(self, context: &mut dyn RunContext) -> Result<()> { + impl Run for List { + fn run(self, context: &mut C) -> Result<()> { let client = context.client_from_config(); let me = client.account.clone(); let Ok(my_multisig_roles) = client @@ -1430,9 +1258,7 @@ mod multisig { let mut proposals = BTreeMap::new(); fold_proposals(&mut proposals, &mut stack, &client)?; - context.print_data(&proposals)?; - - Ok(()) + context.print_data(&proposals) } } @@ -1581,7 +1407,7 @@ mod multisig { proposal_status.instructions = proposal_value.instructions; proposal_status.proposed_at = { let proposed_at = Duration::from_secs( - Duration::from_millis(proposal_value.proposed_at_ms.into()).as_secs(), + Duration::from_millis(proposal_value.proposed_at_ms).as_secs(), ); SystemTime::UNIX_EPOCH .checked_add(proposed_at) @@ -1592,7 +1418,7 @@ mod multisig { let now = SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .unwrap(); - let expires_at = Duration::from_millis(proposal_value.expires_at_ms.into()); + let expires_at = Duration::from_millis(proposal_value.expires_at_ms); Duration::from_secs(expires_at.saturating_sub(now).as_secs()).into() }; } @@ -1601,30 +1427,592 @@ mod multisig { } } -#[cfg(test)] -mod tests { +mod query { + use iroha::data_model::query::AnyQueryBox; + + use super::*; + + #[derive(clap::Subcommand, Debug)] + pub enum Command { + /// Query constructed from a JSON5 stdin + Stdin(Stdin), + } + + impl Run for Command { + fn run(self, context: &mut C) -> Result<()> { + use self::Command::*; + match_all!((self, context), { Stdin }) + } + } + + #[derive(clap::Args, Debug)] + pub struct Stdin; + + impl Run for Stdin { + fn run(self, context: &mut C) -> Result<()> { + let client = Client::new(context.config().clone()); + let query: AnyQueryBox = parse_json5_stdin()?; + + match query { + AnyQueryBox::Singular(query) => { + let result = client + .query_single(query) + .wrap_err("Failed to query response")?; + + context.print_data(&result) + } + AnyQueryBox::Iterable(query) => { + // we can't really do type-erased iterable queries in a nice way right now... + use iroha::data_model::query::builder::QueryExecutor; + + let (mut accumulated_batch, _remaining_items, mut continue_cursor) = + client.start_query(query)?; + + while let Some(cursor) = continue_cursor { + let (next_batch, _remaining_items, next_continue_cursor) = + ::continue_query(cursor)?; + + accumulated_batch.extend(next_batch); + continue_cursor = next_continue_cursor; + } + + // for efficiency reasons iroha encodes query results in a columnar format, + // so we need to transpose the batch to get the format that is more natural for humans + let mut batches = vec![Vec::new(); accumulated_batch.len()]; + for batch in accumulated_batch { + // downcast to json and extract the actual array + // dynamic typing is just easier to use here than introducing a bunch of new types only for iroha_cli + let batch = serde_json::to_value(batch)?; + let serde_json::Value::Object(batch) = batch else { + panic!("Expected the batch serialization to be a JSON object"); + }; + let (_ty, batch) = batch + .into_iter() + .next() + .expect("Expected the batch to have exactly one key"); + let serde_json::Value::Array(batch_vec) = batch else { + panic!("Expected the batch payload to be a JSON array"); + }; + for (target, value) in batches.iter_mut().zip(batch_vec) { + target.push(value); + } + } + + context.print_data(&batches) + } + } + } + } +} + +mod transaction { + use iroha::data_model::{isi::Log, Level as LogLevel}; + use super::*; - #[test] - fn parse_value_arg_cases() { - macro_rules! case { - ($input:expr, $expected:expr) => { - let MetadataValueArg { value } = - $input.parse().expect("should not fail with valid input"); - assert_eq!(value, $expected); + #[derive(clap::Subcommand, Debug)] + pub enum Command { + /// Read a single transaction details + Get(Get), + /// Empty transaction that just leaves a log message + Ping(Ping), + /// Transaction constructed from a Wasm executable input + Wasm(Wasm), + /// Transaction constructed from instructions as a JSON5 stdin + Stdin(Stdin), + } + + impl Run for Command { + fn run(self, context: &mut C) -> Result<()> { + use self::Command::*; + match_all!((self, context), { Get, Ping, Wasm, Stdin }) + } + } + + #[derive(clap::Args, Debug)] + pub struct Get { + /// Transaction hash + #[arg(short('H'), long)] + pub hash: HashOf, + } + + impl Run for Get { + fn run(self, context: &mut C) -> Result<()> { + let client = context.client_from_config(); + let transaction = client + .query(FindTransactions) + .filter_with(|txn| txn.value.hash.eq(self.hash)) + .execute_single()?; + context.print_data(&transaction) + } + } + + #[derive(clap::Args, Debug)] + pub struct Ping { + /// TRACE, DEBUG, INFO, WARN, ERROR: grows more noticeable in this order + #[arg(short, long, default_value = "INFO")] + pub log_level: LogLevel, + /// Log message + #[arg(short, long)] + pub msg: String, + } + + impl Run for Ping { + fn run(self, context: &mut C) -> Result<()> { + let instruction = Log::new(self.log_level, self.msg); + context.finish([instruction]) + } + } + + #[derive(clap::Args, Debug)] + pub struct Wasm { + /// Specify a path to the Wasm file or skip this arg to read from stdin + #[arg(short, long)] + path: Option, + } + + impl Run for Wasm { + fn run(self, context: &mut C) -> Result<()> { + let blob = if let Some(path) = self.path { + fs::read(path).wrap_err("Failed to read a Wasm from the file into the buffer")? + } else { + bytes_from_stdin().wrap_err("Failed to read a Wasm from stdin into the buffer")? }; + + context + .finish(WasmSmartContract::from_compiled(blob)) + .wrap_err("Failed to submit a Wasm transaction") + } + } + + #[derive(clap::Args, Debug)] + pub struct Stdin; + + impl Run for Stdin { + fn run(self, context: &mut C) -> Result<()> { + let instructions: Vec = parse_json5_stdin()?; + context + .finish(instructions) + .wrap_err("Failed to submit parsed instructions") + } + } +} + +mod role { + use super::*; + + #[derive(clap::Subcommand, Debug)] + pub enum Command { + /// Read/Write role permissions + #[command(subcommand)] + Permission(PermissionCommand), + /// List role ids + #[command(subcommand)] + List(List), + /// Register role and grant it to you registrant + Register(Id), + /// Unregister role + Unregister(Id), + } + + impl Run for Command { + fn run(self, context: &mut C) -> Result<()> { + use self::Command::*; + match self { + Permission(cmd) => cmd.run(context), + List(cmd) => cmd.run(context), + Register(args) => { + let instruction = iroha::data_model::isi::Register::role(Role::new( + args.id, + context.config().account.clone(), + )); + context + .finish([instruction]) + .wrap_err("Failed to register role") + } + Unregister(args) => { + let instruction = iroha::data_model::isi::Unregister::role(args.id); + context + .finish([instruction]) + .wrap_err("Failed to unregister role") + } + } + } + } + + #[derive(clap::Subcommand, Debug)] + pub enum PermissionCommand { + /// List role permissions + List(Id), + /// Grant role permission constructed from a JSON5 stdin + Grant(Id), + /// Revoke role permission constructed from a JSON5 stdin + Revoke(Id), + } + + impl Run for PermissionCommand { + fn run(self, context: &mut C) -> Result<()> { + use self::PermissionCommand::*; + match self { + List(args) => { + let client = context.client_from_config(); + let role = client + .query(FindRoles) + .filter_with(|entry| entry.id.eq(args.id)) + .execute_single()?; + for permission in role.permissions() { + context.print_data(&permission)?; + } + Ok(()) + } + Grant(args) => { + let permission: Permission = parse_json5_stdin()?; + let instruction = + iroha::data_model::isi::Grant::role_permission(permission, args.id); + context + .finish([instruction]) + .wrap_err("Failed to grant the permission to the role") + } + Revoke(args) => { + let permission: Permission = parse_json5_stdin()?; + let instruction = + iroha::data_model::isi::Revoke::role_permission(permission, args.id); + context + .finish([instruction]) + .wrap_err("Failed to revoke the permission from the role") + } + } + } + } + + #[derive(clap::Args, Debug)] + pub struct Id { + /// Role name as double-quoted string + #[arg(short, long)] + id: RoleId, + } + + #[derive(clap::Subcommand, Debug)] + pub enum List { + /// List all role ids + All, + } + + impl Run for List { + fn run(self, context: &mut C) -> Result<()> { + let client = context.client_from_config(); + let ids = client.query(FindRoleIds).execute_all()?; + context.print_data(&ids) + } + } +} + +mod parameter { + use super::*; + + #[derive(clap::Subcommand, Debug)] + pub enum Command { + /// List parameters + #[command(subcommand)] + List(List), + /// Set parameter constructed from a JSON5 stdin + Set(Set), + } + + impl Run for Command { + fn run(self, context: &mut C) -> Result<()> { + use self::Command::*; + match_all!((self, context), { List, Set }) } + } - // Boolean values - case!("true", Json::new(true)); - case!("false", Json::new(false)); + #[derive(clap::Subcommand, Debug)] + pub enum List { + /// List all parameters + All, + } - // Numeric values - case!("\"123\"", Json::new(numeric!(123))); - case!("\"123.0\"", Json::new(numeric!(123.0))); + impl Run for List { + fn run(self, context: &mut C) -> Result<()> { + let client = context.client_from_config(); + let params = client.query_single(FindParameters)?; + context.print_data(¶ms) + } + } + + #[derive(clap::Args, Debug)] + pub struct Set; - // JSON Value - let json_str = r#"{"Vec":[{"String":"a"},{"String":"b"}]}"#; - case!(json_str, serde_json::from_str(json_str).unwrap()); + impl Run for Set { + fn run(self, context: &mut C) -> Result<()> { + let entry: Parameter = parse_json5_stdin()?; + let instruction = SetParameter::new(entry); + context.finish([instruction]) + } } } + +mod trigger { + use super::*; + + #[derive(clap::Subcommand, Debug)] + pub enum Command { + /// List trigger ids + #[command(subcommand)] + List(List), + /// Read a single trigger details + // TODO for readability and reusability, trigger should hold a reference to a Wasm executable instead of the blob itself + Get(Id), + /// TODO Register trigger + Register(Register), + /// Unregister trigger + Unregister(Id), + /// Read/Write metadata + #[command(subcommand)] + Meta(metadata::trigger::Command), + } + + impl Run for Command { + fn run(self, context: &mut C) -> Result<()> { + use self::Command::*; + match self { + List(cmd) => cmd.run(context), + Get(args) => { + let client = context.client_from_config(); + let entry = client + .query(FindTriggers) + .filter_with(|entry| entry.id.eq(args.id)) + .execute_single() + .wrap_err("Failed to get trigger")?; + context.print_data(&entry) + } + Register(args) => args.run(context), + Unregister(args) => { + let instruction = iroha::data_model::isi::Unregister::trigger(args.id); + context + .finish([instruction]) + .wrap_err("Failed to unregister trigger") + } + Meta(cmd) => cmd.run(context), + } + } + } + + #[derive(clap::Subcommand, Debug)] + pub enum List { + /// List all trigger ids + All, + } + + impl Run for List { + fn run(self, context: &mut C) -> Result<()> { + let client = context.client_from_config(); + let ids = client.query(FindActiveTriggerIds).execute_all()?; + context.print_data(&ids) + } + } + + #[derive(clap::Args, Debug)] + pub struct Id { + /// Trigger name as double-quoted string + #[arg(short, long)] + pub id: TriggerId, + } + + #[derive(clap::Args, Debug)] + pub struct Register; + + impl Run for Register { + fn run(self, _context: &mut C) -> Result<()> { + todo!() + } + } +} + +mod executor { + use super::*; + + #[derive(clap::Subcommand, Debug)] + pub enum Command { + /// Upgrade executor + Upgrade(Upgrade), + } + + impl Run for Command { + fn run(self, context: &mut C) -> Result<()> { + use self::Command::*; + match_all!((self, context), { Upgrade }) + } + } + + #[derive(clap::Args, Debug)] + pub struct Upgrade { + /// Path to the compiled Wasm file + #[arg(short, long)] + path: PathBuf, + } + + impl Run for Upgrade { + fn run(self, context: &mut C) -> Result<()> { + let instruction = fs::read(self.path) + .map(WasmSmartContract::from_compiled) + .map(Executor::new) + .map(iroha::data_model::isi::Upgrade::new) + .wrap_err("Failed to read a Wasm from the file")?; + context.finish([instruction]) + } + } +} + +mod metadata { + use super::*; + + macro_rules! impl_metadata_command { + ($entity:ty, $query:expr, $constructor:ident) => { + pub mod $constructor { + use super::*; + + #[derive(clap::Subcommand, Debug)] + pub enum Command { + /// Read a value from a key-value store + Get(IdKey), + /// Create or update an entry in a key-value store, with a value constructed from a JSON5 stdin + Set(IdKey), + /// Delete an entry from a key-value store + Remove(IdKey), + } + + #[derive(clap::Args, Debug)] + pub struct IdKey { + #[arg(short, long)] + pub id: <$entity as Identifiable>::Id, + #[arg(short, long)] + pub key: Name, + } + + impl Run for Command { + fn run(self, context: &mut C) -> Result<()> { + use self::Command::*; + match self { + Get(args) => { + let client = context.client_from_config(); + let value = client + .query($query) + .filter_with(|entry| entry.id.eq(args.id)) + .select_with(|entry| entry.metadata.key(args.key)) + .execute_single() + .wrap_err("Failed to get value")?; + context.print_data(&value) + } + Set(args) => { + let value: Json = parse_json5_stdin()?; + let instruction = iroha::data_model::isi::SetKeyValue::$constructor( + args.id, args.key, value, + ); + context.finish([instruction]) + } + Remove(args) => { + let instruction = + iroha::data_model::isi::RemoveKeyValue::$constructor( + args.id, args.key, + ); + context.finish([instruction]) + } + } + } + } + } + }; + } + + impl_metadata_command!(Domain, FindDomains, domain); + impl_metadata_command!(Account, FindAccounts, account); + impl_metadata_command!(AssetDefinition, FindAssetsDefinitions, asset_definition); + + // TODO apply macro after trigger.action.metadata is relocated to trigger.metadata + pub mod trigger { + use super::*; + + #[derive(clap::Subcommand, Debug)] + pub enum Command { + /// Read a value from a key-value store + Get(IdKey), + /// Create or update an entry in a key-value store, with a value constructed from a JSON5 stdin + Set(IdKey), + /// Delete an entry from a key-value store + Remove(IdKey), + } + + #[derive(clap::Args, Debug)] + pub struct IdKey { + #[arg(short, long)] + pub id: ::Id, + #[arg(short, long)] + pub key: Name, + } + + impl Run for Command { + fn run(self, context: &mut C) -> Result<()> { + use self::Command::*; + match self { + Get(args) => { + let client = context.client_from_config(); + let value = client + .query(FindTriggers) + .filter_with(|entry| entry.id.eq(args.id)) + .select_with(|entry| entry.action.metadata.key(args.key)) + .execute_single() + .wrap_err("Failed to get value")?; + context.print_data(&value) + } + Set(args) => { + let value: Json = parse_json5_stdin()?; + let instruction = + iroha::data_model::isi::SetKeyValue::trigger(args.id, args.key, value); + context.finish([instruction]) + } + Remove(args) => { + let instruction = + iroha::data_model::isi::RemoveKeyValue::trigger(args.id, args.key); + context.finish([instruction]) + } + } + } + } + } +} + +fn dump_json5_stdout(value: &T) -> Result<()> +where + T: serde::Serialize, +{ + let s = json5::to_string(value)?; + io::stdout().write_all(s.as_bytes())?; + Ok(()) +} + +fn parse_json5_stdin() -> Result +where + T: for<'a> serde::Deserialize<'a>, +{ + parse_json5(&string_from_stdin()?) +} + +fn parse_json5(s: &str) -> Result +where + T: for<'a> serde::Deserialize<'a>, +{ + Ok(json5::from_str(s)?) +} + +fn string_from_stdin() -> Result { + let mut buf = String::new(); + io::stdin().read_to_string(&mut buf)?; + Ok(buf) +} + +fn bytes_from_stdin() -> Result> { + let mut buf = Vec::new(); + io::stdin().read_to_end(&mut buf)?; + Ok(buf) +} diff --git a/crates/iroha_data_model/src/isi.rs b/crates/iroha_data_model/src/isi.rs index 75b3b341c32..4f0db0d105a 100644 --- a/crates/iroha_data_model/src/isi.rs +++ b/crates/iroha_data_model/src/isi.rs @@ -958,7 +958,6 @@ mod transparent { #[display(fmt = "LOG({level}): {msg}")] pub struct Log { /// Message log level - #[serde(flatten)] pub level: Level, #[getset(skip)] // TODO: Fix this by addressing ffi issues /// Msg to be logged diff --git a/crates/iroha_data_model/src/parameter.rs b/crates/iroha_data_model/src/parameter.rs index b90a4e7ff27..3d081679746 100644 --- a/crates/iroha_data_model/src/parameter.rs +++ b/crates/iroha_data_model/src/parameter.rs @@ -108,6 +108,7 @@ mod model { Deserialize, IntoSchema, )] + #[serde(rename_all = "snake_case")] pub enum SumeragiParameter { BlockTimeMs(u64), CommitTimeMs(u64), @@ -145,6 +146,7 @@ mod model { #[derive( Debug, Display, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Encode, Serialize, IntoSchema, )] + #[serde(rename_all = "snake_case")] pub enum BlockParameter { MaxTransactions(NonZeroU64), } @@ -179,6 +181,7 @@ mod model { #[derive( Debug, Display, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Encode, Serialize, IntoSchema, )] + #[serde(rename_all = "snake_case")] pub enum TransactionParameter { MaxInstructions(NonZeroU64), SmartContractSize(NonZeroU64), @@ -214,6 +217,7 @@ mod model { #[derive( Debug, Display, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Encode, Serialize, IntoSchema, )] + #[serde(rename_all = "snake_case")] pub enum SmartContractParameter { Fuel(NonZeroU64), Memory(NonZeroU64), @@ -298,6 +302,7 @@ mod model { IntoSchema, )] #[ffi_type(opaque)] + #[serde(rename_all = "snake_case")] pub enum Parameter { Sumeragi(SumeragiParameter), Block(BlockParameter), diff --git a/crates/iroha_executor/src/default/isi/multisig/transaction.rs b/crates/iroha_executor/src/default/isi/multisig/transaction.rs index 47236cb89bb..17b23d9aeb0 100644 --- a/crates/iroha_executor/src/default/isi/multisig/transaction.rs +++ b/crates/iroha_executor/src/default/isi/multisig/transaction.rs @@ -1,7 +1,6 @@ //! Validation and execution logic of instructions for multisig transactions use alloc::{collections::btree_set::BTreeSet, vec}; -use core::num::NonZeroU64; use iroha_smart_contract::data_model::query::error::QueryExecutionFail; @@ -189,15 +188,13 @@ fn proposal_value( .map_err(metadata_conversion_error) } -fn now_ms(executor: &V) -> NonZeroU64 { +fn now_ms(executor: &V) -> u64 { executor .context() .curr_block .creation_time() .as_millis() .try_into() - .ok() - .and_then(NonZeroU64::new) .dbg_expect("shouldn't overflow within 584942417 years") } diff --git a/crates/iroha_executor_data_model/src/isi.rs b/crates/iroha_executor_data_model/src/isi.rs index 1b48b2b500f..c0b9a736780 100644 --- a/crates/iroha_executor_data_model/src/isi.rs +++ b/crates/iroha_executor_data_model/src/isi.rs @@ -132,9 +132,9 @@ pub mod multisig { /// Proposal contents pub instructions: Vec, /// Time in milliseconds at which the proposal was made - pub proposed_at_ms: NonZeroU64, + pub proposed_at_ms: u64, /// Time in milliseconds at which the proposal will expire - pub expires_at_ms: NonZeroU64, + pub expires_at_ms: u64, /// List of approvers of the proposal so far pub approvals: BTreeSet, /// In case this proposal is some relaying approval, indicates if it has executed or not diff --git a/docs/source/references/schema.json b/docs/source/references/schema.json index a0e9b8e56ad..99c2137b5fe 100644 --- a/docs/source/references/schema.json +++ b/docs/source/references/schema.json @@ -3041,11 +3041,11 @@ }, { "name": "proposed_at_ms", - "type": "NonZero" + "type": "u64" }, { "name": "expires_at_ms", - "type": "NonZero" + "type": "u64" }, { "name": "approvals", diff --git a/hooks/pre-commit.sample b/hooks/pre-commit.sample index 93aa9f5e8f6..06361612e0b 100755 --- a/hooks/pre-commit.sample +++ b/hooks/pre-commit.sample @@ -6,15 +6,18 @@ cargo fmt --all -- --check cargo fmt --manifest-path ./wasm/Cargo.toml --all -- --check # lints cargo clippy --workspace --benches --tests --examples --all-features +cargo clippy --workspace --benches --tests --examples --all-features --manifest-path ./wasm/Cargo.toml # TODO: fails, re-enable # cargo clippy --workspace --benches --tests --examples --no-default-features # update the default genesis, assuming the transaction authority is `iroha_test_samples::SAMPLE_GENESIS_ACCOUNT_ID` cargo run --bin kagami -- genesis generate --executor executor.wasm --wasm-dir libs --genesis-public-key ed01204164BF554923ECE1FD412D241036D863A6AE430476C898248B8237D77534CFC4 > ./defaults/genesis.json # update schema cargo run --bin kagami -- schema > ./docs/source/references/schema.json +# update command-line help +cargo run --bin iroha -- markdown-help > ./crates/iroha_cli/CommandLineHelp.md # update docker compose files cargo run --bin iroha_swarm -- -p 1 -s Iroha -H -c ./defaults -i hyperledger/iroha:local -b . -o ./defaults/docker-compose.single.yml -F cargo run --bin iroha_swarm -- -p 4 -s Iroha -H -c ./defaults -i hyperledger/iroha:local -b . -o ./defaults/docker-compose.local.yml -F cargo run --bin iroha_swarm -- -p 4 -s Iroha -H -c ./defaults -i hyperledger/iroha:dev -o ./defaults/docker-compose.yml -F # stage updates -git add ./defaults/genesis.json ./docs/source/references/schema.json ./defaults/docker-compose.single.yml ./defaults/docker-compose.local.yml ./defaults/docker-compose.yml +git add ./defaults/genesis.json ./docs/source/references/schema.json ./crates/iroha_cli/CommandLineHelp.md ./defaults/docker-compose.single.yml ./defaults/docker-compose.local.yml ./defaults/docker-compose.yml diff --git a/pytests/iroha_cli_tests/src/iroha_cli/iroha_cli.py b/pytests/iroha_cli_tests/src/iroha_cli/iroha_cli.py index af1b604e887..578c0e3d579 100644 --- a/pytests/iroha_cli_tests/src/iroha_cli/iroha_cli.py +++ b/pytests/iroha_cli_tests/src/iroha_cli/iroha_cli.py @@ -296,7 +296,7 @@ def _execute_isi(self, temp_file_path): """ self._execute_pipe( ["cat", temp_file_path], - [self.BASE_PATH] + self.BASE_FLAGS + ["json", "transaction"], + [self.BASE_PATH] + self.BASE_FLAGS + ["transaction", "stdin"], ) def register_trigger(self, account): diff --git a/pytests/iroha_cli_tests/test/events/test_listen_events.py b/pytests/iroha_cli_tests/test/events/test_listen_events.py index f43b27a002a..9ade220db76 100644 --- a/pytests/iroha_cli_tests/test/events/test_listen_events.py +++ b/pytests/iroha_cli_tests/test/events/test_listen_events.py @@ -14,6 +14,6 @@ def test_stream_data_events_timeouts(GIVEN_currently_authorized_account): with allure.step( f"WHEN {GIVEN_currently_authorized_account} streams block-pipeline events with timeout " ): - iroha_cli.execute("events data --timeout 1s") + iroha_cli.execute("events state --timeout 1s") iroha_cli.should(have.error("Timeout period has expired.\n")) diff --git a/scripts/tests/consistency.sh b/scripts/tests/consistency.sh index cbbdabd04a2..adf4fb2f1d5 100755 --- a/scripts/tests/consistency.sh +++ b/scripts/tests/consistency.sh @@ -13,6 +13,11 @@ case $1 in echo 'Please re-generate schema with `cargo run --release --bin kagami -- schema > docs/source/references/schema.json`' exit 1 };; + "cli-help") + cargo run --release --bin iroha -- markdown-help | diff - crates/iroha_cli/CommandLineHelp.md || { + echo 'Please re-generate command-line help with `cargo run --bin iroha -- markdown-help > crates/iroha_cli/CommandLineHelp.md`' + exit 1 + };; "docker-compose") do_check() { cmd_base=$1 diff --git a/scripts/tests/instructions.json b/scripts/tests/multisig.instructions.json similarity index 100% rename from scripts/tests/instructions.json rename to scripts/tests/multisig.instructions.json diff --git a/scripts/tests/multisig.recursion.sh b/scripts/tests/multisig.recursion.sh index 953c855c1de..51c720ddb40 100644 --- a/scripts/tests/multisig.recursion.sh +++ b/scripts/tests/multisig.recursion.sh @@ -64,7 +64,7 @@ SIGS_012345=(${SIGNATORIES[0]} $MSA_12345) ./iroha multisig register --account $MSA_012345 --signatories ${SIGS_012345[*]} --weights 1 1 --quorum 2 # propose a multisig transaction -INSTRUCTIONS="../scripts/tests/instructions.json" +INSTRUCTIONS="../scripts/tests/multisig.instructions.json" cat $INSTRUCTIONS | ./iroha --config "client.0.toml" multisig propose --account $MSA_012345 get_list_as_signatory() { diff --git a/scripts/tests/multisig.sh b/scripts/tests/multisig.sh index 63ca0ddb811..5e1ddc0aa6d 100644 --- a/scripts/tests/multisig.sh +++ b/scripts/tests/multisig.sh @@ -46,7 +46,7 @@ TRANSACTION_TTL="1y 6M 2w 3d 12h 30m 30s 500ms" ./iroha --config "client.toml" multisig register --account $MULTISIG_ACCOUNT --signatories ${SIGNATORIES[*]} --weights ${WEIGHTS[*]} --quorum $QUORUM --transaction-ttl "$TRANSACTION_TTL" # propose a multisig transaction -INSTRUCTIONS="../scripts/tests/instructions.json" +INSTRUCTIONS="../scripts/tests/multisig.instructions.json" cat $INSTRUCTIONS | ./iroha --config "client.1.toml" multisig propose --account $MULTISIG_ACCOUNT get_list_as_signatory() { diff --git a/scripts/tests/tick.json b/scripts/tests/tick.json index 2d8e1cfac86..9e2361059de 100644 --- a/scripts/tests/tick.json +++ b/scripts/tests/tick.json @@ -1,7 +1,7 @@ [ { "Log": { - "DEBUG": null, + "level": "DEBUG", "msg": "Just ticking time" } }