From 88b0c237f50551dd770f174d55d4ba173bbfb501 Mon Sep 17 00:00:00 2001 From: Shunkichi Sato <49983831+s8sato@users.noreply.github.com> Date: Thu, 13 Feb 2025 19:07:17 +0900 Subject: [PATCH] feat(cli, 2.0.0-rc.1): add `iroha transaction get` and other important commands (#5290) * fix: un-flatten enum `Level` to serialize * feat(cli): add `iroha transaction get` and other important commands * ci: exclude from coverage `iroha_cli` `iroha_torii` covered by pytests * ci: complement #4605: tag not `iroha2:*` but `iroha:*` * docs: fix invalid links * docs: fix lints * chore: remove `scripts/tests/tick.json` * chore: update CHANGELOG.md * chore: minor version bump to `2.0.0-rc.1.1` Signed-off-by: Shunkichi Sato <49983831+s8sato@users.noreply.github.com> --- .github/workflows/iroha2-dev-nightly.yml | 2 +- .github/workflows/iroha2-dev-pr.yml | 9 +- .github/workflows/iroha2-dev.yml | 2 +- .../workflows/iroha2-no-incorrect-image.yml | 4 +- CHANGELOG.md | 6 + Cargo.lock | 116 +- Cargo.toml | 70 +- README.md | 4 +- crates/iroha_cli/Cargo.toml | 1 + crates/iroha_cli/CommandLineHelp.md | 1528 +++++++++++ crates/iroha_cli/README.md | 71 +- crates/iroha_cli/docs/multisig.md | 133 + crates/iroha_cli/samples/instructions.json | 8 + crates/iroha_cli/samples/parameter.json | 5 + crates/iroha_cli/samples/query.json | 32 + crates/iroha_cli/src/main.rs | 2367 ++++++++++------- .../src/smartcontracts/wasm/cache.rs | 11 +- crates/iroha_data_model/src/isi.rs | 1 - crates/iroha_smart_contract_utils/src/lib.rs | 2 +- hooks/pre-commit.sample | 5 +- .../src/iroha_cli/iroha_cli.py | 2 +- .../test/events/test_listen_events.py | 2 +- scripts/tests/consistency.sh | 5 + ...ctions.json => multisig.instructions.json} | 0 scripts/tests/multisig.recursion.sh | 2 +- scripts/tests/multisig.sh | 2 +- scripts/tests/tick.json | 8 - wasm/Cargo.toml | 14 +- wasm/samples/mint_rose_trigger/Cargo.toml | 2 +- 29 files changed, 3262 insertions(+), 1152 deletions(-) create mode 100644 crates/iroha_cli/CommandLineHelp.md create mode 100644 crates/iroha_cli/docs/multisig.md create mode 100644 crates/iroha_cli/samples/instructions.json create mode 100644 crates/iroha_cli/samples/parameter.json create mode 100644 crates/iroha_cli/samples/query.json rename scripts/tests/{instructions.json => multisig.instructions.json} (100%) delete mode 100644 scripts/tests/tick.json diff --git a/.github/workflows/iroha2-dev-nightly.yml b/.github/workflows/iroha2-dev-nightly.yml index 1b7b5d6237e..474e40d32bd 100644 --- a/.github/workflows/iroha2-dev-nightly.yml +++ b/.github/workflows/iroha2-dev-nightly.yml @@ -41,7 +41,7 @@ jobs: with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Build and push iroha2:dev-nightly image + - name: Build and push iroha:dev-nightly image uses: docker/build-push-action@v6 with: push: true diff --git a/.github/workflows/iroha2-dev-pr.yml b/.github/workflows/iroha2-dev-pr.yml index 61aae2aff95..4cd31db89af 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 @@ -119,7 +122,7 @@ jobs: --all-features --branch --no-report - name: Generate lcov report - run: cargo llvm-cov report --doctests --lcov --output-path lcov.info + run: cargo llvm-cov report --doctests --ignore-filename-regex 'iroha_cli|iroha_torii' --lcov --output-path lcov.info - name: Upload lcov report uses: actions/upload-artifact@v4 with: @@ -160,12 +163,12 @@ jobs: uses: docker/setup-buildx-action@v2 with: install: true - - name: Build and push iroha2:dev image + - name: Build and push iroha:dev image uses: docker/build-push-action@v6 if: always() with: push: true - tags: docker.soramitsu.co.jp/iroha2/iroha2:dev-${{ github.event.pull_request.head.sha }} + tags: docker.soramitsu.co.jp/iroha2/iroha:dev-${{ github.event.pull_request.head.sha }} labels: commit=${{ github.sha }} build-args: TAG=dev # This context specification is required diff --git a/.github/workflows/iroha2-dev.yml b/.github/workflows/iroha2-dev.yml index 3227b7794d5..265f69e4178 100644 --- a/.github/workflows/iroha2-dev.yml +++ b/.github/workflows/iroha2-dev.yml @@ -58,7 +58,7 @@ jobs: registry: docker.soramitsu.co.jp username: ${{ secrets.HARBOR_USERNAME }} password: ${{ secrets.HARBOR_TOKEN }} - - name: Build and push iroha2:dev image + - name: Build and push iroha:dev image uses: docker/build-push-action@v6 with: context: . diff --git a/.github/workflows/iroha2-no-incorrect-image.yml b/.github/workflows/iroha2-no-incorrect-image.yml index a1c2b311be4..effa50a4641 100644 --- a/.github/workflows/iroha2-no-incorrect-image.yml +++ b/.github/workflows/iroha2-no-incorrect-image.yml @@ -19,7 +19,7 @@ jobs: run: pip install -r .github/scripts/ci_test/requirements.txt --no-input - name: Check containers on iroha2 stable branch if: github.base_ref == 'stable' - run: python .github/scripts/ci_test/ci_image_scan.py --allow iroha2:stable -- docker-compose*.yml + run: python .github/scripts/ci_test/ci_image_scan.py --allow iroha:stable -- docker-compose*.yml - name: Check containers on iroha2 main branch if: github.base_ref == 'main' - run: python .github/scripts/ci_test/ci_image_scan.py --allow iroha2:dev -- docker-compose*.yml + run: python .github/scripts/ci_test/ci_image_scan.py --allow iroha:dev -- docker-compose*.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index 4174b19d1e4..3d958c1af9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ## [Unreleased] +## [2.0.0-rc.1.1] - 2025-02-12 + +### Added + +- add `iroha transaction get` and other important commands (#5289) + ## [2.0.0-rc.1.0] - 2024-12-06 ### Added diff --git a/Cargo.lock b/Cargo.lock index 65c495e40e1..cae563fadde 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" @@ -1610,7 +1619,7 @@ dependencies = [ [[package]] name = "executor_custom_data_model" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "iroha_data_model", "iroha_executor_data_model", @@ -2908,7 +2917,7 @@ checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" [[package]] name = "iroha" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "assert_matches", "assertables", @@ -2956,9 +2965,10 @@ dependencies = [ [[package]] name = "iroha_cli" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "clap", + "clap-markdown", "color-eyre", "derive_more", "erased-serde", @@ -2980,7 +2990,7 @@ dependencies = [ [[package]] name = "iroha_codec" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "clap", "colored", @@ -2997,7 +3007,7 @@ dependencies = [ [[package]] name = "iroha_config" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "assertables", "cfg-if", @@ -3025,7 +3035,7 @@ dependencies = [ [[package]] name = "iroha_config_base" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "derive_more", "drop_bomb", @@ -3044,7 +3054,7 @@ dependencies = [ [[package]] name = "iroha_config_base_derive" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "darling", "expect-test", @@ -3060,7 +3070,7 @@ dependencies = [ [[package]] name = "iroha_core" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "async-trait", "criterion", @@ -3102,7 +3112,7 @@ dependencies = [ [[package]] name = "iroha_crypto" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "aead", "amcl", @@ -3144,7 +3154,7 @@ dependencies = [ [[package]] name = "iroha_data_model" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "base64 0.22.1", "criterion", @@ -3171,7 +3181,7 @@ dependencies = [ [[package]] name = "iroha_data_model_derive" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "darling", "derive_more", @@ -3190,7 +3200,7 @@ dependencies = [ [[package]] name = "iroha_derive" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "darling", "impls", @@ -3206,7 +3216,7 @@ dependencies = [ [[package]] name = "iroha_executor" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "iroha_executor_data_model", "iroha_executor_derive", @@ -3219,7 +3229,7 @@ dependencies = [ [[package]] name = "iroha_executor_data_model" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "derive_more", "iroha_data_model", @@ -3231,7 +3241,7 @@ dependencies = [ [[package]] name = "iroha_executor_data_model_derive" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "iroha_executor_data_model", "manyhow", @@ -3242,7 +3252,7 @@ dependencies = [ [[package]] name = "iroha_executor_derive" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "darling", "iroha_macro_utils", @@ -3254,7 +3264,7 @@ dependencies = [ [[package]] name = "iroha_ffi" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "derive_more", "getset", @@ -3264,7 +3274,7 @@ dependencies = [ [[package]] name = "iroha_ffi_derive" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "darling", "getset", @@ -3281,7 +3291,7 @@ dependencies = [ [[package]] name = "iroha_futures" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "iroha_config", "iroha_futures_derive", @@ -3297,7 +3307,7 @@ dependencies = [ [[package]] name = "iroha_futures_derive" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "iroha_macro_utils", "manyhow", @@ -3308,7 +3318,7 @@ dependencies = [ [[package]] name = "iroha_genesis" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "derive_more", "eyre", @@ -3325,7 +3335,7 @@ dependencies = [ [[package]] name = "iroha_kagami" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "clap", "color-eyre", @@ -3344,7 +3354,7 @@ dependencies = [ [[package]] name = "iroha_logger" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "color-eyre", "console-subscriber", @@ -3363,14 +3373,14 @@ dependencies = [ [[package]] name = "iroha_macro" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "iroha_derive", ] [[package]] name = "iroha_macro_utils" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "darling", "drop_bomb", @@ -3382,7 +3392,7 @@ dependencies = [ [[package]] name = "iroha_numeric" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "derive_more", "displaydoc", @@ -3398,7 +3408,7 @@ dependencies = [ [[package]] name = "iroha_p2p" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "async-trait", "bytes", @@ -3420,7 +3430,7 @@ dependencies = [ [[package]] name = "iroha_primitives" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "derive_more", "displaydoc", @@ -3442,7 +3452,7 @@ dependencies = [ [[package]] name = "iroha_primitives_derive" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "iroha_numeric", "iroha_primitives", @@ -3454,7 +3464,7 @@ dependencies = [ [[package]] name = "iroha_schema" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "impls", "iroha_schema_derive", @@ -3465,7 +3475,7 @@ dependencies = [ [[package]] name = "iroha_schema_derive" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "darling", "iroha_macro_utils", @@ -3479,7 +3489,7 @@ dependencies = [ [[package]] name = "iroha_schema_gen" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "iroha_crypto", "iroha_data_model", @@ -3491,7 +3501,7 @@ dependencies = [ [[package]] name = "iroha_smart_contract" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "derive_more", "displaydoc", @@ -3506,7 +3516,7 @@ dependencies = [ [[package]] name = "iroha_smart_contract_derive" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "iroha_macro_utils", "manyhow", @@ -3517,7 +3527,7 @@ dependencies = [ [[package]] name = "iroha_smart_contract_utils" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "cfg-if", "getrandom", @@ -3528,7 +3538,7 @@ dependencies = [ [[package]] name = "iroha_swarm" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "clap", "color-eyre", @@ -3551,7 +3561,7 @@ dependencies = [ [[package]] name = "iroha_telemetry" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "async-trait", "chrono", @@ -3577,7 +3587,7 @@ dependencies = [ [[package]] name = "iroha_telemetry_derive" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "iroha_macro_utils", "manyhow", @@ -3589,7 +3599,7 @@ dependencies = [ [[package]] name = "iroha_test_network" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "backoff", "color-eyre", @@ -3622,7 +3632,7 @@ dependencies = [ [[package]] name = "iroha_test_samples" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "iroha_crypto", "iroha_data_model", @@ -3631,7 +3641,7 @@ dependencies = [ [[package]] name = "iroha_torii" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "async-trait", "axum 0.7.5", @@ -3666,14 +3676,14 @@ dependencies = [ [[package]] name = "iroha_torii_const" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "iroha_primitives", ] [[package]] name = "iroha_trigger" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "iroha_smart_contract", "iroha_smart_contract_utils", @@ -3682,7 +3692,7 @@ dependencies = [ [[package]] name = "iroha_trigger_derive" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "darling", "iroha_macro_utils", @@ -3694,7 +3704,7 @@ dependencies = [ [[package]] name = "iroha_version" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "iroha_data_model", "iroha_logger", @@ -3708,7 +3718,7 @@ dependencies = [ [[package]] name = "iroha_version_derive" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "darling", "iroha_macro", @@ -3725,7 +3735,7 @@ dependencies = [ [[package]] name = "iroha_wasm_builder" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "clap", "color-eyre", @@ -3740,7 +3750,7 @@ dependencies = [ [[package]] name = "iroha_wasm_codec" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "iroha_wasm_codec_derive", "parity-scale-codec", @@ -3750,7 +3760,7 @@ dependencies = [ [[package]] name = "iroha_wasm_codec_derive" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "iroha_macro_utils", "manyhow", @@ -3761,7 +3771,7 @@ dependencies = [ [[package]] name = "iroha_wasm_test_runner" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "anyhow", "wasmtime", @@ -3769,7 +3779,7 @@ dependencies = [ [[package]] name = "irohad" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "assertables", "clap", @@ -3921,7 +3931,7 @@ dependencies = [ [[package]] name = "kura_inspector" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "clap", "iroha_core", @@ -4170,7 +4180,7 @@ dependencies = [ [[package]] name = "mint_rose_trigger_data_model" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" dependencies = [ "iroha_data_model", "serde", diff --git a/Cargo.toml b/Cargo.toml index 8f503b36eba..3238f141e60 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace.package] edition = "2021" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" # TODO: teams are being deprecated update the authors URL authors = ["Iroha 2 team "] @@ -14,40 +14,40 @@ keywords = ["blockchain", "crypto", "iroha", "ledger"] categories = ["cryptography::cryptocurrencies"] [workspace.dependencies] -iroha_core = { version = "=2.0.0-rc.1.0 ", path = "crates/iroha_core" } - -iroha_torii = { version = "=2.0.0-rc.1.0", path = "crates/iroha_torii" } -iroha_torii_const = { version = "=2.0.0-rc.1.0", path = "crates/iroha_torii_const" } - -iroha = { version = "=2.0.0-rc.1.0", path = "crates/iroha" } - -iroha_macro_utils = { version = "=2.0.0-rc.1.0", path = "crates/iroha_macro_utils" } -iroha_telemetry = { version = "=2.0.0-rc.1.0", path = "crates/iroha_telemetry" } -iroha_p2p = { version = "=2.0.0-rc.1.0", path = "crates/iroha_p2p" } -iroha_primitives = { version = "=2.0.0-rc.1.0", path = "crates/iroha_primitives", default-features = false } -iroha_config = { version = "=2.0.0-rc.1.0", path = "crates/iroha_config" } -iroha_config_base = { version = "=2.0.0-rc.1.0", path = "crates/iroha_config_base" } -iroha_schema_gen = { version = "=2.0.0-rc.1.0", path = "crates/iroha_schema_gen" } -iroha_schema = { version = "=2.0.0-rc.1.0", path = "crates/iroha_schema", default-features = false } -iroha_logger = { version = "=2.0.0-rc.1.0", path = "crates/iroha_logger" } -iroha_crypto = { version = "=2.0.0-rc.1.0", path = "crates/iroha_crypto", default-features = false } -iroha_macro = { version = "=2.0.0-rc.1.0", path = "crates/iroha_macro", default-features = false } -iroha_futures = { version = "=2.0.0-rc.1.0", path = "crates/iroha_futures" } -iroha_genesis = { version = "=2.0.0-rc.1.0", path = "crates/iroha_genesis" } -iroha_ffi = { version = "=2.0.0-rc.1.0", path = "crates/iroha_ffi" } -iroha_version = { version = "=2.0.0-rc.1.0", path = "crates/iroha_version", default-features = false } -iroha_wasm_codec = { version = "=2.0.0-rc.1.0", path = "crates/iroha_wasm_codec" } -iroha_wasm_builder = { version = "=2.0.0-rc.1.0", path = "crates/iroha_wasm_builder" } - -iroha_smart_contract = { version = "=2.0.0-rc.1.0", path = "crates/iroha_smart_contract" } -iroha_smart_contract_utils = { version = "=2.0.0-rc.1.0", path = "crates/iroha_smart_contract_utils" } -iroha_executor = { version = "=2.0.0-rc.1.0", path = "crates/iroha_executor" } - -iroha_data_model = { version = "=2.0.0-rc.1.0", path = "crates/iroha_data_model", default-features = false } -iroha_executor_data_model = { version = "=2.0.0-rc.1.0", path = "crates/iroha_executor_data_model" } - -iroha_test_network = { version = "=2.0.0-rc.1.0", path = "crates/iroha_test_network" } -iroha_test_samples = { version = "=2.0.0-rc.1.0", path = "crates/iroha_test_samples" } +iroha_core = { version = "=2.0.0-rc.1.1 ", path = "crates/iroha_core" } + +iroha_torii = { version = "=2.0.0-rc.1.1", path = "crates/iroha_torii" } +iroha_torii_const = { version = "=2.0.0-rc.1.1", path = "crates/iroha_torii_const" } + +iroha = { version = "=2.0.0-rc.1.1", path = "crates/iroha" } + +iroha_macro_utils = { version = "=2.0.0-rc.1.1", path = "crates/iroha_macro_utils" } +iroha_telemetry = { version = "=2.0.0-rc.1.1", path = "crates/iroha_telemetry" } +iroha_p2p = { version = "=2.0.0-rc.1.1", path = "crates/iroha_p2p" } +iroha_primitives = { version = "=2.0.0-rc.1.1", path = "crates/iroha_primitives", default-features = false } +iroha_config = { version = "=2.0.0-rc.1.1", path = "crates/iroha_config" } +iroha_config_base = { version = "=2.0.0-rc.1.1", path = "crates/iroha_config_base" } +iroha_schema_gen = { version = "=2.0.0-rc.1.1", path = "crates/iroha_schema_gen" } +iroha_schema = { version = "=2.0.0-rc.1.1", path = "crates/iroha_schema", default-features = false } +iroha_logger = { version = "=2.0.0-rc.1.1", path = "crates/iroha_logger" } +iroha_crypto = { version = "=2.0.0-rc.1.1", path = "crates/iroha_crypto", default-features = false } +iroha_macro = { version = "=2.0.0-rc.1.1", path = "crates/iroha_macro", default-features = false } +iroha_futures = { version = "=2.0.0-rc.1.1", path = "crates/iroha_futures" } +iroha_genesis = { version = "=2.0.0-rc.1.1", path = "crates/iroha_genesis" } +iroha_ffi = { version = "=2.0.0-rc.1.1", path = "crates/iroha_ffi" } +iroha_version = { version = "=2.0.0-rc.1.1", path = "crates/iroha_version", default-features = false } +iroha_wasm_codec = { version = "=2.0.0-rc.1.1", path = "crates/iroha_wasm_codec" } +iroha_wasm_builder = { version = "=2.0.0-rc.1.1", path = "crates/iroha_wasm_builder" } + +iroha_smart_contract = { version = "=2.0.0-rc.1.1", path = "crates/iroha_smart_contract" } +iroha_smart_contract_utils = { version = "=2.0.0-rc.1.1", path = "crates/iroha_smart_contract_utils" } +iroha_executor = { version = "=2.0.0-rc.1.1", path = "crates/iroha_executor" } + +iroha_data_model = { version = "=2.0.0-rc.1.1", path = "crates/iroha_data_model", default-features = false } +iroha_executor_data_model = { version = "=2.0.0-rc.1.1", path = "crates/iroha_executor_data_model" } + +iroha_test_network = { version = "=2.0.0-rc.1.1", path = "crates/iroha_test_network" } +iroha_test_samples = { version = "=2.0.0-rc.1.1", path = "crates/iroha_test_samples" } proc-macro2 = "1.0.86" syn = { version = "2.0.72", default-features = false } diff --git a/README.md b/README.md index 6223a42c4e4..1cda3a67ea0 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ Prerequisites: - (Optional) Build the latest Iroha image: ```bash - docker build . -t hyperledger/iroha2:dev + docker build . -t hyperledger/iroha:dev ``` If you skip this step, the Iroha container will be built using the latest available image. @@ -181,7 +181,7 @@ Iroha stores blocks and snapshots in the `storage` directory, which is created a Multiple instances of Iroha peer and client binaries can be run on the same physical machine and in the same working directory. However, we recommend to give each instance a clean new working directory. -The provided `docker-compose` file showcases a minimum viable network and the general methods of using the `hyperledger/iroha2:dev` docker image for deploying a network of peers. +The provided `docker-compose` file showcases a minimum viable network and the general methods of using the `hyperledger/iroha:dev` docker image for deploying a network of peers. ## Further Reading 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..780acba1754 --- /dev/null +++ b/crates/iroha_cli/CommandLineHelp.md @@ -0,0 +1,1528 @@ +# 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 mint`↴](#iroha-trigger-mint) +* [`iroha trigger burn`↴](#iroha-trigger-burn) +* [`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 data-model`↴](#iroha-executor-data-model) +* [`iroha executor upgrade`↴](#iroha-executor-upgrade) +* [`iroha markdown-help`↴](#iroha-markdown-help) + +## `iroha` + +Iroha Client CLI provides a simple way to interact with the Iroha Web API + +**Usage:** `iroha [OPTIONS] ` + +###### **Subcommands:** + +* `domain` — Read and write domains +* `account` — Read and write accounts +* `asset` — Read and write assets +* `peer` — Read and write peers +* `events` — Subscribe to events: state changes, transaction/block/trigger progress +* `blocks` — Subscribe to blocks +* `multisig` — Read and write multi-signature accounts and transactions +* `query` — Read various data +* `transaction` — Read transactions and write various data +* `role` — Read and write roles +* `parameter` — Read and write system parameters +* `trigger` — Read and write triggers +* `executor` — Read and write the executor +* `markdown-help` — Output CLI documentation in Markdown format + +###### **Options:** + +* `-c`, `--config ` — Path to the configuration file + + Default value: `client.toml` +* `-v`, `--verbose` — Print configuration details to stderr +* `-m`, `--metadata ` — Path to a JSON5 file for attaching transaction metadata (optional) +* `-i`, `--input` — Reads instructions from stdin and appends new ones. + + Example usage: + + `echo "[]" | iroha -io domain register --id "domain" | iroha -i asset definition register --id "asset#domain" -t Numeric` +* `-o`, `--output` — Outputs instructions to stdout without submitting them. + + Example usage: + + `iroha -o domain register --id "domain" | iroha -io asset definition register --id "asset#domain" -t Numeric | iroha transaction stdin` + + + +## `iroha domain` + +Read and write domains + +**Usage:** `iroha domain ` + +###### **Subcommands:** + +* `list` — List domains +* `get` — Retrieve details of a specific domain +* `register` — Register a domain +* `unregister` — Unregister a domain +* `transfer` — Transfer ownership of a domain +* `meta` — Read and write metadata + + + +## `iroha domain list` + +List domains + +**Usage:** `iroha domain list ` + +###### **Subcommands:** + +* `all` — List all IDs, or full entries when `--verbose` is specified +* `filter` — Filter by a given predicate + + + +## `iroha domain list all` + +List all IDs, or full entries when `--verbose` is specified + +**Usage:** `iroha domain list all [OPTIONS]` + +###### **Options:** + +* `-v`, `--verbose` — Display detailed entry information instead of just IDs + + + +## `iroha domain list filter` + +Filter by a given predicate + +**Usage:** `iroha domain list filter ` + +###### **Arguments:** + +* `` — Filtering condition specified as a JSON5 string + + + +## `iroha domain get` + +Retrieve details of a specific domain + +**Usage:** `iroha domain get --id ` + +###### **Options:** + +* `-i`, `--id ` — Domain name + + + +## `iroha domain register` + +Register a domain + +**Usage:** `iroha domain register --id ` + +###### **Options:** + +* `-i`, `--id ` — Domain name + + + +## `iroha domain unregister` + +Unregister a domain + +**Usage:** `iroha domain unregister --id ` + +###### **Options:** + +* `-i`, `--id ` — Domain name + + + +## `iroha domain transfer` + +Transfer ownership of a domain + +**Usage:** `iroha domain transfer --id --from --to ` + +###### **Options:** + +* `-i`, `--id ` — Domain name +* `-f`, `--from ` — Source account, in the format "multihash@domain" +* `-t`, `--to ` — Destination account, in the format "multihash@domain" + + + +## `iroha domain meta` + +Read and write metadata + +**Usage:** `iroha domain meta ` + +###### **Subcommands:** + +* `get` — Retrieve a value from the key-value store +* `set` — Create or update an entry in the key-value store using JSON5 input from stdin +* `remove` — Delete an entry from the key-value store + + + +## `iroha domain meta get` + +Retrieve a value from the 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 the key-value store using JSON5 input from stdin + +**Usage:** `iroha domain meta set --id --key ` + +###### **Options:** + +* `-i`, `--id ` +* `-k`, `--key ` + + + +## `iroha domain meta remove` + +Delete an entry from the key-value store + +**Usage:** `iroha domain meta remove --id --key ` + +###### **Options:** + +* `-i`, `--id ` +* `-k`, `--key ` + + + +## `iroha account` + +Read and write accounts + +**Usage:** `iroha account ` + +###### **Subcommands:** + +* `role` — Read and write account roles +* `permission` — Read and write account permissions +* `list` — List accounts +* `get` — Retrieve details of a specific account +* `register` — Register an account +* `unregister` — Unregister an account +* `meta` — Read and write metadata + + + +## `iroha account role` + +Read and write account roles + +**Usage:** `iroha account role ` + +###### **Subcommands:** + +* `list` — List account role IDs +* `grant` — Grant a role to an account +* `revoke` — Revoke a role from an account + + + +## `iroha account role list` + +List account role IDs + +**Usage:** `iroha account role list --id ` + +###### **Options:** + +* `-i`, `--id ` — Account in the format "multihash@domain" + + + +## `iroha account role grant` + +Grant a role to an account + +**Usage:** `iroha account role grant --id --role ` + +###### **Options:** + +* `-i`, `--id ` — Account in the format "multihash@domain" +* `-r`, `--role ` — Role name + + + +## `iroha account role revoke` + +Revoke a role from an account + +**Usage:** `iroha account role revoke --id --role ` + +###### **Options:** + +* `-i`, `--id ` — Account in the format "multihash@domain" +* `-r`, `--role ` — Role name + + + +## `iroha account permission` + +Read and write account permissions + +**Usage:** `iroha account permission ` + +###### **Subcommands:** + +* `list` — List account permissions +* `grant` — Grant an account permission using JSON5 input from stdin +* `revoke` — Revoke an account permission using JSON5 input from stdin + + + +## `iroha account permission list` + +List account permissions + +**Usage:** `iroha account permission list --id ` + +###### **Options:** + +* `-i`, `--id ` — Account in the format "multihash@domain" + + + +## `iroha account permission grant` + +Grant an account permission using JSON5 input from stdin + +**Usage:** `iroha account permission grant --id ` + +###### **Options:** + +* `-i`, `--id ` — Account in the format "multihash@domain" + + + +## `iroha account permission revoke` + +Revoke an account permission using JSON5 input from stdin + +**Usage:** `iroha account permission revoke --id ` + +###### **Options:** + +* `-i`, `--id ` — Account in the format "multihash@domain" + + + +## `iroha account list` + +List accounts + +**Usage:** `iroha account list ` + +###### **Subcommands:** + +* `all` — List all IDs, or full entries when `--verbose` is specified +* `filter` — Filter by a given predicate + + + +## `iroha account list all` + +List all IDs, or full entries when `--verbose` is specified + +**Usage:** `iroha account list all [OPTIONS]` + +###### **Options:** + +* `-v`, `--verbose` — Display detailed entry information instead of just IDs + + + +## `iroha account list filter` + +Filter by a given predicate + +**Usage:** `iroha account list filter ` + +###### **Arguments:** + +* `` — Filtering condition specified as a JSON5 string + + + +## `iroha account get` + +Retrieve details of a specific account + +**Usage:** `iroha account get --id ` + +###### **Options:** + +* `-i`, `--id ` — Account in the format "multihash@domain" + + + +## `iroha account register` + +Register an account + +**Usage:** `iroha account register --id ` + +###### **Options:** + +* `-i`, `--id ` — Account in the format "multihash@domain" + + + +## `iroha account unregister` + +Unregister an account + +**Usage:** `iroha account unregister --id ` + +###### **Options:** + +* `-i`, `--id ` — Account in the format "multihash@domain" + + + +## `iroha account meta` + +Read and write metadata + +**Usage:** `iroha account meta ` + +###### **Subcommands:** + +* `get` — Retrieve a value from the key-value store +* `set` — Create or update an entry in the key-value store using JSON5 input from stdin +* `remove` — Delete an entry from the key-value store + + + +## `iroha account meta get` + +Retrieve a value from the 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 the key-value store using JSON5 input from stdin + +**Usage:** `iroha account meta set --id --key ` + +###### **Options:** + +* `-i`, `--id ` +* `-k`, `--key ` + + + +## `iroha account meta remove` + +Delete an entry from the key-value store + +**Usage:** `iroha account meta remove --id --key ` + +###### **Options:** + +* `-i`, `--id ` +* `-k`, `--key ` + + + +## `iroha asset` + +Read and write assets + +**Usage:** `iroha asset ` + +###### **Subcommands:** + +* `definition` — Read and write asset definitions +* `get` — Retrieve details of a specific asset +* `list` — List assets +* `mint` — Increase the quantity of an asset +* `burn` — Decrease the quantity of an asset +* `transfer` — Transfer an asset between accounts +* `transferkvs` — Transfer a key-value store between accounts +* `getkv` — Retrieve a value from the key-value store +* `setkv` — Create or update a key-value entry using JSON5 input from stdin +* `removekv` — Delete an entry from the key-value store + + + +## `iroha asset definition` + +Read and write asset definitions + +**Usage:** `iroha asset definition ` + +###### **Subcommands:** + +* `list` — List asset definitions +* `get` — Retrieve details of a specific asset definition +* `register` — Register an asset definition +* `unregister` — Unregister an asset definition +* `transfer` — Transfer ownership of an asset definition +* `meta` — Read and write metadata + + + +## `iroha asset definition list` + +List asset definitions + +**Usage:** `iroha asset definition list ` + +###### **Subcommands:** + +* `all` — List all IDs, or full entries when `--verbose` is specified +* `filter` — Filter by a given predicate + + + +## `iroha asset definition list all` + +List all IDs, or full entries when `--verbose` is specified + +**Usage:** `iroha asset definition list all [OPTIONS]` + +###### **Options:** + +* `-v`, `--verbose` — Display detailed entry information instead of just IDs + + + +## `iroha asset definition list filter` + +Filter by a given predicate + +**Usage:** `iroha asset definition list filter ` + +###### **Arguments:** + +* `` — Filtering condition specified as a JSON5 string + + + +## `iroha asset definition get` + +Retrieve details of a specific asset definition + +**Usage:** `iroha asset definition get --id ` + +###### **Options:** + +* `-i`, `--id ` — Asset definition in the format "asset#domain" + + + +## `iroha asset definition register` + +Register an asset definition + +**Usage:** `iroha asset definition register [OPTIONS] --id --type ` + +###### **Options:** + +* `-i`, `--id ` — Asset definition in the format "asset#domain" +* `-m`, `--mint-once` — Disables minting after the first instance +* `-t`, `--type ` — Data type stored in the asset + + + +## `iroha asset definition unregister` + +Unregister an asset definition + +**Usage:** `iroha asset definition unregister --id ` + +###### **Options:** + +* `-i`, `--id ` — Asset definition in the format "asset#domain" + + + +## `iroha asset definition transfer` + +Transfer ownership of an asset definition + +**Usage:** `iroha asset definition transfer --id --from --to ` + +###### **Options:** + +* `-i`, `--id ` — Asset definition in the format "asset#domain" +* `-f`, `--from ` — Source account, in the format "multihash@domain" +* `-t`, `--to ` — Destination account, in the format "multihash@domain" + + + +## `iroha asset definition meta` + +Read and write metadata + +**Usage:** `iroha asset definition meta ` + +###### **Subcommands:** + +* `get` — Retrieve a value from the key-value store +* `set` — Create or update an entry in the key-value store using JSON5 input from stdin +* `remove` — Delete an entry from the key-value store + + + +## `iroha asset definition meta get` + +Retrieve a value from the 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 the key-value store using JSON5 input from stdin + +**Usage:** `iroha asset definition meta set --id --key ` + +###### **Options:** + +* `-i`, `--id ` +* `-k`, `--key ` + + + +## `iroha asset definition meta remove` + +Delete an entry from the key-value store + +**Usage:** `iroha asset definition meta remove --id --key ` + +###### **Options:** + +* `-i`, `--id ` +* `-k`, `--key ` + + + +## `iroha asset get` + +Retrieve details of a specific asset + +**Usage:** `iroha asset get --id ` + +###### **Options:** + +* `-i`, `--id ` — Asset in the format "asset##account@domain" or "asset#another_domain#account@domain" + + + +## `iroha asset list` + +List assets + +**Usage:** `iroha asset list ` + +###### **Subcommands:** + +* `all` — List all IDs, or full entries when `--verbose` is specified +* `filter` — Filter by a given predicate + + + +## `iroha asset list all` + +List all IDs, or full entries when `--verbose` is specified + +**Usage:** `iroha asset list all [OPTIONS]` + +###### **Options:** + +* `-v`, `--verbose` — Display detailed entry information instead of just IDs + + + +## `iroha asset list filter` + +Filter by a given predicate + +**Usage:** `iroha asset list filter ` + +###### **Arguments:** + +* `` — Filtering condition specified as a JSON5 string + + + +## `iroha asset mint` + +Increase the quantity of an asset + +**Usage:** `iroha asset mint --id --quantity ` + +###### **Options:** + +* `-i`, `--id ` — Asset in the format "asset##account@domain" or "asset#another_domain#account@domain" +* `-q`, `--quantity ` — Amount of change (integer or decimal) + + + +## `iroha asset burn` + +Decrease the quantity of an asset + +**Usage:** `iroha asset burn --id --quantity ` + +###### **Options:** + +* `-i`, `--id ` — Asset in the format "asset##account@domain" or "asset#another_domain#account@domain" +* `-q`, `--quantity ` — Amount of change (integer or decimal) + + + +## `iroha asset transfer` + +Transfer an asset between accounts + +**Usage:** `iroha asset transfer --id --to --quantity ` + +###### **Options:** + +* `-i`, `--id ` — Asset in the format "asset##account@domain" or "asset#another_domain#account@domain" +* `-t`, `--to ` — Destination account, in the format "multihash@domain" +* `-q`, `--quantity ` — Transfer amount (integer or decimal) + + + +## `iroha asset transferkvs` + +Transfer a key-value store between accounts + +**Usage:** `iroha asset transferkvs --id --to ` + +###### **Options:** + +* `-i`, `--id ` — Asset in the format "asset##account@domain" or "asset#another_domain#account@domain" +* `-t`, `--to ` — Destination account, in the format "multihash@domain" + + + +## `iroha asset getkv` + +Retrieve a value from the key-value store + +**Usage:** `iroha asset getkv --id --key ` + +###### **Options:** + +* `-i`, `--id ` — Asset in the format "asset##account@domain" or "asset#another_domain#account@domain" +* `-k`, `--key ` — Key for retrieving the corresponding value + + + +## `iroha asset setkv` + +Create or update a key-value entry using JSON5 input from stdin + +**Usage:** `iroha asset setkv --id --key ` + +###### **Options:** + +* `-i`, `--id ` — Asset in the format "asset##account@domain" or "asset#another_domain#account@domain" +* `-k`, `--key ` — Key for retrieving the corresponding value + + + +## `iroha asset removekv` + +Delete an entry from the key-value store + +**Usage:** `iroha asset removekv --id --key ` + +###### **Options:** + +* `-i`, `--id ` — Asset in the format "asset##account@domain" or "asset#another_domain#account@domain" +* `-k`, `--key ` — Key for retrieving the corresponding value + + + +## `iroha peer` + +Read and write peers + +**Usage:** `iroha peer ` + +###### **Subcommands:** + +* `list` — List registered peers expected to connect with each other +* `register` — Register a peer +* `unregister` — Unregister a 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 a peer + +**Usage:** `iroha peer register --key ` + +###### **Options:** + +* `-k`, `--key ` — Peer's public key in multihash format + + + +## `iroha peer unregister` + +Unregister a peer + +**Usage:** `iroha peer unregister --key ` + +###### **Options:** + +* `-k`, `--key ` — Peer's public key in multihash format + + + +## `iroha events` + +Subscribe to events: state changes, transaction/block/trigger progress + +**Usage:** `iroha events [OPTIONS] ` + +###### **Subcommands:** + +* `state` — Notify when the world state undergoes certain changes +* `transaction` — Notify when a transaction reaches specific stages +* `block` — Notify when a block reaches specific stages +* `trigger-execute` — Notify when a trigger execution is ordered +* `trigger-complete` — Notify when a trigger execution is completed + +###### **Options:** + +* `-t`, `--timeout ` — Duration to listen for events. Example: "1y 6M 2w 3d 12h 30m 30s" + + + +## `iroha events state` + +Notify when the world state undergoes certain changes + +**Usage:** `iroha events state` + + + +## `iroha events transaction` + +Notify when a transaction reaches specific stages + +**Usage:** `iroha events transaction` + + + +## `iroha events block` + +Notify when a block reaches specific stages + +**Usage:** `iroha events block` + + + +## `iroha events trigger-execute` + +Notify when a trigger execution is ordered + +**Usage:** `iroha events trigger-execute` + + + +## `iroha events trigger-complete` + +Notify when a trigger execution is completed + +**Usage:** `iroha events trigger-complete` + + + +## `iroha blocks` + +Subscribe to blocks + +**Usage:** `iroha blocks [OPTIONS] ` + +###### **Arguments:** + +* `` — Block height from which to start streaming blocks + +###### **Options:** + +* `-t`, `--timeout ` — Duration to listen for events. Example: "1y 6M 2w 3d 12h 30m 30s" + + + +## `iroha multisig` + +Read and write multi-signature accounts and transactions. + +See the [usage guide](./docs/multisig.md) for details + +**Usage:** `iroha multisig ` + +###### **Subcommands:** + +* `list` — List pending multisig transactions relevant to you +* `register` — Register a multisig account +* `propose` — Propose a multisig transaction using JSON5 input from 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 ` — List of signatories for the multisig account +* `-w`, `--weights ` — Relative weights of signatories' responsibilities +* `-q`, `--quorum ` — Threshold of total weight required for authentication +* `-t`, `--transaction-ttl ` — Time-to-live for multisig transactions. Example: "1y 6M 2w 3d 12h 30m 30s" + + Default value: `1h` + + + +## `iroha multisig propose` + +Propose a multisig transaction using JSON5 input from stdin + +**Usage:** `iroha multisig propose [OPTIONS] --account ` + +###### **Options:** + +* `-a`, `--account ` — Multisig authority managing the proposed transaction +* `-t`, `--transaction-ttl ` — Overrides the default time-to-live for this transaction. Example: "1y 6M 2w 3d 12h 30m 30s" + + + +## `iroha multisig approve` + +Approve a multisig transaction + +**Usage:** `iroha multisig approve --account --instructions-hash ` + +###### **Options:** + +* `-a`, `--account ` — Multisig authority of the transaction +* `-i`, `--instructions-hash ` — Hash of the instructions to approve + + + +## `iroha query` + +Read various data + +**Usage:** `iroha query ` + +###### **Subcommands:** + +* `stdin` — Query using JSON5 input from stdin + + + +## `iroha query stdin` + +Query using JSON5 input from stdin + +**Usage:** `iroha query stdin` + + + +## `iroha transaction` + +Read transactions and write various data + +**Usage:** `iroha transaction ` + +###### **Subcommands:** + +* `get` — Retrieve details of a specific transaction +* `ping` — Send an empty transaction that logs a message +* `wasm` — Send a transaction using Wasm input +* `stdin` — Send a transaction using JSON5 input from stdin + + + +## `iroha transaction get` + +Retrieve details of a specific transaction + +**Usage:** `iroha transaction get --hash ` + +###### **Options:** + +* `-H`, `--hash ` — Hash of the transaction to retrieve + + + +## `iroha transaction ping` + +Send an empty transaction that logs a message + +**Usage:** `iroha transaction ping [OPTIONS] --msg ` + +###### **Options:** + +* `-l`, `--log-level ` — Log levels: TRACE, DEBUG, INFO, WARN, ERROR (in increasing order of visibility) + + Default value: `INFO` +* `-m`, `--msg ` — Log message + + + +## `iroha transaction wasm` + +Send a transaction using Wasm input + +**Usage:** `iroha transaction wasm [OPTIONS]` + +###### **Options:** + +* `-p`, `--path ` — Path to the Wasm file. If omitted, reads from stdin + + + +## `iroha transaction stdin` + +Send a transaction using JSON5 input from stdin + +**Usage:** `iroha transaction stdin` + + + +## `iroha role` + +Read and write roles + +**Usage:** `iroha role ` + +###### **Subcommands:** + +* `permission` — Read and write role permissions +* `list` — List role IDs +* `register` — Register a role and grant it to the registrant +* `unregister` — Unregister a role + + + +## `iroha role permission` + +Read and write role permissions + +**Usage:** `iroha role permission ` + +###### **Subcommands:** + +* `list` — List role permissions +* `grant` — Grant role permission using JSON5 input from stdin +* `revoke` — Revoke role permission using JSON5 input from stdin + + + +## `iroha role permission list` + +List role permissions + +**Usage:** `iroha role permission list --id ` + +###### **Options:** + +* `-i`, `--id ` — Role name + + + +## `iroha role permission grant` + +Grant role permission using JSON5 input from stdin + +**Usage:** `iroha role permission grant --id ` + +###### **Options:** + +* `-i`, `--id ` — Role name + + + +## `iroha role permission revoke` + +Revoke role permission using JSON5 input from stdin + +**Usage:** `iroha role permission revoke --id ` + +###### **Options:** + +* `-i`, `--id ` — Role name + + + +## `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 a role and grant it to the registrant + +**Usage:** `iroha role register --id ` + +###### **Options:** + +* `-i`, `--id ` — Role name + + + +## `iroha role unregister` + +Unregister a role + +**Usage:** `iroha role unregister --id ` + +###### **Options:** + +* `-i`, `--id ` — Role name + + + +## `iroha parameter` + +Read and write system parameters + +**Usage:** `iroha parameter ` + +###### **Subcommands:** + +* `list` — List system parameters +* `set` — Set a system parameter using JSON5 input from stdin + + + +## `iroha parameter list` + +List system parameters + +**Usage:** `iroha parameter list ` + +###### **Subcommands:** + +* `all` — List all system parameters + + + +## `iroha parameter list all` + +List all system parameters + +**Usage:** `iroha parameter list all` + + + +## `iroha parameter set` + +Set a system parameter using JSON5 input from stdin + +**Usage:** `iroha parameter set` + + + +## `iroha trigger` + +Read and write triggers + +**Usage:** `iroha trigger ` + +###### **Subcommands:** + +* `list` — List trigger IDs +* `get` — Retrieve details of a specific trigger +* `register` — TODO: Register a trigger +* `unregister` — Unregister a trigger +* `mint` — Increase the number of trigger executions +* `burn` — Decrease the number of trigger executions +* `meta` — Read and 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` + +Retrieve details of a specific trigger + +**Usage:** `iroha trigger get --id ` + +###### **Options:** + +* `-i`, `--id ` — Trigger name + + + +## `iroha trigger register` + +TODO: Register a trigger + +**Usage:** `iroha trigger register` + + + +## `iroha trigger unregister` + +Unregister a trigger + +**Usage:** `iroha trigger unregister --id ` + +###### **Options:** + +* `-i`, `--id ` — Trigger name + + + +## `iroha trigger mint` + +Increase the number of trigger executions + +**Usage:** `iroha trigger mint --id --repetitions ` + +###### **Options:** + +* `-i`, `--id ` — Trigger name +* `-r`, `--repetitions ` — Amount of change (integer) + + + +## `iroha trigger burn` + +Decrease the number of trigger executions + +**Usage:** `iroha trigger burn --id --repetitions ` + +###### **Options:** + +* `-i`, `--id ` — Trigger name +* `-r`, `--repetitions ` — Amount of change (integer) + + + +## `iroha trigger meta` + +Read and write metadata + +**Usage:** `iroha trigger meta ` + +###### **Subcommands:** + +* `get` — Retrieve a value from the key-value store +* `set` — Create or update an entry in the key-value store using JSON5 input from stdin +* `remove` — Delete an entry from the key-value store + + + +## `iroha trigger meta get` + +Retrieve a value from the 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 the key-value store using JSON5 input from stdin + +**Usage:** `iroha trigger meta set --id --key ` + +###### **Options:** + +* `-i`, `--id ` +* `-k`, `--key ` + + + +## `iroha trigger meta remove` + +Delete an entry from the key-value store + +**Usage:** `iroha trigger meta remove --id --key ` + +###### **Options:** + +* `-i`, `--id ` +* `-k`, `--key ` + + + +## `iroha executor` + +Read and write the executor + +**Usage:** `iroha executor ` + +###### **Subcommands:** + +* `data-model` — Retrieve the executor data model +* `upgrade` — Upgrade the executor + + + +## `iroha executor data-model` + +Retrieve the executor data model + +**Usage:** `iroha executor data-model` + + + +## `iroha executor upgrade` + +Upgrade the executor + +**Usage:** `iroha executor upgrade --path ` + +###### **Options:** + +* `-p`, `--path ` — Path to the compiled Wasm file + + + +## `iroha markdown-help` + +Output CLI documentation in Markdown format + +**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..67b57c6df72 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,22 +58,22 @@ 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. -You can also filter based on either account, asset or domain id by using the filtering API provided by the Iroha client CLI. Generally, filtering follows the `./iroha ENTITY list filter PREDICATE` pattern, where ENTITY is asset, account or domain and PREDICATE is condition used for filtering serialized using JSON5 (check `iroha::data_model::predicate::value::ValuePredicate` type). +You can also filter based on either account, asset or domain id by using the filtering API provided by the Iroha client CLI. Generally, filtering follows the `iroha ENTITY list filter PREDICATE` pattern, where ENTITY is asset, account or domain and PREDICATE is condition used for filtering serialized using JSON5 (check `iroha::data_model::predicate::value::ValuePredicate` type). Here are some examples of filtering: ```bash # Filter domains by id -./iroha domain list filter '{"Identifiable": {"Is": "wonderland"}}' +iroha domain list filter '{"Atom": {"Id": {"Atom": {"Equals": "wonderland"}}}}' # Filter accounts by domain -./iroha account list filter '{"Identifiable": {"EndsWith": "@wonderland"}}' +iroha account list filter '{"Atom": {"Id": {"Domain": {"Atom": {"Equals": "wonderland"}}}}}' # Filter asset by domain -./iroha asset list filter '{"Or": [{"Identifiable": {"Contains": "#wonderland#"}}, {"And": [{"Identifiable": {"Contains": "##"}}, {"Identifiable": {"EndsWith": "@wonderland"}}]}]}' +iroha asset list filter '{"Or": [{"Atom": {"Id": {"Definition": {"Domain": {"Atom": {"Equals": "wonderland"}}}}}}, {"Atom": {"Id": {"Account": {"Domain": {"Atom": {"Equals": "wonderland"}}}}}}]}' ``` ### Execute WASM transaction @@ -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 query stdin ``` diff --git a/crates/iroha_cli/docs/multisig.md b/crates/iroha_cli/docs/multisig.md new file mode 100644 index 00000000000..68b74a7c078 --- /dev/null +++ b/crates/iroha_cli/docs/multisig.md @@ -0,0 +1,133 @@ +# Multi-Signature Usage Guide + +This guide explains how to create and operate a multi-signature account shared by multiple users. + +## Registering a Multi-Signature Account + +__Prerequisites:__ + +- All __signatories__ must be registered in advance. +- The registrant must have sufficient permissions to create an account. + +__Example usage:__ + +```bash +iroha multisig register \ +--account \ +ed0120987EE8092B2CE4622B4F66D6FE87F5D61575F0D0DFCB2D6B2E8905FE68F685B6@domain \ +--signatories \ +ed01203EB45C199FD3998A18FCA1E567F5F228C714BFF5203FEFF00FF06230836BAD22@domain \ +ed01206D75010256E96805161387608125326DD0068F29B4D4FC6755C98E5DA5413EC5@domain \ +ed01201F8E7213A3064DF569776E2ED861D21E574BED93EE5BC41B5540593E70182F8B@domain \ +--weights 1 2 3 \ +--quorum 3 \ +--transaction-ttl "1y 6M 2w 3d 12h 30m 30s" +``` + +__Explanation:__ + +To simplify explanations, accounts are identified by the last four digits of their multihash. + +- `85B6` represents a multi-signature __account__. + + ⚠️ __Warning:__ Any private key associated with the account can control it as a personal account. This security issue will be addressed in [#5022](https://github.com/hyperledger-iroha/iroha/issues/5022). + +- The multi-signature account consists of three __signatories__: `AD22`, `3EC5`, and `2F8B`. +- Each signatory has an assigned __weight__: + - `AD22`: __1__ + - `3EC5`: __2__ + - `2F8B`: __3__ +- A transaction is executed once the total weight of approving signatories meets the __quorum__: + - For example, `AD22` (weight __1__) and `3EC5` (weight __2__) together meet the quorum (__3__). + - Alternatively, `2F8B` (weight __3__) alone meets the quorum. +- If the __transaction TTL__ expires before reaching the quorum, the proposal is discarded. + +## Proposing a Multi-Signature Transaction + +__Prerequisites:__ + +- The multi-signature account must already be registered. +- The proposer must be one of the signatories. + +__Example usage:__ + +```bash +echo '"congratulations"' | iroha -o account meta set \ +--id ed0120987EE8092B2CE4622B4F66D6FE87F5D61575F0D0DFCB2D6B2E8905FE68F685B6@domain \ +--key success_marker \ +| iroha multisig propose \ +--account ed0120987EE8092B2CE4622B4F66D6FE87F5D61575F0D0DFCB2D6B2E8905FE68F685B6@domain +``` + +__Explanation:__ + +- Proposes setting the string value "congratulations" for the key "success_marker" in the metadata of the multi-signature __account__. +- The proposer automatically becomes the first __approver__. + +## Listing Multi-Signature Transactions + +__Assumptions:__ + +- `AD22` (weight __1__) proposed the transaction. +- `3EC5` (weight __2__) is your account, listing the transactions involved. + +__Usage:__ + +```bash +iroha multisig list all +``` + +__Example output:__ + +```json +{ + "FB8AEBB405236A9B4CCD26BBA4988D0B8E03957FDC52DD2A1F9F0A6953079989": { + "instructions": [ + { + "SetKeyValue": { + "Account": { + "object": "ed0120987EE8092B2CE4622B4F66D6FE87F5D61575F0D0DFCB2D6B2E8905FE68F685B6@domain", + "key": "success_marker", + "value": "congratulations" + } + } + } + ], + "proposed_at": "2025-02-06T19:59:58Z", + "expires_in": "1year 6months 17days 12h 26m 39s", + "approval_path": [ + "2 -> [1/3] ed0120987EE8092B2CE4622B4F66D6FE87F5D61575F0D0DFCB2D6B2E8905FE68F685B6@domain" + ] + } +} +``` + +__Explanation:__ + +- The key `FB8A..9989` is the __instructions hash__, identifying the proposal. +- `instructions` contains the proposed changes that will be executed once the quorum is reached. +- `approval_path` represents the approval chain from your account to the root multi-signature account for this proposal. + + The notation `2 -> [1/3]` means: + You are adding a weight of 2 to an existing 1 (by the proposer), out of a required 3 (quorum). + +## Approving a Multi-Signature Transaction + +__Prerequisites:__ + +- The proposal must have been submitted. +- The approver must be a signatory of the multi-signature account. + +__Example usage:__ + +```bash +iroha multisig approve \ +--account ed0120987EE8092B2CE4622B4F66D6FE87F5D61575F0D0DFCB2D6B2E8905FE68F685B6@domain \ +--instructions-hash FB8AEBB405236A9B4CCD26BBA4988D0B8E03957FDC52DD2A1F9F0A6953079989 +``` + +__Explanation:__ + +- Approves a proposal linked to the given __instructions hash__ for the multi-signature __account__. +- Approval may lead to either execution or expiration of the proposal. +- If the approval meets the quorum but the multi-signature account lacks the necessary permissions to execute it, the final approval is discarded. Signatories who have not yet approved it can retry after the multi-signature account has acquired the required permissions. 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..76f72b99ccc --- /dev/null +++ b/crates/iroha_cli/samples/parameter.json @@ -0,0 +1,5 @@ +{ + "Sumeragi": { + "BlockTimeMs": 2001 + } +} diff --git a/crates/iroha_cli/samples/query.json b/crates/iroha_cli/samples/query.json new file mode 100644 index 00000000000..1c01e409634 --- /dev/null +++ b/crates/iroha_cli/samples/query.json @@ -0,0 +1,32 @@ +{ + "Iterable": { + "query": { + "FindAccounts": { + "query": null, + "predicate": { + "Atom": { + "Metadata": { + "Key": { + "key": "key", + "projection": { + "Atom": { + "Equals": "value" + } + } + } + } + } + }, + "selector": [ + { + "Id": { + "Signatory": { + "Atom": null + } + } + } + ] + } + } + } +} diff --git a/crates/iroha_cli/src/main.rs b/crates/iroha_cli/src/main.rs index 52ef99b42bd..aad665ed608 100644 --- a/crates/iroha_cli/src/main.rs +++ b/crates/iroha_cli/src/main.rs @@ -1,174 +1,236 @@ //! 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. +/// Iroha Client CLI provides a simple way to interact with the Iroha Web API. #[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 + /// Print configuration details to stderr #[arg(short, long)] verbose: bool, - /// Subcommands of client cli + /// Path to a JSON5 file for attaching transaction metadata (optional) + #[arg(short, long, value_name("PATH"))] + metadata: Option, + /// Reads instructions from stdin and appends new ones. + /// + /// Example usage: + /// + /// `echo "[]" | iroha -io domain register --id "domain" | iroha -i asset definition register --id "asset#domain" -t Numeric` + #[arg(short, long)] + input: bool, + /// Outputs instructions to stdout without submitting them. + /// + /// Example usage: + /// + /// `iroha -o domain register --id "domain" | iroha -io asset definition register --id "asset#domain" -t Numeric | iroha transaction stdin` + #[arg(short, long)] + output: 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 and write domains + #[command(subcommand)] + Domain(domain::Command), + /// Read and write accounts + #[command(subcommand)] + Account(account::Command), + /// Read and write assets + #[command(subcommand)] + Asset(asset::Command), + /// Read and write peers + #[command(subcommand)] + Peer(peer::Command), + /// Subscribe to events: state changes, transaction/block/trigger progress Events(events::Args), - /// The subcommand related to Wasm - Wasm(wasm::Args), - /// The subcommand related to block streaming + /// Subscribe to 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 and write multi-signature accounts and transactions. + /// + /// See the [usage guide](./docs/multisig.md) for details + #[command(subcommand)] + Multisig(multisig::Command), + /// Read various data + #[command(subcommand)] + Query(query::Command), + /// Read transactions and write various data + #[command(subcommand)] + Transaction(transaction::Command), + /// Read and write roles + #[command(subcommand)] + Role(role::Command), + /// Read and write system parameters + #[command(subcommand)] + Parameter(parameter::Command), + /// Read and write triggers + #[command(subcommand)] + Trigger(trigger::Command), + /// Read and write the executor + #[command(subcommand)] + Executor(executor::Command), + /// Output CLI documentation in Markdown format + 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 input_instructions(&self) -> bool; + + fn output_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<()> { + let mut instructions = match instructions.into() { + Executable::Wasm(wasm) => { + if self.input_instructions() || self.output_instructions() { + eyre::bail!( + "Incompatible `--input` `--output` flags with `iroha transaction wasm`" + ) + } + return self._submit(wasm); + } + Executable::Instructions(instructions) => instructions.into_vec(), + }; + if self.input_instructions() { + let mut acc: Vec = parse_json5_stdin_unchecked()?; + acc.append(&mut instructions); + instructions = acc; + } + if self.output_instructions() { + dump_json5_stdout(&instructions) + } else { + self._submit(instructions) + } + } + + /// 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, + input_instructions: bool, + output_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 input_instructions(&self) -> bool { + self.input_instructions + } + + fn output_instructions(&self) -> bool { + self.output_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 +240,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 +280,26 @@ fn main() -> error_stack::Result<(), MainError> { } let mut context = PrintJsonContext { - write: stdout(), + write: io::stdout(), config, + transaction_metadata: None, + input_instructions: args.input, + output_instructions: args.output, }; - 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 +314,38 @@ 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::>)] + /// Filtering condition specified as a JSON5 string + #[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::>)] + /// Filtering condition specified as a JSON5 string + #[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::>)] + /// Filtering condition specified as a JSON5 string + #[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::>)] + /// Filtering condition specified as a JSON5 string + #[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 +354,48 @@ mod events { use super::*; - #[derive(clap::Args, Debug, Clone, Copy)] + #[derive(clap::Args, Debug)] pub struct Args { - /// Wait timeout - #[clap(short, long, global = true)] + /// Duration to listen for events. + /// Example: "1y 6M 2w 3d 12h 30m 30s" + #[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 the world state undergoes certain changes + State, + /// Notify when a transaction reaches specific stages + Transaction, + /// Notify when a block reaches specific stages + Block, + /// Notify when a trigger execution is ordered + TriggerExecute, + /// Notify when a 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,12 +403,12 @@ 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?)?; } @@ -372,7 +419,7 @@ mod events { 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 +431,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)] + /// Duration to listen for events. + /// Example: "1y 6M 2w 3d 12h 30m 30s" + #[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,18 +451,18 @@ 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?)?; } @@ -427,319 +473,294 @@ mod blocks { 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(()) } } +macro_rules! impl_list { + ($filter:ty, $query:expr) => { + #[derive(clap::Subcommand, Debug)] + pub enum List { + /// List all IDs, or full entries when `--verbose` is specified + All { + /// Display detailed entry information instead of just IDs + #[arg(short, long)] + verbose: bool, + }, + /// Filter by a given predicate + Filter($filter), + } + + impl Run for List { + fn run(self, context: &mut C) -> Result<()> { + let client = context.client_from_config(); + let query = client.query($query); + match self { + List::All { verbose } => { + if verbose { + let entries = query.execute_all()?; + context.print_data(&entries)?; + } else { + let ids = query.select_with(|entry| entry.id).execute_all()?; + context.print_data(&ids)?; + } + } + List::Filter(filter) => { + let view = query.filter(filter.predicate).execute_all()?; + context.print_data(&view)?; + } + } + Ok(()) + } + } + }; +} + mod domain { use super::*; - /// Arguments for domain subcommand - #[derive(Debug, clap::Subcommand)] - pub enum Args { - /// Register domain - Register(Register), + #[derive(clap::Subcommand, Debug)] + pub enum Command { /// List domains - #[clap(subcommand)] + #[command(subcommand)] List(List), - /// Transfer domain + /// Retrieve details of a specific domain + Get(Id), + /// Register a domain + Register(Id), + /// Unregister a domain + Unregister(Id), + /// Transfer ownership of a domain Transfer(Transfer), - /// Edit domain metadata - #[clap(subcommand)] - Metadata(metadata::Args), - } - - impl RunArgs for Args { - fn run(self, context: &mut dyn RunContext) -> Result<()> { - match_all!((self, context), { Args::Register, Args::List, Args::Transfer, Args::Metadata, }) - } - } - - /// Add subcommand for domain - #[derive(Debug, clap::Args)] - pub struct Register { - /// Domain name as double-quoted string - #[arg(short, long)] - pub id: DomainId, - #[command(flatten)] - pub metadata: MetadataArgs, - } - - 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") - } - } - - /// List domains with this command - #[derive(clap::Subcommand, Debug, Clone)] - pub enum List { - /// All domains - All, - /// Filter domains by given predicate - Filter(filter::DomainFilter), + /// Read and write metadata + #[command(subcommand)] + Meta(metadata::domain::Command), } - impl RunArgs for List { - fn run(self, context: &mut dyn RunContext) -> Result<()> { - let client = context.client_from_config(); - - let query = client.query(FindDomains::new()); - - 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(()) + 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), + } } } - /// Transfer a domain between accounts - #[derive(Debug, clap::Args)] + #[derive(clap::Args, Debug)] pub struct Transfer { - /// Domain name as double-quited string + /// Domain name #[arg(short, long)] pub id: DomainId, - /// Account from which to transfer (in form `name@domain_name`) + /// Source account, in the format "multihash@domain" #[arg(short, long)] pub from: AccountId, - /// Account to which to transfer (in form `name@domain_name`) + /// Destination account, in the format "multihash@domain" #[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") - } } - 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), - } - - impl RunArgs for Args { - fn run(self, context: &mut dyn RunContext) -> Result<()> { - match_all!((self, context), { Args::Set, Args::Remove, }) - } - } - - /// 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, - } - - 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") - } - } - - /// 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::Args, Debug)] + pub struct Id { + /// Domain name + #[arg(short, long)] + pub id: DomainId, } + + impl_list!(filter::DomainFilter, FindDomains); } mod account { use std::fmt::Debug; - use super::{Permission as DataModelPermission, *}; + use super::*; - /// subcommands for account subcommand #[derive(clap::Subcommand, Debug)] - pub enum Args { - /// Register account - Register(Register), + pub enum Command { + /// Read and write account roles + #[command(subcommand)] + Role(RoleCommand), + /// Read and write account permissions + #[command(subcommand)] + Permission(PermissionCommand), /// 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, - }) - } - } - - /// Register account - #[derive(clap::Args, Debug)] - pub struct Register { - /// Id of account in form `name@domain_name` - #[arg(short, long)] - pub id: AccountId, - #[command(flatten)] - pub metadata: MetadataArgs, + /// Retrieve details of a specific account + Get(Id), + /// Register an account + Register(Id), + /// Unregister an account + Unregister(Id), + /// Read and write metadata + #[command(subcommand)] + Meta(metadata::account::Command), } - 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") + 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), + } } } - /// List accounts with this command - #[derive(clap::Subcommand, Debug, Clone)] - pub enum List { - /// All accounts - All, - /// Filter accounts by given predicate - Filter(filter::AccountFilter), + #[derive(clap::Subcommand, Debug)] + pub enum RoleCommand { + /// List account role IDs + List(Id), + /// Grant a role to an account + Grant(IdRole), + /// Revoke a role from an account + 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") + } + } + } } - impl RunArgs for List { - fn run(self, context: &mut dyn RunContext) -> Result<()> { - let client = context.client_from_config(); - - let query = client.query(FindAccounts::new()); - - 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::Subcommand, Debug)] + pub enum PermissionCommand { + /// List account permissions + List(Id), + /// Grant an account permission using JSON5 input from stdin + Grant(Id), + /// Revoke an account permission using JSON5 input from 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(context)?; + 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(context)?; + 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") + } + } } } #[derive(clap::Args, Debug)] - pub struct Grant { - /// Account id + pub struct Id { + /// Account in the format "multihash@domain" #[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") - } + id: AccountId, } - /// List all account permissions #[derive(clap::Args, Debug)] - pub struct ListPermissions { - /// Account id + pub struct IdRole { + /// Account in the format "multihash@domain" #[arg(short, long)] - id: AccountId, + pub id: AccountId, + /// Role name + #[arg(short, long)] + pub role: RoleId, } - 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(()) - } - } + impl_list!(filter::AccountFilter, FindAccounts); } mod asset { @@ -747,37 +768,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), + pub enum Command { + /// Read and write asset definitions + #[command(subcommand)] + Definition(definition::Command), + /// Retrieve details of a specific asset + Get(Id), /// List assets - #[clap(subcommand)] + #[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 the quantity of an asset + Mint(IdQuantity), + /// Decrease the quantity of an asset + Burn(IdQuantity), + /// Transfer an asset between accounts + #[command(name = "transfer")] + TransferNumeric(TransferNumeric), + /// Transfer a key-value store between accounts + #[command(name = "transferkvs")] + TransferStore(TransferStore), + /// Retrieve a value from the key-value store + #[command(name = "getkv")] + GetKeyValue(IdKey), + /// Create or update a key-value entry using JSON5 input from stdin + #[command(name = "setkv")] + SetKeyValue(IdKey), + /// Delete an entry from the 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(context)?; + 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,485 +875,217 @@ mod asset { use super::*; - /// Subcommand for managing asset definitions #[derive(clap::Subcommand, Debug)] - pub enum Args { - /// Command for Registering a new asset - Register(Register), + pub enum Command { /// List asset definitions - #[clap(subcommand)] + #[command(subcommand)] List(List), + /// Retrieve details of a specific asset definition + Get(Id), + /// Register an asset definition + Register(Register), + /// Unregister an asset definition + Unregister(Id), + /// Transfer ownership of an asset definition + Transfer(Transfer), + /// Read and 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.mint_once { + 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 the format "asset#domain" + #[arg(short, long)] pub id: AssetDefinitionId, - /// Mintability of asset + /// Disables minting after the first instance #[arg(short, long)] - pub unmintable: bool, - /// Value type stored in asset + pub mint_once: bool, + /// Data type stored in the 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 the format "asset#domain" + #[arg(short, long)] + pub id: AssetDefinitionId, + /// Source account, in the format "multihash@domain" + #[arg(short, long)] + pub from: AccountId, + /// Destination account, in the format "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 the format "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()); - - 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(()) - } - } + impl_list!(filter::AssetDefinitionFilter, FindAssetsDefinitions); } - /// 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 id: AssetId, - /// Quantity to mint + pub struct TransferNumeric { + /// Asset in the format "asset##account@domain" or "asset#another_domain#account@domain" #[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 id: AssetId, - /// Quantity to mint + /// Destination account, in the format "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`") - } - } - - /// 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 + /// Transfer amount (integer or decimal) #[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") - } } - /// 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 TransferStore { + /// Asset in the format "asset##account@domain" or "asset#another_domain#account@domain" + #[arg(short, long)] pub id: AssetId, - } - - 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(()) - } - } - - /// List assets with this command - #[derive(clap::Subcommand, Debug, Clone)] - pub enum List { - /// All assets - All, - /// Filter assets by given predicate - Filter(filter::AssetFilter), - } - - impl RunArgs for List { - fn run(self, context: &mut dyn RunContext) -> Result<()> { - let client = context.client_from_config(); - - let query = client.query(FindAssets::new()); - - 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(()) - } + /// Destination account, in the format "multihash@domain" + #[arg(short, long)] + pub to: AccountId, } #[derive(clap::Args, Debug)] - pub struct SetKeyValue { - /// Asset id for the Store asset (in form of `asset##account@domain_name`) - #[clap(long)] + pub struct Id { + /// Asset in the format "asset##account@domain" or "asset#another_domain#account@domain" + #[arg(short, 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 struct IdQuantity { + /// Asset in the format "asset##account@domain" or "asset#another_domain#account@domain" + #[arg(short, 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(()) - } + /// Amount of change (integer or decimal) + #[arg(short, long)] + pub quantity: Numeric, } #[derive(clap::Args, Debug)] - pub struct GetKeyValue { - /// Asset id for the Store asset (in form of `asset##account@domain_name`) - #[clap(long)] + pub struct IdKey { + /// Asset in the format "asset##account@domain" or "asset#another_domain#account@domain" + #[arg(short, long)] pub id: AssetId, - /// The key for the store value - #[clap(long)] + /// Key for retrieving the corresponding value + #[arg(short, 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(()) - } - } + impl_list!(filter::AssetFilter, FindAssets); } 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 a peer + Register(Id), + /// Unregister a 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, - } - - 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") - } - } - - /// Unregister subcommand of peer - #[derive(clap::Args, Debug)] - pub struct Unregister { - /// Public key of the peer - #[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)?; - } - } + #[derive(clap::Subcommand, Debug)] + pub enum List { + /// List all registered peers + All, + } - Ok(()) - } - } + 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) } } + + #[derive(clap::Args, Debug)] + pub struct Id { + /// Peer's public key in multihash format + #[arg(short, long)] + pub key: PublicKey, + } } mod multisig { use std::{ collections::BTreeMap, - io::{BufReader, Read as _}, num::{NonZeroU16, NonZeroU64}, time::{Duration, SystemTime}, }; @@ -1276,41 +1097,41 @@ 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 using JSON5 input from 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)] pub account: AccountId, - /// Signatories of the multisig account + /// List of signatories for the multisig account #[arg(short, long, num_args(2..))] pub signatories: Vec, - /// Relative weights of responsibility of respective signatories + /// Relative weights of signatories' responsibilities #[arg(short, long, num_args(2..))] pub weights: Vec, - /// Threshold of total weight at which the multisig is considered authenticated + /// Threshold of total weight required for authentication #[arg(short, long)] pub quorum: u16, - /// Time-to-live of multisig transactions made by the multisig account + /// Time-to-live for multisig transactions. + /// Example: "1y 6M 2w 3d 12h 30m 30s" #[arg(short, long, default_value_t = default_transaction_ttl())] pub transaction_ttl: humantime::Duration, } @@ -1319,12 +1140,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 +1159,26 @@ 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 + /// Multisig authority managing the proposed transaction #[arg(short, long)] pub account: AccountId, - /// Time-to-live of multisig transactions that overrides to shorten the account default + /// Overrides the default time-to-live for this transaction. + /// Example: "1y 6M 2w 3d 12h 30m 30s" #[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(context)?; let transaction_ttl_ms = self.transaction_ttl.map(|duration| { duration .as_millis() @@ -1378,41 +1194,41 @@ 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 + /// Multisig authority of the transaction #[arg(short, long)] pub account: AccountId, - /// Instructions to approve + /// Hash of the instructions to approve #[arg(short, long)] 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 +1246,7 @@ mod multisig { let mut proposals = BTreeMap::new(); fold_proposals(&mut proposals, &mut stack, &client)?; - context.print_data(&proposals)?; - - Ok(()) + context.print_data(&proposals) } } @@ -1601,30 +1415,639 @@ mod multisig { } } -#[cfg(test)] -mod tests { +mod query { + use iroha::data_model::query::AnyQueryBox; + + use super::*; + + #[derive(clap::Subcommand, Debug)] + pub enum Command { + /// Query using JSON5 input from 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(context)?; + + 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 { + /// Retrieve details of a specific transaction + Get(Get), + /// Send an empty transaction that logs a message + Ping(Ping), + /// Send a transaction using Wasm input + Wasm(Wasm), + /// Send a transaction using JSON5 input from 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 { + /// Hash of the transaction to retrieve + #[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 { + /// Log levels: TRACE, DEBUG, INFO, WARN, ERROR (in increasing order of visibility) + #[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 { + /// Path to the Wasm file. If omitted, reads 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)?; + context + .finish(instructions) + .wrap_err("Failed to submit parsed instructions") + } + } +} + +mod role { + use super::*; + + #[derive(clap::Subcommand, Debug)] + pub enum Command { + /// Read and write role permissions + #[command(subcommand)] + Permission(PermissionCommand), + /// List role IDs + #[command(subcommand)] + List(List), + /// Register a role and grant it to the registrant + Register(Id), + /// Unregister a 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 using JSON5 input from stdin + Grant(Id), + /// Revoke role permission using JSON5 input from 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(context)?; + 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(context)?; + 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 + #[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 system parameters + #[command(subcommand)] + List(List), + /// Set a system parameter using JSON5 input from stdin + Set(Set), + } + + impl Run for Command { + fn run(self, context: &mut C) -> Result<()> { + use self::Command::*; + match_all!((self, context), { List, Set }) + } + } + + #[derive(clap::Subcommand, Debug)] + pub enum List { + /// List all system parameters + All, + } + + 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; + + impl Run for Set { + fn run(self, context: &mut C) -> Result<()> { + let entry: Parameter = parse_json5_stdin(context)?; + 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), + /// Retrieve details of a specific trigger + // TODO: For better readability and reusability, triggers should reference a Wasm executable instead of storing the blob itself. + Get(Id), + /// TODO: Register a trigger + Register(Register), + /// Unregister a trigger + Unregister(Id), + /// Increase the number of trigger executions + Mint(IdInt), + /// Decrease the number of trigger executions + Burn(IdInt), + /// Read and 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") + } + Mint(args) => { + let instruction = iroha::data_model::isi::Mint::trigger_repetitions( + args.repetitions, + args.id, + ); + context + .finish([instruction]) + .wrap_err("Failed to mint trigger repetitions") + } + Burn(args) => { + let instruction = iroha::data_model::isi::Burn::trigger_repetitions( + args.repetitions, + args.id, + ); + context + .finish([instruction]) + .wrap_err("Failed to burn trigger repetitions") + } + 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 + #[arg(short, long)] + pub id: TriggerId, + } + + #[derive(clap::Args, Debug)] + pub struct IdInt { + /// Trigger name + #[arg(short, long)] + pub id: TriggerId, + /// Amount of change (integer) + #[arg(short, long)] + pub repetitions: u32, + } + + #[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 { + /// Retrieve the executor data model + DataModel, + /// Upgrade the executor + Upgrade(Upgrade), + } + + impl Run for Command { + fn run(self, context: &mut C) -> Result<()> { + use self::Command::*; + match self { + DataModel => { + let client = context.client_from_config(); + let model = client.query_single(FindExecutorDataModel)?; + context.print_data(&model) + } + Upgrade(args) => { + let instruction = fs::read(args.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]) + } + } + } + } + + #[derive(clap::Args, Debug)] + pub struct Upgrade { + /// Path to the compiled Wasm file + #[arg(short, long)] + path: PathBuf, + } +} + +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 { + /// Retrieve a value from the key-value store + Get(IdKey), + /// Create or update an entry in the key-value store using JSON5 input from stdin + Set(IdKey), + /// Delete an entry from the 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(context)?; + 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 { + /// Retrieve a value from the key-value store + Get(IdKey), + /// Create or update an entry in the key-value store using JSON5 input from stdin + Set(IdKey), + /// Delete an entry from the key-value store + Remove(IdKey), + } + + #[derive(clap::Args, Debug)] + pub struct IdKey { + #[arg(short, long)] + pub id: ::Id, + #[arg(short, long)] + pub key: Name, } - // Boolean values - case!("true", Json::new(true)); - case!("false", Json::new(false)); + 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(context)?; + 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]) + } + } + } + } + } +} - // Numeric values - case!("\"123\"", Json::new(numeric!(123))); - case!("\"123.0\"", Json::new(numeric!(123.0))); +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(()) +} - // JSON Value - let json_str = r#"{"Vec":[{"String":"a"},{"String":"b"}]}"#; - case!(json_str, serde_json::from_str(json_str).unwrap()); +fn parse_json5_stdin(context: &impl RunContext) -> Result +where + T: for<'a> serde::Deserialize<'a>, +{ + if context.input_instructions() { + eyre::bail!("Incompatible `--input` flag with the command") } + parse_json5_stdin_unchecked() +} + +fn parse_json5_stdin_unchecked() -> 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_core/src/smartcontracts/wasm/cache.rs b/crates/iroha_core/src/smartcontracts/wasm/cache.rs index c3cf72652ad..85c2bd9293f 100644 --- a/crates/iroha_core/src/smartcontracts/wasm/cache.rs +++ b/crates/iroha_core/src/smartcontracts/wasm/cache.rs @@ -10,10 +10,13 @@ use crate::{ state::StateTransaction, }; -/// Executor related things (linker initialization, module instantiation, memory free) -/// takes significant amount of time in case of single peer transactions handling. -/// (https://github.com/hyperledger/iroha/issues/3716#issuecomment-2348417005). -/// So this cache is used to share `Store` and `Instance` for different transaction validation. +/// Enables the reuse of `Store` and `Instance` across multiple transaction validations. +/// +/// # Context +/// +/// Executor-related operations (such as linker initialization, module instantiation, and memory deallocation) +/// can significantly impact performance when handling transactions. This issue is discussed in +/// [#3716](https://github.com/hyperledger-iroha/iroha/issues/3716#issuecomment-2348417005). #[derive(Default)] pub struct WasmCache<'world, 'block, 'state> { cache: Option>>, 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_smart_contract_utils/src/lib.rs b/crates/iroha_smart_contract_utils/src/lib.rs index 9e907a15d6c..340917d5822 100644 --- a/crates/iroha_smart_contract_utils/src/lib.rs +++ b/crates/iroha_smart_contract_utils/src/lib.rs @@ -36,7 +36,7 @@ macro_rules! register_getrandom_err_callback { fn stub_getrandom(_dest: &mut [u8]) -> Result<(), $crate::getrandom::Error> { const ERROR_MESSAGE: &str = "`getrandom()` is not implemented. To provide your custom function \ - see https://docs.rs/getrandom/latest/getrandom/macro.register_custom_getrandom.html. \ + see https://docs.rs/getrandom/0.2/getrandom/macro.register_custom_getrandom.html. \ Be aware that your function must give the same result on different peers at the same execution round, and keep in mind the consequences of purely implemented random function."; 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 deleted file mode 100644 index 2d8e1cfac86..00000000000 --- a/scripts/tests/tick.json +++ /dev/null @@ -1,8 +0,0 @@ -[ - { - "Log": { - "DEBUG": null, - "msg": "Just ticking time" - } - } -] diff --git a/wasm/Cargo.toml b/wasm/Cargo.toml index 60db2044600..e449f1a641b 100644 --- a/wasm/Cargo.toml +++ b/wasm/Cargo.toml @@ -1,6 +1,6 @@ [workspace.package] edition = "2021" -version = "2.0.0-rc.1.0" +version = "2.0.0-rc.1.1" # TODO: teams are being deprecated update the authors URL authors = ["Iroha 2 team "] @@ -24,13 +24,13 @@ opt-level = "z" # Optimize for size vs speed with "s"/"z"(removes vectorizat codegen-units = 1 # Further reduces binary size but increases compilation time [workspace.dependencies] -iroha_smart_contract = { version = "=2.0.0-rc.1.0", path = "../crates/iroha_smart_contract", features = ["debug"] } -iroha_trigger = { version = "=2.0.0-rc.1.0", path = "../crates/iroha_trigger", features = ["debug"] } -iroha_executor = { version = "=2.0.0-rc.1.0", path = "../crates/iroha_executor", features = ["debug"] } -iroha_schema = { version = "=2.0.0-rc.1.0", path = "../crates/iroha_schema" } +iroha_smart_contract = { version = "=2.0.0-rc.1.1", path = "../crates/iroha_smart_contract", features = ["debug"] } +iroha_trigger = { version = "=2.0.0-rc.1.1", path = "../crates/iroha_trigger", features = ["debug"] } +iroha_executor = { version = "=2.0.0-rc.1.1", path = "../crates/iroha_executor", features = ["debug"] } +iroha_schema = { version = "=2.0.0-rc.1.1", path = "../crates/iroha_schema" } -iroha_data_model = { version = "=2.0.0-rc.1.0", path = "../crates/iroha_data_model", default-features = false } -iroha_executor_data_model = { version = "=2.0.0-rc.1.0", path = "../crates/iroha_executor_data_model" } +iroha_data_model = { version = "=2.0.0-rc.1.1", path = "../crates/iroha_data_model", default-features = false } +iroha_executor_data_model = { version = "=2.0.0-rc.1.1", path = "../crates/iroha_executor_data_model" } mint_rose_trigger_data_model = { path = "../data_model/samples/mint_rose_trigger_data_model" } executor_custom_data_model = { path = "../data_model/samples/executor_custom_data_model" } diff --git a/wasm/samples/mint_rose_trigger/Cargo.toml b/wasm/samples/mint_rose_trigger/Cargo.toml index 2ceb93a8810..539e35a4fb1 100644 --- a/wasm/samples/mint_rose_trigger/Cargo.toml +++ b/wasm/samples/mint_rose_trigger/Cargo.toml @@ -11,7 +11,7 @@ license.workspace = true crate-type = ['cdylib'] [dependencies] -mint_rose_trigger_data_model = { version = "=2.0.0-rc.1.0", path = "../../../data_model/samples/mint_rose_trigger_data_model" } +mint_rose_trigger_data_model = { version = "=2.0.0-rc.1.1", path = "../../../data_model/samples/mint_rose_trigger_data_model" } iroha_trigger.workspace = true panic-halt.workspace = true