From 3f3047803200cfd8bd814543f41cb8f05e5c5903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marin=20Ver=C5=A1i=C4=87?= Date: Tue, 11 Jul 2023 12:35:06 +0200 Subject: [PATCH] [feature] #3468: implement server-side cursor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marin Veršić --- Cargo.lock | 888 ++++++++++-------- cli/Cargo.toml | 1 + cli/src/stream.rs | 2 +- cli/src/torii/cursor.rs | 79 ++ cli/src/torii/mod.rs | 53 +- cli/src/torii/pagination.rs | 97 +- cli/src/torii/routing.rs | 114 ++- cli/src/torii/utils.rs | 2 +- client/benches/torii.rs | 21 +- client/src/client.rs | 410 ++++---- client/src/http.rs | 12 +- client/src/http_default.rs | 12 +- client/tests/integration/asset.rs | 43 +- client/tests/integration/asset_propagation.rs | 6 +- .../integration/multiple_blocks_created.rs | 6 +- .../integration/multisignature_account.rs | 6 +- .../integration/multisignature_transaction.rs | 11 +- client/tests/integration/non_mintable.rs | 14 +- client/tests/integration/offline_peers.rs | 6 +- client/tests/integration/pagination.rs | 16 +- client/tests/integration/permissions.rs | 4 +- client/tests/integration/queries/account.rs | 6 +- client/tests/integration/queries/role.rs | 21 +- client/tests/integration/restart_peer.rs | 28 +- client/tests/integration/roles.rs | 14 +- client/tests/integration/set_parameter.rs | 11 +- client/tests/integration/sorting.rs | 114 ++- client/tests/integration/transfer_asset.rs | 6 +- .../integration/triggers/time_trigger.rs | 6 +- client/tests/integration/tx_history.rs | 17 +- client/tests/integration/tx_rollback.rs | 8 +- client/tests/integration/unregister_peer.rs | 6 +- client/tests/integration/unstable_network.rs | 6 +- client/tests/integration/upgrade.rs | 8 +- config/Cargo.toml | 1 + config/src/torii.rs | 18 +- configs/peer/config.json | 4 +- configs/peer/validator.wasm | Bin 381036 -> 380675 bytes core/benches/apply_blocks/apply_blocks.rs | 2 +- core/src/queue.rs | 4 +- core/src/smartcontracts/isi/account.rs | 28 +- core/src/smartcontracts/isi/asset.rs | 52 +- core/src/smartcontracts/isi/block.rs | 11 +- core/src/smartcontracts/isi/domain.rs | 23 +- core/src/smartcontracts/isi/query.rs | 12 +- core/src/smartcontracts/isi/triggers/mod.rs | 26 +- core/src/smartcontracts/isi/triggers/set.rs | 76 +- core/src/smartcontracts/isi/tx.rs | 19 +- core/src/smartcontracts/isi/world.rs | 27 +- core/src/smartcontracts/mod.rs | 6 +- core/src/smartcontracts/wasm.rs | 6 +- core/src/sumeragi/main_loop.rs | 1 - core/src/wsv.rs | 11 +- core/test_network/Cargo.toml | 1 + core/test_network/src/lib.rs | 112 +-- data_model/cbindgen.toml | 3 + .../transparent_api_private_item.stderr | 2 +- data_model/src/lib.rs | 15 +- data_model/src/numeric.rs | 2 +- data_model/src/pagination.rs | 122 --- data_model/src/predicate.rs | 30 +- data_model/src/query/cursor.rs | 57 ++ data_model/src/{query.rs => query/mod.rs} | 138 ++- data_model/src/query/pagination.rs | 60 ++ data_model/src/{ => query}/sorting.rs | 17 +- data_model/src/transaction.rs | 20 +- data_model/src/visit.rs | 2 + docs/source/references/config.md | 26 +- docs/source/references/schema.json | 98 +- lints.toml | 11 - macro/derive/src/lib.rs | 4 +- macro/utils/src/lib.rs | 4 +- schema/gen/src/lib.rs | 10 +- tools/kagami/src/docs.rs | 4 +- version/derive/src/lib.rs | 43 +- version/src/lib.rs | 4 +- wasm/src/lib.rs | 7 +- wasm/validator/derive/src/validate.rs | 4 +- 78 files changed, 1816 insertions(+), 1361 deletions(-) create mode 100644 cli/src/torii/cursor.rs create mode 100644 data_model/cbindgen.toml delete mode 100644 data_model/src/pagination.rs create mode 100644 data_model/src/query/cursor.rs rename data_model/src/{query.rs => query/mod.rs} (94%) create mode 100644 data_model/src/query/pagination.rs rename data_model/src/{ => query}/sorting.rs (77%) diff --git a/Cargo.lock b/Cargo.lock index 996a22db197..0206b299424 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,11 +13,11 @@ dependencies = [ [[package]] name = "addr2line" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" +checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" dependencies = [ - "gimli 0.27.2", + "gimli 0.27.3", ] [[package]] @@ -203,9 +203,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.71" +version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" +checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" [[package]] name = "arc-swap" @@ -221,9 +221,9 @@ checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" [[package]] name = "arrayvec" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "async-stream" @@ -244,18 +244,18 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.26", ] [[package]] name = "async-trait" -version = "0.1.68" +version = "0.1.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" +checksum = "a564d521dd56509c4c47480d00b80ee55f7e385ae48db5744c67ad50c92d2ebf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.26", ] [[package]] @@ -301,9 +301,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "axum" -version = "0.6.18" +version = "0.6.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8175979259124331c1d7bf6586ee7e0da434155e4b2d48ec2c8386281d8df39" +checksum = "a6a1de45611fdb535bfde7b7de4fd54f4fd2b17b1737c0a59b69bf9b92074b8c" dependencies = [ "async-trait", "axum-core", @@ -346,16 +346,16 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.67" +version = "0.3.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" +checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" dependencies = [ - "addr2line 0.19.0", + "addr2line 0.20.0", "cc", "cfg-if", "libc", - "miniz_oxide 0.6.2", - "object 0.30.4", + "miniz_oxide", + "object 0.31.1", "rustc-demangle", ] @@ -373,9 +373,9 @@ checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" [[package]] name = "basic-toml" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c0de75129aa8d0cceaf750b89013f0e08804d6ec61416da787b35ad0d7cddf1" +checksum = "7bfc506e7a2370ec239e1d072507b2a80c833083699d3c6fa176fbb4de8448c6" dependencies = [ "serde", ] @@ -412,9 +412,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.3.1" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6776fc96284a0bb647b615056fc496d1fe1644a7ab01829818a6d91cae888b84" +checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" [[package]] name = "bitvec" @@ -497,13 +497,12 @@ checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" [[package]] name = "bstr" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a246e68bb43f6cd9db24bea052a53e40405417c5fb372e3d1a8a7f770a564ef5" +checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05" dependencies = [ "memchr", - "once_cell", - "regex-automata", + "regex-automata 0.3.3", "serde", ] @@ -654,7 +653,7 @@ dependencies = [ "bitflags 1.3.2", "clap_derive 3.2.25", "clap_lex 0.2.4", - "indexmap", + "indexmap 1.9.3", "once_cell", "strsim", "termcolor", @@ -663,20 +662,20 @@ dependencies = [ [[package]] name = "clap" -version = "4.3.11" +version = "4.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1640e5cc7fb47dbb8338fd471b105e7ed6c3cb2aeb00c2e067127ffd3764a05d" +checksum = "8f644d0dac522c8b05ddc39aaaccc5b136d5dc4ff216610c5641e3be5becf56c" dependencies = [ "clap_builder", - "clap_derive 4.3.2", + "clap_derive 4.3.12", "once_cell", ] [[package]] name = "clap_builder" -version = "4.3.11" +version = "4.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98c59138d527eeaf9b53f35a77fcc1fad9d883116070c63d5de1c7dc7b00c72b" +checksum = "af410122b9778e024f9e0fb35682cc09cc3f85cad5e8d3ba8f47a9702df6e73d" dependencies = [ "anstream", "anstyle", @@ -700,14 +699,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.3.2" +version = "4.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f" +checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.26", ] [[package]] @@ -776,13 +775,13 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "colored" -version = "2.0.0" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" +checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" dependencies = [ - "atty", + "is-terminal", "lazy_static", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -812,9 +811,9 @@ dependencies = [ [[package]] name = "console-subscriber" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ab2224a0311582eb03adba4caaf18644f7b1f10a760803a803b9b605187fc7" +checksum = "d4cf42660ac07fcebed809cfe561dd8730bcd35b075215e6479c516bcd0d11cb" dependencies = [ "console-api", "crossbeam-channel", @@ -867,9 +866,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.7" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" dependencies = [ "libc", ] @@ -1058,14 +1057,14 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.14" +version = "0.9.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" +checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" dependencies = [ "autocfg 1.1.0", "cfg-if", "crossbeam-utils", - "memoffset 0.8.0", + "memoffset 0.9.0", "scopeguard", ] @@ -1081,9 +1080,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.15" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" dependencies = [ "cfg-if", ] @@ -1106,9 +1105,9 @@ dependencies = [ [[package]] name = "crossterm_winapi" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" dependencies = [ "winapi", ] @@ -1200,9 +1199,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.97" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88abab2f5abbe4c56e8f1fb431b784d710b709888f35755a160e62e33fe38e8" +checksum = "5032837c1384de3708043de9d4e97bb91290faca6c16529a28aa340592a78166" dependencies = [ "cc", "cxxbridge-flags", @@ -1212,9 +1211,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.97" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c0c11acd0e63bae27dcd2afced407063312771212b7a823b4fd72d633be30fb" +checksum = "51368b3d0dbf356e10fcbfd455a038503a105ee556f7ee79b6bb8c53a7247456" dependencies = [ "cc", "codespan-reporting", @@ -1222,31 +1221,31 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.23", + "syn 2.0.26", ] [[package]] name = "cxxbridge-flags" -version = "1.0.97" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d3816ed957c008ccd4728485511e3d9aaf7db419aa321e3d2c5a2f3411e36c8" +checksum = "0d9062157072e4aafc8e56ceaf8325ce850c5ae37578c852a0d4de2cecdded13" [[package]] name = "cxxbridge-macro" -version = "1.0.97" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26acccf6f445af85ea056362561a24ef56cdc15fcc685f03aec50b9c702cb6d" +checksum = "cf01e8a540f5a4e0f284595834f81cf88572f244b768f051724537afa99a2545" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.26", ] [[package]] name = "darling" -version = "0.20.1" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0558d22a7b463ed0241e993f76f09f30b126687447751a8638587b864e4b3944" +checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" dependencies = [ "darling_core", "darling_macro", @@ -1254,37 +1253,37 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.1" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab8bfa2e259f8ee1ce5e97824a3c55ec4404a0d772ca7fa96bf19f0752a046eb" +checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn 2.0.23", + "syn 2.0.26", ] [[package]] name = "darling_macro" -version = "0.20.1" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a" +checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core", "quote", - "syn 2.0.23", + "syn 2.0.26", ] [[package]] name = "dashmap" -version = "5.4.0" +version = "5.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc" +checksum = "6943ae99c34386c84a470c499d3414f66502a41340aa895406e0d2e4a207b91d" dependencies = [ "cfg-if", - "hashbrown 0.12.3", + "hashbrown 0.14.0", "lock_api", "once_cell", "parking_lot_core", @@ -1377,14 +1376,14 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.26", ] [[package]] name = "dissimilar" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "210ec60ae7d710bed8683e333e9d2855a8a56a3e9892b38bad3bb0d4d29b0d5e" +checksum = "86e3bdc80eee6e16b2b6b0f87fbc98c04bee3455e35174c0de1a125d0688c632" [[package]] name = "duct" @@ -1406,9 +1405,9 @@ checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" [[package]] name = "dyn-clone" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" +checksum = "304e6508efa593091e97a9abbc10f90aa7ca635b6d2784feff3c89d41dd12272" [[package]] name = "ecdsa" @@ -1495,11 +1494,17 @@ dependencies = [ "termcolor", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "erased-serde" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f2b0c2380453a92ea8b6c8e5f64ecaafccddde8ceab55ff7a8ac1029f894569" +checksum = "da96524cc884f6558f1769b6c46686af2fe8e8b4cd253bd5a3cdba8181b8e070" dependencies = [ "serde", ] @@ -1593,6 +1598,12 @@ dependencies = [ "instant", ] +[[package]] +name = "fastrand" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" + [[package]] name = "ff" version = "0.10.1" @@ -1652,7 +1663,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" dependencies = [ "crc32fast", - "miniz_oxide 0.7.1", + "miniz_oxide", ] [[package]] @@ -1747,7 +1758,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.26", ] [[package]] @@ -1871,24 +1882,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" dependencies = [ "fallible-iterator", - "indexmap", + "indexmap 1.9.3", "stable_deref_trait", ] [[package]] name = "gimli" -version = "0.27.2" +version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" +checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" [[package]] name = "gix" -version = "0.44.1" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bf41b61f7df395284f7a579c0fa1a7e012c5aede655174d4e91299ef1cac643" +checksum = "c1e74cea676de7f53a79f3c0365812b11f6814b81e671b8ee4abae6ca09c7881" dependencies = [ "gix-actor", "gix-attributes", + "gix-commitgraph", "gix-config", "gix-credentials", "gix-date", @@ -1903,6 +1915,7 @@ dependencies = [ "gix-index", "gix-lock", "gix-mailmap", + "gix-negotiate", "gix-object", "gix-odb", "gix-pack", @@ -1913,6 +1926,7 @@ dependencies = [ "gix-revision", "gix-sec", "gix-tempfile", + "gix-trace", "gix-traverse", "gix-url", "gix-utils", @@ -1928,9 +1942,9 @@ dependencies = [ [[package]] name = "gix-actor" -version = "0.20.0" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "848efa0f1210cea8638f95691c82a46f98a74b9e3524f01d4955ebc25a8f84f3" +checksum = "1969b77b9ee4cc1755c841987ec6f7622aaca95e952bcafb76973ae59d1b8716" dependencies = [ "bstr", "btoi", @@ -1942,9 +1956,9 @@ dependencies = [ [[package]] name = "gix-attributes" -version = "0.12.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3015baa01ad2122fbcaab7863c857a603eb7b7ec12ac8141207c42c6439805e2" +checksum = "e3772b0129dcd1fc73e985bbd08a1482d082097d2915cb1ee31ce8092b8e4434" dependencies = [ "bstr", "gix-glob", @@ -1959,36 +1973,50 @@ dependencies = [ [[package]] name = "gix-bitmap" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc02feb20ad313d52a450852f2005c2205d24f851e74d82b7807cbe12c371667" +checksum = "311e2fa997be6560c564b070c5da2d56d038b645a94e1e5796d5d85a350da33c" dependencies = [ "thiserror", ] [[package]] name = "gix-chunk" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7acf3bc6c4b91e8fb260086daf5e105ea3a6d913f5fd3318137f7e309d6e540" +checksum = "39db5ed0fc0a2e9b1b8265993f7efdbc30379dec268f3b91b7af0c2de4672fdd" dependencies = [ "thiserror", ] [[package]] name = "gix-command" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6141b70cfb21255223e42f3379855037cbbe8673b58dd8318d2f09b516fad1" +checksum = "bb49ab557a37b0abb2415bca2b10e541277dff0565deb5bd5e99fd95f93f51eb" dependencies = [ "bstr", ] +[[package]] +name = "gix-commitgraph" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed42baa50075d41c1a0931074ce1a97c5797c7c6fe7591d9f1f2dcd448532c26" +dependencies = [ + "bstr", + "gix-chunk", + "gix-features", + "gix-hash", + "memmap2", + "thiserror", +] + [[package]] name = "gix-config" -version = "0.22.0" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d252a0eddb6df74600d3d8872dc9fe98835a7da43110411d705b682f49d4ac1" +checksum = "817688c7005a716d9363e267913526adea402dabd947f4ba63842d10cc5132af" dependencies = [ "bstr", "gix-config-value", @@ -2008,11 +2036,11 @@ dependencies = [ [[package]] name = "gix-config-value" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f216df1c33e6e1555923eff0096858a879e8aaadd35b5d788641e4e8064c892" +checksum = "83960be5e99266bcf55dae5a24731bbd39f643bfb68f27e939d6b06836b5b87d" dependencies = [ - "bitflags 2.3.1", + "bitflags 2.3.3", "bstr", "gix-path", "libc", @@ -2021,9 +2049,9 @@ dependencies = [ [[package]] name = "gix-credentials" -version = "0.14.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4874a4fc11ffa844a3c2b87a66957bda30a73b577ef1acf15ac34df5745de5ff" +checksum = "75a75565e0e6e7f80cfa4eb1b05cc448c6846ddd48dcf413a28875fbc11ee9af" dependencies = [ "bstr", "gix-command", @@ -2037,21 +2065,21 @@ dependencies = [ [[package]] name = "gix-date" -version = "0.5.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc164145670e9130a60a21670d9b6f0f4f8de04e5dd256c51fa5a0340c625902" +checksum = "8e9a04a1d2387c955ec91059d56b673000dd24f3c07cad08ed253e36381782bf" dependencies = [ "bstr", "itoa", "thiserror", - "time 0.3.22", + "time 0.3.23", ] [[package]] name = "gix-diff" -version = "0.29.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "644a0f2768bc42d7a69289ada80c9e15c589caefc6a315d2307202df83ed1186" +checksum = "aaf5d9b9b521b284ebe53ee69eee33341835ec70edc314f36b2100ea81396121" dependencies = [ "gix-hash", "gix-object", @@ -2061,9 +2089,9 @@ dependencies = [ [[package]] name = "gix-discover" -version = "0.18.1" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a6b61363e63e7cdaa3e6f96acb0257ebdb3d8883e21eba5930c99f07f0a5fc0" +checksum = "272aad20dc63dedba76615373dd8885fb5aebe4795e5b5b0aa2a24e63c82085c" dependencies = [ "bstr", "dunce", @@ -2076,13 +2104,14 @@ dependencies = [ [[package]] name = "gix-features" -version = "0.29.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf69b0f5c701cc3ae22d3204b671907668f6437ca88862d355eaf9bc47a4f897" +checksum = "06142d8cff5d17509399b04052b64d2f9b3a311d5cff0b1a32b220f62cd0d595" dependencies = [ "crc32fast", "flate2", "gix-hash", + "gix-trace", "libc", "once_cell", "prodash", @@ -2093,20 +2122,20 @@ dependencies = [ [[package]] name = "gix-fs" -version = "0.1.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b37a1832f691fdc09910bd267f9a2e413737c1f9ec68c6e31f9e802616278a9" +checksum = "bb15956bc0256594c62a2399fcf6958a02a11724217eddfdc2b49b21b6292496" dependencies = [ "gix-features", ] [[package]] name = "gix-glob" -version = "0.7.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c07c98204529ac3f24b34754540a852593d2a4c7349008df389240266627a72a" +checksum = "c18bdff83143d61e7d60da6183b87542a870d026b2a2d0b30170b8e9c0cd321a" dependencies = [ - "bitflags 2.3.1", + "bitflags 2.3.3", "bstr", "gix-features", "gix-path", @@ -2114,9 +2143,9 @@ dependencies = [ [[package]] name = "gix-hash" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee181c85d3955f54c4426e6bfaeeada4428692e1a39b8788c2ac7785fc301dd8" +checksum = "a0dd58cdbe7ffa4032fc111864c80d5f8cecd9a2c9736c97ae7e5be834188272" dependencies = [ "hex", "thiserror", @@ -2124,20 +2153,20 @@ dependencies = [ [[package]] name = "gix-hashtable" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd259bd0d96e6153e357a8cdaca76c48e103fd34208b6c0ce77b1ad995834bd2" +checksum = "9e133bc56d938eaec1c675af7c681a51de9662b0ada779f45607b967a10da77a" dependencies = [ "gix-hash", - "hashbrown 0.13.2", + "hashbrown 0.14.0", "parking_lot", ] [[package]] name = "gix-ignore" -version = "0.2.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba205b6df563e2906768bb22834c82eb46c5fdfcd86ba2c347270bc8309a05b2" +checksum = "ca801f2d0535210f77b33e2c067d565aedecacc82f1b3dbce26da1388ebc4634" dependencies = [ "bstr", "gix-glob", @@ -2147,11 +2176,11 @@ dependencies = [ [[package]] name = "gix-index" -version = "0.16.1" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f39c1ccc8f1912cbbd5191efc28dbc5f0d0598042aa56bc09427b7c34efab3ba" +checksum = "68099abdf6ee50ae3c897e8b05de96871cbe54d52a37cdf559101f911b883562" dependencies = [ - "bitflags 2.3.1", + "bitflags 2.3.3", "bstr", "btoi", "filetime", @@ -2169,9 +2198,9 @@ dependencies = [ [[package]] name = "gix-lock" -version = "5.0.1" +version = "7.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c693d7f05730fa74a7c467150adc7cea393518410c65f0672f80226b8111555" +checksum = "714bcb13627995ac33716e9c5e4d25612b19947845395f64d2a9cbe6007728e4" dependencies = [ "gix-tempfile", "gix-utils", @@ -2180,24 +2209,42 @@ dependencies = [ [[package]] name = "gix-mailmap" -version = "0.12.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8856cec3bdc3610c06970d28b6cb20a0c6621621cf9a8ec48cbd23f2630f362" +checksum = "1787e3c37fc43b1f7c0e3be6196c6837b3ba5f869190dfeaa444b816f0a7f34b" dependencies = [ "bstr", "gix-actor", + "gix-date", + "thiserror", +] + +[[package]] +name = "gix-negotiate" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7bce64d4452dd609f44d04b14b29da2e0ad2c45fcdf4ce1472a5f5f8ec21c2" +dependencies = [ + "bitflags 2.3.3", + "gix-commitgraph", + "gix-date", + "gix-hash", + "gix-object", + "gix-revwalk", + "smallvec", "thiserror", ] [[package]] name = "gix-object" -version = "0.29.2" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d96bd620fd08accdd37f70b2183cfa0b001b4f1c6ade8b7f6e15cb3d9e261ce" +checksum = "a953f3d7ffad16734aa3ab1d05807972c80e339d1bd9dde03e0198716b99e2a6" dependencies = [ "bstr", "btoi", "gix-actor", + "gix-date", "gix-features", "gix-hash", "gix-validate", @@ -2210,11 +2257,12 @@ dependencies = [ [[package]] name = "gix-odb" -version = "0.45.0" +version = "0.49.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bca2f324aa67672b6d0f2c0fa93f96eb6a7029d260e4c1df5dce3c015f5e5add" +checksum = "f6418cff00ecc2713b58c8e04bff30dda808fbba1a080e7248b299d069894a01" dependencies = [ "arc-swap", + "gix-date", "gix-features", "gix-hash", "gix-object", @@ -2228,9 +2276,9 @@ dependencies = [ [[package]] name = "gix-pack" -version = "0.35.0" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "164a515900a83257ae4aa80e741655bee7a2e39113fb535d7a5ac623b445ff20" +checksum = "414935138d90043ea5898de7a93f02c2558e52652492719470e203ef26a8fd0a" dependencies = [ "clru", "gix-chunk", @@ -2250,11 +2298,12 @@ dependencies = [ [[package]] name = "gix-path" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1226f2e50adeb4d76c754c1856c06f13a24cad1624801653fbf09b869e5b808" +checksum = "dfca182d2575ded2ed38280f1ebf75cd5d3790b77e0872de07854cf085821fbe" dependencies = [ "bstr", + "gix-trace", "home", "once_cell", "thiserror", @@ -2262,22 +2311,22 @@ dependencies = [ [[package]] name = "gix-prompt" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e15fe57fa48572b7d3bf465d6a2a0351cd3c55cba74fd5f0b9c23689f9c1a31e" +checksum = "8dfd363fd89a40c1e7bff9c9c1b136cd2002480f724b0c627c1bc771cd5480ec" dependencies = [ "gix-command", "gix-config-value", "parking_lot", - "rustix 0.37.19", + "rustix 0.37.23", "thiserror", ] [[package]] name = "gix-quote" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29d59489bff95b06dcdabe763b7266d3dc0a628cac1ac1caf65a7ca0a43eeae0" +checksum = "3874de636c2526de26a3405b8024b23ef1a327bebf4845d770d00d48700b6a40" dependencies = [ "bstr", "btoi", @@ -2286,11 +2335,12 @@ dependencies = [ [[package]] name = "gix-ref" -version = "0.29.1" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e03989e9d49954368e1b526578230fc7189d1634acdfbe79e9ba1de717e15d5" +checksum = "39453f4e5f23cddc2e6e4cca2ba20adfdbec29379e3ca829714dfe98ae068ccd" dependencies = [ "gix-actor", + "gix-date", "gix-features", "gix-fs", "gix-hash", @@ -2306,9 +2356,9 @@ dependencies = [ [[package]] name = "gix-refspec" -version = "0.10.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6ea733820df67e4cd7797deb12727905824d8f5b7c59d943c456d314475892" +checksum = "b8e76ff1f82fba295a121e31ab02f69642994e532c45c0c899aa393f4b740302" dependencies = [ "bstr", "gix-hash", @@ -2320,25 +2370,41 @@ dependencies = [ [[package]] name = "gix-revision" -version = "0.13.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "810f35e9afeccca999d5d348b239f9c162353127d2e13ff3240e31b919e35476" +checksum = "237428a7d3978e8572964e1e45d984027c2acc94df47e594baa6c4b0da7c9922" dependencies = [ "bstr", "gix-date", "gix-hash", "gix-hashtable", "gix-object", + "gix-revwalk", + "thiserror", +] + +[[package]] +name = "gix-revwalk" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028d50fcaf8326a8f79a359490d9ca9fb4e2b51ac9ac86503560d0bcc888d2eb" +dependencies = [ + "gix-commitgraph", + "gix-date", + "gix-hash", + "gix-hashtable", + "gix-object", + "smallvec", "thiserror", ] [[package]] name = "gix-sec" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2b7b38b766eb95dcc5350a9c450030b69892c0902fa35f4a6d0809273bd9dae" +checksum = "ede298863db2a0574a14070991710551e76d1f47c9783b62d4fcbca17f56371c" dependencies = [ - "bitflags 2.3.1", + "bitflags 2.3.3", "gix-path", "libc", "windows", @@ -2346,9 +2412,9 @@ dependencies = [ [[package]] name = "gix-tempfile" -version = "5.0.3" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71a0d32f34e71e86586124225caefd78dabc605d0486de580d717653addf182" +checksum = "4fac8310c17406ea619af72f42ee46dac795110f68f41b4f4fa231b69889c6a2" dependencies = [ "gix-fs", "libc", @@ -2359,23 +2425,33 @@ dependencies = [ "tempfile", ] +[[package]] +name = "gix-trace" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "103eac621617be3ebe0605c9065ca51a223279a23218aaf67d10daa6e452f663" + [[package]] name = "gix-traverse" -version = "0.25.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5be1e807f288c33bb005075111886cceb43ed8a167b3182a0f62c186e2a0dd1" +checksum = "e3cdfd54598db4fae57d5ae6f52958422b2d13382d2745796bfe5c8015ffa86e" dependencies = [ + "gix-commitgraph", + "gix-date", "gix-hash", "gix-hashtable", "gix-object", + "gix-revwalk", + "smallvec", "thiserror", ] [[package]] name = "gix-url" -version = "0.18.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc77f89054297cc81491e31f1bab4027e554b5ef742a44bd7035db9a0f78b76" +checksum = "beaede6dbc83f408b19adfd95bb52f1dbf01fb8862c3faf6c6243e2e67fcdfa1" dependencies = [ "bstr", "gix-features", @@ -2387,18 +2463,18 @@ dependencies = [ [[package]] name = "gix-utils" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbcfcb150c7ef553d76988467d223254045bdcad0dc6724890f32fbe96415da5" +checksum = "7058c94f4164fcf5b8457d35f6d8f6e1007f9f7f938c9c7684a7e01d23c6ddde" dependencies = [ - "fastrand", + "fastrand 2.0.0", ] [[package]] name = "gix-validate" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ea5845b506c7728b9d89f4227cc369a5fc5a1d5b26c3add0f0d323413a3a60" +checksum = "8d092b594c8af00a3a31fe526d363ee8a51a6f29d8496cdb991ed2f01ec0ec13" dependencies = [ "bstr", "thiserror", @@ -2406,9 +2482,9 @@ dependencies = [ [[package]] name = "gix-worktree" -version = "0.17.1" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69eaff0ae973a9d37c40f02ae5ae50fa726c8fc2fd3ab79d0a19eb61975aafa" +checksum = "c1363b9aa66b9e14412ac04e1f759827203f491729d92172535a8ce6cde02efa" dependencies = [ "bstr", "filetime", @@ -2444,9 +2520,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.19" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d357c7ae988e7d2182f7d7871d0b963962420b0678b0997ce7de72001aeab782" +checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" dependencies = [ "bytes", "fnv", @@ -2454,7 +2530,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 1.9.3", "slab", "tokio", "tokio-util", @@ -2484,9 +2560,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.13.2" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" [[package]] name = "hdrhistogram" @@ -2543,18 +2619,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" -dependencies = [ - "libc", -] - -[[package]] -name = "hermit-abi" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" [[package]] name = "hex" @@ -2642,9 +2709,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.26" +version = "0.14.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" dependencies = [ "bytes", "futures-channel", @@ -2759,6 +2826,16 @@ dependencies = [ "serde", ] +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown 0.14.0", +] + [[package]] name = "inquire" version = "0.6.2" @@ -2812,7 +2889,7 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ - "hermit-abi 0.3.1", + "hermit-abi 0.3.2", "libc", "windows-sys 0.48.0", ] @@ -2823,6 +2900,7 @@ version = "2.0.0-pre-rc.16" dependencies = [ "async-trait", "color-eyre", + "dashmap", "displaydoc", "eyre", "futures", @@ -2938,6 +3016,7 @@ dependencies = [ "iroha_data_model", "iroha_primitives", "json5", + "once_cell", "path-absolutize", "proptest", "serde", @@ -3395,13 +3474,12 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ - "hermit-abi 0.3.1", - "io-lifetimes 1.0.11", - "rustix 0.37.19", + "hermit-abi 0.3.2", + "rustix 0.38.4", "windows-sys 0.48.0", ] @@ -3422,9 +3500,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "ittapi-rs" @@ -3446,9 +3524,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.63" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] @@ -3480,7 +3558,7 @@ dependencies = [ name = "kagami" version = "2.0.0-pre-rc.16" dependencies = [ - "clap 4.3.11", + "clap 4.3.15", "color-eyre", "derive_more", "duct", @@ -3528,7 +3606,7 @@ dependencies = [ name = "kura_inspector" version = "2.0.0-pre-rc.16" dependencies = [ - "clap 4.3.11", + "clap 4.3.15", "iroha_core", "iroha_data_model", "iroha_version", @@ -3548,9 +3626,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.146" +version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "libm" @@ -3560,9 +3638,9 @@ checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" [[package]] name = "link-cplusplus" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" +checksum = "9d240c6f7e1ba3a28b0249f774e6a9dd0175054b52dfbb61b16eb8505c3785c9" dependencies = [ "cc", ] @@ -3579,6 +3657,12 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" +[[package]] +name = "linux-raw-sys" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" + [[package]] name = "lock_api" version = "0.4.10" @@ -3591,9 +3675,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.18" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de" +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" [[package]] name = "mach" @@ -3610,7 +3694,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" dependencies = [ - "regex-automata", + "regex-automata 0.1.10", ] [[package]] @@ -3631,14 +3715,14 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffc89ccdc6e10d6907450f753537ebc5c5d3460d2e4e62ea74bd571db62c0f9e" dependencies = [ - "rustix 0.37.19", + "rustix 0.37.23", ] [[package]] name = "memmap2" -version = "0.5.10" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +checksum = "f49388d20533534cd19360ad3d6a7dadc885944aa802ba3995040c5ec11288c6" dependencies = [ "libc", ] @@ -3654,9 +3738,9 @@ dependencies = [ [[package]] name = "memoffset" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" dependencies = [ "autocfg 1.1.0", ] @@ -3683,15 +3767,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "miniz_oxide" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" -dependencies = [ - "adler", -] - [[package]] name = "miniz_oxide" version = "0.7.1" @@ -3802,11 +3877,11 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.2.6", + "hermit-abi 0.3.2", "libc", ] @@ -3827,15 +3902,15 @@ checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424" dependencies = [ "crc32fast", "hashbrown 0.11.2", - "indexmap", + "indexmap 1.9.3", "memchr", ] [[package]] name = "object" -version = "0.30.4" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03b4680b86d9cfafba8fc491dc9b6df26b68cf40e9e6cd73909194759a63c385" +checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" dependencies = [ "memchr", ] @@ -3866,9 +3941,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" -version = "0.10.54" +version = "0.10.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69b3f656a17a6cbc115b5c7a40c616947d213ba182135b014d6051b73ab6f019" +checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d" dependencies = [ "bitflags 1.3.2", "cfg-if", @@ -3887,7 +3962,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.26", ] [[package]] @@ -3907,9 +3982,9 @@ dependencies = [ [[package]] name = "openssl-sys" -version = "0.9.88" +version = "0.9.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ce0f250f34a308dcfdbb351f511359857d4ed2134ba715a4eadd46e1ffd617" +checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6" dependencies = [ "cc", "libc", @@ -3930,9 +4005,9 @@ dependencies = [ [[package]] name = "os_str_bytes" -version = "6.5.0" +version = "6.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" +checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac" [[package]] name = "overload" @@ -3951,9 +4026,9 @@ dependencies = [ [[package]] name = "parity-scale-codec" -version = "3.5.0" +version = "3.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ddb756ca205bd108aee3c62c6d3c994e1df84a59b9d6d4a5ea42ee1fd5a9a28" +checksum = "dd8e946cc0cc711189c0b0249fb8b599cbeeab9784d83c415719368bb8d4ac64" dependencies = [ "arrayvec", "bitvec", @@ -3965,9 +4040,9 @@ dependencies = [ [[package]] name = "parity-scale-codec-derive" -version = "3.1.4" +version = "3.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b26a931f824dd4eca30b3e43bb4f31cd5f0d3a403c5f5ff27106b805bfde7b" +checksum = "2a296c3079b5fefbc499e1de58dc26c09b1b9a5952d26694ee89f04a43ebbb3e" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -3979,7 +4054,7 @@ dependencies = [ name = "parity_scale_decoder" version = "2.0.0-pre-rc.16" dependencies = [ - "clap 4.3.11", + "clap 4.3.15", "colored", "eyre", "iroha_crypto", @@ -4017,14 +4092,14 @@ dependencies = [ "redox_syscall 0.3.5", "smallvec", "thread-id", - "windows-targets 0.48.0", + "windows-targets 0.48.1", ] [[package]] name = "paste" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] name = "path-absolutize" @@ -4058,9 +4133,9 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pest" -version = "2.6.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e68e84bfb01f0507134eac1e9b410a12ba379d064eab48c50ba4ce329a527b70" +checksum = "0d2d1d55045829d65aad9d389139882ad623b33b904e7c9f1b10c5b8927298e5" dependencies = [ "thiserror", "ucd-trie", @@ -4068,9 +4143,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.6.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b79d4c71c865a25a4322296122e3924d30bc8ee0834c8bfc8b95f7f054afbfb" +checksum = "5f94bca7e7a599d89dea5dfa309e217e7906c3c007fb9c3299c40b10d6a315d3" dependencies = [ "pest", "pest_generator", @@ -4078,26 +4153,26 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.6.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c435bf1076437b851ebc8edc3a18442796b30f1728ffea6262d59bbe28b077e" +checksum = "99d490fe7e8556575ff6911e45567ab95e71617f43781e5c05490dc8d75c965c" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.26", ] [[package]] name = "pest_meta" -version = "2.6.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "745a452f8eb71e39ffd8ee32b3c5f51d03845f99786fa9b68db6ff509c505411" +checksum = "2674c66ebb4b4d9036012091b537aae5878970d6999f81a265034d85b136b341" dependencies = [ "once_cell", "pest", - "sha2 0.10.6", + "sha2 0.10.7", ] [[package]] @@ -4107,34 +4182,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4" dependencies = [ "fixedbitset", - "indexmap", + "indexmap 1.9.3", ] [[package]] name = "pin-project" -version = "1.1.0" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c95a7476719eab1e366eaf73d0260af3021184f18177925b07f54b30089ceead" +checksum = "030ad2bc4db10a8944cb0d837f158bdfec4d4a4873ab701a95046770d11f8842" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.0" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07" +checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.26", ] [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" [[package]] name = "pin-utils" @@ -4160,9 +4235,9 @@ checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "plotters" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2538b639e642295546c50fcd545198c9d64ee2a38620a628724a3b266d5fbf97" +checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" dependencies = [ "num-traits", "plotters-backend", @@ -4173,15 +4248,15 @@ dependencies = [ [[package]] name = "plotters-backend" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142" +checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" [[package]] name = "plotters-svg" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a81d2759aae1dae668f783c308bc5c8ebd191ff4184aaa1b37f65a6ae5a56f" +checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" dependencies = [ "plotters-backend", ] @@ -4249,18 +4324,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.63" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] [[package]] name = "prodash" -version = "23.1.2" +version = "25.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9516b775656bc3e8985e19cd4b8c0c0de045095074e453d2c0a513b5f978392d" +checksum = "c236e70b7f9b9ea00d33c69f63ec1ae6e9ae96118923cd37bd4e9c7396f0b107" [[package]] name = "prometheus" @@ -4345,9 +4420,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.28" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" +checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0" dependencies = [ "proc-macro2", ] @@ -4594,13 +4669,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.8.4" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" +checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.7.2", + "regex-automata 0.3.3", + "regex-syntax 0.7.4", ] [[package]] @@ -4612,6 +4688,17 @@ dependencies = [ "regex-syntax 0.6.29", ] +[[package]] +name = "regex-automata" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.7.4", +] + [[package]] name = "regex-syntax" version = "0.6.29" @@ -4620,9 +4707,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" [[package]] name = "region" @@ -4650,9 +4737,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.35.13" +version = "0.35.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727a1a6d65f786ec22df8a81ca3121107f235970dc1705ed681d3e6e8b9cd5f9" +checksum = "6380889b07a03b5ecf1d44dc9ede6fd2145d84b502a2a9ca0b03c48e0cc3220f" dependencies = [ "bitflags 1.3.2", "errno 0.2.8", @@ -4664,9 +4751,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.19" +version = "0.37.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" +checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" dependencies = [ "bitflags 1.3.2", "errno 0.3.1", @@ -4676,20 +4763,33 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "rustix" +version = "0.38.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5" +dependencies = [ + "bitflags 2.3.3", + "errno 0.3.1", + "libc", + "linux-raw-sys 0.4.3", + "windows-sys 0.48.0", +] + [[package]] name = "rustls-pemfile" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" +checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" dependencies = [ "base64 0.21.2", ] [[package]] name = "rustversion" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "rusty-fork" @@ -4705,9 +4805,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "same-file" @@ -4720,11 +4820,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" dependencies = [ - "windows-sys 0.42.0", + "windows-sys 0.48.0", ] [[package]] @@ -4735,15 +4835,15 @@ checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "scopeguard" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "scratch" -version = "1.0.5" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" +checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152" [[package]] name = "sealed" @@ -4754,7 +4854,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.26", ] [[package]] @@ -4802,18 +4902,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.163" +version = "1.0.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" +checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9" dependencies = [ "serde_derive", ] [[package]] name = "serde_bytes" -version = "0.11.9" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416bda436f9aab92e02c8e10d49a15ddd339cea90b6e340fe51ed97abb548294" +checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff" dependencies = [ "serde", ] @@ -4830,20 +4930,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.163" +version = "1.0.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" +checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.26", ] [[package]] name = "serde_json" -version = "1.0.99" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46266871c240a00b8f503b877622fe33430b3c7d963bdc0f2adc511e54a1eae3" +checksum = "d03b412469450d4404fe8499a268edd7f8b79fecb074b0d812ad64ca21f4031b" dependencies = [ "itoa", "ryu", @@ -4881,16 +4981,16 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.26", ] [[package]] name = "serde_yaml" -version = "0.9.21" +version = "0.9.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9d684e3ec7de3bf5466b32bd75303ac16f0736426e5a4e0d6e489559ce1249c" +checksum = "bd5f51e3fdb5b9cdd1577e1cb7a733474191b1aca6a72c2e50913241632c1180" dependencies = [ - "indexmap", + "indexmap 2.0.0", "itoa", "ryu", "serde", @@ -4979,9 +5079,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" dependencies = [ "cfg-if", "cpufeatures", @@ -4990,12 +5090,15 @@ dependencies = [ [[package]] name = "sha256" -version = "1.1.4" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08a975c1bc0941703000eaf232c4d8ce188d8d5408d6344b6b2c8c6262772828" +checksum = "f894f93906f2a96d3a75a60362f790e71247c588d9f87e97796db1e94bcb808e" dependencies = [ + "async-trait", + "bytes", "hex", - "sha2 0.10.6", + "sha2 0.10.7", + "tokio", ] [[package]] @@ -5050,9 +5153,9 @@ checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" [[package]] name = "signal-hook" -version = "0.3.15" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9" +checksum = "b824b6e687aff278cdbf3b36f07aa52d4bd4099699324d5da86a2ebce3aa00b3" dependencies = [ "libc", "signal-hook-registry", @@ -5115,9 +5218,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" dependencies = [ "serde", ] @@ -5255,9 +5358,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.23" +version = "2.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59fb7d6d8281a51045d62b8eb3a7d1ce347b76f312af50cd3dc0af39c87c1737" +checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970" dependencies = [ "proc-macro2", "quote", @@ -5290,9 +5393,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "target-lexicon" -version = "0.12.7" +version = "0.12.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd1ba337640d60c3e96bc6f0638a939b9c9a7f2c316a1598c279828b3d1dc8c5" +checksum = "df8e77cb757a61f51b947ec4a7e3646efd825b73561db1c232a8ccb639e611a0" [[package]] name = "tempfile" @@ -5302,9 +5405,9 @@ checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" dependencies = [ "autocfg 1.1.0", "cfg-if", - "fastrand", + "fastrand 1.9.0", "redox_syscall 0.3.5", - "rustix 0.37.19", + "rustix 0.37.23", "windows-sys 0.48.0", ] @@ -5327,6 +5430,7 @@ dependencies = [ "iroha_client", "iroha_config", "iroha_core", + "iroha_crypto", "iroha_data_model", "iroha_genesis", "iroha_logger", @@ -5354,22 +5458,22 @@ checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" -version = "1.0.40" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +checksum = "a35fc5b8971143ca348fa6df4f024d4d55264f3468c71ad1c2f365b0a4d58c42" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.40" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.26", ] [[package]] @@ -5412,9 +5516,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.22" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd" +checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446" dependencies = [ "itoa", "libc", @@ -5432,9 +5536,9 @@ checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" +checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4" dependencies = [ "time-core", ] @@ -5466,11 +5570,12 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.28.2" +version = "1.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2" +checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da" dependencies = [ "autocfg 1.1.0", + "backtrace", "bytes", "libc", "mio", @@ -5501,7 +5606,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.26", ] [[package]] @@ -5588,17 +5693,17 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a76a9312f5ba4c2dec6b9161fdf25d87ad8a09256ccea5a556fef03c706a10f" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" [[package]] name = "toml_edit" -version = "0.19.10" +version = "0.19.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380d56e8670370eee6566b0bfd4265f65b3f432e8c6d85623f728d4fa31f739" +checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" dependencies = [ - "indexmap", + "indexmap 2.0.0", "toml_datetime", "winnow", ] @@ -5639,7 +5744,7 @@ checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ "futures-core", "futures-util", - "indexmap", + "indexmap 1.9.3", "pin-project", "pin-project-lite", "rand 0.8.5", @@ -5678,27 +5783,27 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.24" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" +checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.26", ] [[package]] name = "tracing-bunyan-formatter" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25a348912d4e90923cb93343691d3be97e3409607363706c400fc935bb032fb0" +checksum = "464ce79ea7f689ca56d90a9c5563e803a4b61b2695e789205644ed8e8101e6bf" dependencies = [ "ahash 0.8.3", "gethostname", "log", "serde", "serde_json", - "time 0.3.22", + "time 0.3.23", "tracing", "tracing-core", "tracing-log", @@ -5782,9 +5887,9 @@ checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "trybuild" -version = "1.0.80" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501dbdbb99861e4ab6b60eb6a7493956a9defb644fd034bc4a5ef27c693c8a3a" +checksum = "04366e99ff743345622cd00af2af01d711dc2d1ef59250d7347698d21b546729" dependencies = [ "basic-toml", "glob", @@ -5861,9 +5966,9 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "ucd-trie" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" [[package]] name = "unarray" @@ -5894,9 +5999,9 @@ checksum = "98e90c70c9f0d4d1ee6d0a7d04aa06cb9bbd53d8cfbdd62a0269a7c2eb640552" [[package]] name = "unicode-ident" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[package]] name = "unicode-normalization" @@ -5946,9 +6051,9 @@ dependencies = [ [[package]] name = "unsafe-libyaml" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1865806a559042e51ab5414598446a5871b561d21b6764f2eabb0dd481d880a6" +checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa" [[package]] name = "url" @@ -6033,14 +6138,14 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "vergen" -version = "8.2.1" +version = "8.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b3c89c2c7e50f33e4d35527e5bf9c11d6d132226dbbd1753f0fbe9f19ef88c6" +checksum = "bbc5ad0d9d26b2c49a5ab7da76c3e79d3ee37e7821799f8223fcb8f2f391a2e7" dependencies = [ "anyhow", "gix", "rustversion", - "time 0.3.22", + "time 0.3.23", ] [[package]] @@ -6070,11 +6175,10 @@ dependencies = [ [[package]] name = "want" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ - "log", "try-lock", ] @@ -6129,9 +6233,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -6139,24 +6243,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.26", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -6164,28 +6268,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.26", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "wasm-encoder" -version = "0.29.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18c41dbd92eaebf3612a39be316540b8377c871cb9bde6b064af962984912881" +checksum = "06a3d1b4a575ffb873679402b2aedb3117555eb65c27b1b86c8a91e574bc2a2a" dependencies = [ "leb128", ] @@ -6236,7 +6340,7 @@ version = "0.86.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bcbfe95447da2aa7ff171857fc8427513eb57c75a729bb190e974dc695e8f5c" dependencies = [ - "indexmap", + "indexmap 1.9.3", ] [[package]] @@ -6250,7 +6354,7 @@ dependencies = [ "backtrace", "bincode", "cfg-if", - "indexmap", + "indexmap 1.9.3", "lazy_static", "libc", "log", @@ -6285,7 +6389,7 @@ dependencies = [ "directories-next", "file-per-thread-logger", "log", - "rustix 0.35.13", + "rustix 0.35.14", "serde", "sha2 0.9.9", "toml", @@ -6324,7 +6428,7 @@ dependencies = [ "anyhow", "cranelift-entity", "gimli 0.26.2", - "indexmap", + "indexmap 1.9.3", "log", "more-asserts", "object 0.28.4", @@ -6343,7 +6447,7 @@ checksum = "2f6aba0b317746e8213d1f36a4c51974e66e69c1f05bfc09ed29b4d4bda290eb" dependencies = [ "cc", "cfg-if", - "rustix 0.35.13", + "rustix 0.35.14", "windows-sys 0.36.1", ] @@ -6364,7 +6468,7 @@ dependencies = [ "object 0.28.4", "region", "rustc-demangle", - "rustix 0.35.13", + "rustix 0.35.14", "serde", "target-lexicon", "thiserror", @@ -6382,7 +6486,7 @@ checksum = "55e23273fddce8cab149a0743c46932bf4910268641397ed86b46854b089f38f" dependencies = [ "lazy_static", "object 0.28.4", - "rustix 0.35.13", + "rustix 0.35.14", ] [[package]] @@ -6395,7 +6499,7 @@ dependencies = [ "backtrace", "cc", "cfg-if", - "indexmap", + "indexmap 1.9.3", "libc", "log", "mach", @@ -6404,7 +6508,7 @@ dependencies = [ "more-asserts", "rand 0.8.5", "region", - "rustix 0.35.13", + "rustix 0.35.14", "thiserror", "wasmtime-environ", "wasmtime-fiber", @@ -6426,9 +6530,9 @@ dependencies = [ [[package]] name = "wast" -version = "60.0.0" +version = "62.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd06cc744b536e30387e72a48fdd492105b9c938bb4f415c39c616a7a0a697ad" +checksum = "c7f7ee878019d69436895f019b65f62c33da63595d8e857cbdc87c13ecb29a32" dependencies = [ "leb128", "memchr", @@ -6438,18 +6542,18 @@ dependencies = [ [[package]] name = "wat" -version = "1.0.66" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5abe520f0ab205366e9ac7d3e6b2fc71de44e32a2b58f2ec871b6b575bdcea3b" +checksum = "295572bf24aa5b685a971a83ad3e8b6e684aaad8a9be24bc7bf59bed84cc1c08" dependencies = [ "wast", ] [[package]] name = "web-sys" -version = "0.3.63" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", @@ -6508,7 +6612,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows-targets 0.48.0", + "windows-targets 0.48.1", ] [[package]] @@ -6554,7 +6658,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.48.0", + "windows-targets 0.48.1", ] [[package]] @@ -6574,9 +6678,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.48.0" +version = "0.48.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" dependencies = [ "windows_aarch64_gnullvm 0.48.0", "windows_aarch64_msvc 0.48.0", @@ -6703,9 +6807,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "winnow" -version = "0.4.6" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699" +checksum = "81fac9742fd1ad1bd9643b991319f72dd031016d44b77039a26977eb667141e7" dependencies = [ "memchr", ] @@ -6747,7 +6851,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.26", ] [[package]] diff --git a/cli/Cargo.toml b/cli/Cargo.toml index fc07c845864..328ebcae528 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -67,6 +67,7 @@ thiserror = { workspace = true } displaydoc = { workspace = true } tokio = { workspace = true, features = ["sync", "time", "rt", "io-util", "rt-multi-thread", "macros", "fs", "signal"] } warp = { workspace = true, features = ["multipart", "websocket"] } +dashmap = "5.4.0" serial_test = "0.8.0" once_cell = { workspace = true } owo-colors = { workspace = true, features = ["supports-colors"] } diff --git a/cli/src/stream.rs b/cli/src/stream.rs index ade044472b7..340923c9426 100644 --- a/cli/src/stream.rs +++ b/cli/src/stream.rs @@ -13,7 +13,7 @@ const TIMEOUT: Duration = Duration::from_millis(10_000); const TIMEOUT: Duration = Duration::from_millis(1000); /// Error type with generic for actual Stream/Sink error type -#[derive(thiserror::Error, displaydoc::Display, Debug)] +#[derive(Debug, displaydoc::Display, thiserror::Error)] #[ignore_extra_doc_attributes] pub enum Error where diff --git a/cli/src/torii/cursor.rs b/cli/src/torii/cursor.rs new file mode 100644 index 00000000000..aae3713314c --- /dev/null +++ b/cli/src/torii/cursor.rs @@ -0,0 +1,79 @@ +use std::num::NonZeroUsize; + +use iroha_data_model::query::ForwardCursor; + +use crate::torii::{Error, Result}; + +pub trait Batch: IntoIterator + Sized { + fn batched(self, fetch_size: NonZeroUsize) -> Batched; +} + +impl Batch for I { + fn batched(self, batch_size: NonZeroUsize) -> Batched { + Batched { + iter: self.into_iter(), + batch_size, + cursor: ForwardCursor::default(), + } + } +} + +/// Paginated [`Iterator`]. +/// Not recommended to use directly, only use in iterator chains. +#[derive(Debug)] +pub struct Batched { + iter: I::IntoIter, + batch_size: NonZeroUsize, + cursor: ForwardCursor, +} + +impl> Batched { + pub(crate) fn next_batch(&mut self, cursor: ForwardCursor) -> Result<(I, ForwardCursor)> { + if cursor != self.cursor { + return Err(Error::UnknownCursor); + } + + let mut batch_size = 0; + let batch: I = self + .iter + .by_ref() + .inspect(|_| batch_size += 1) + .take(self.batch_size.get()) + .collect(); + + self.cursor.cursor = if let Some(cursor) = self.cursor.cursor { + if batch_size >= self.batch_size.get() { + let batch_size = self + .batch_size + .get() + .try_into() + .expect("usize should fit in u64"); + Some( + cursor + .checked_add(batch_size) + .expect("Cursor size should never reach the platform limit"), + ) + } else { + None + } + } else if batch_size >= self.batch_size.get() { + Some(self.batch_size.try_into().expect("usize should fit in u64")) + } else { + None + }; + + Ok((batch, self.cursor)) + } + + pub fn is_depleted(&self) -> bool { + self.cursor.cursor.is_none() + } +} + +impl Iterator for Batched { + type Item = I::Item; + + fn next(&mut self) -> Option { + self.iter.next() + } +} diff --git a/cli/src/torii/mod.rs b/cli/src/torii/mod.rs index 5490a31cf75..c98ba158b1d 100644 --- a/cli/src/torii/mod.rs +++ b/cli/src/torii/mod.rs @@ -7,8 +7,10 @@ use std::{ fmt::{Debug, Write as _}, net::ToSocketAddrs, sync::Arc, + time::{Duration, Instant}, }; +use dashmap::DashMap; use futures::{stream::FuturesUnordered, StreamExt}; use iroha_core::{ kura::Kura, @@ -17,8 +19,8 @@ use iroha_core::{ sumeragi::SumeragiHandle, EventsSender, }; -use thiserror::Error; -use tokio::sync::Notify; +use iroha_data_model::Value; +use tokio::{sync::Notify, time::sleep}; use utils::*; use warp::{ http::StatusCode, @@ -27,10 +29,44 @@ use warp::{ Filter as _, Reply, }; +use self::cursor::Batched; + #[macro_use] pub(crate) mod utils; +mod cursor; mod pagination; -pub mod routing; +mod routing; + +type LiveQuery = Batched>; + +#[derive(Default)] +struct LiveQueryStore { + queries: DashMap, (LiveQuery, Instant)>, +} + +impl LiveQueryStore { + fn insert(&self, request: Vec, live_query: LiveQuery) { + self.queries.insert(request, (live_query, Instant::now())); + } + + fn remove(&self, request: &Vec) -> Option<(Vec, LiveQuery)> { + self.queries + .remove(request) + .map(|(query_id, (query, _))| (query_id, query)) + } + + // TODO: Add notifier channel to enable graceful shutdown + fn expired_query_cleanup(self: Arc, idle_time: Duration) -> tokio::task::JoinHandle<()> { + tokio::task::spawn(async move { + loop { + sleep(idle_time).await; + + self.queries + .retain(|_, (_, last_access_time)| last_access_time.elapsed() <= idle_time); + } + }) + } +} /// Main network handler and the only entrypoint of the Iroha. pub struct Torii { @@ -39,11 +75,12 @@ pub struct Torii { events: EventsSender, notify_shutdown: Arc, sumeragi: SumeragiHandle, + query_store: Arc, kura: Arc, } /// Torii errors. -#[derive(Debug, Error, displaydoc::Display)] +#[derive(Debug, thiserror::Error, displaydoc::Display)] pub enum Error { /// Failed to execute or validate query Query(#[from] iroha_data_model::ValidationFail), @@ -58,10 +95,12 @@ pub enum Error { #[cfg(feature = "telemetry")] /// Error while getting Prometheus metrics Prometheus(#[source] eyre::Report), + /// Error while resuming cursor + UnknownCursor, } /// Status code for query error response. -pub(crate) fn query_status_code(validation_error: &iroha_data_model::ValidationFail) -> StatusCode { +fn query_status_code(validation_error: &iroha_data_model::ValidationFail) -> StatusCode { use iroha_data_model::{ isi::error::InstructionExecutionError, query::error::QueryExecutionFail::*, ValidationFail::*, @@ -104,7 +143,9 @@ impl Error { use Error::*; match self { Query(e) => query_status_code(e), - AcceptTransaction(_) | ConfigurationReload(_) => StatusCode::BAD_REQUEST, + AcceptTransaction(_) | ConfigurationReload(_) | UnknownCursor => { + StatusCode::BAD_REQUEST + } Config(_) => StatusCode::NOT_FOUND, PushIntoQueue(err) => match **err { queue::Error::Full => StatusCode::INTERNAL_SERVER_ERROR, diff --git a/cli/src/torii/pagination.rs b/cli/src/torii/pagination.rs index 2a20556a686..e305edbaec1 100644 --- a/cli/src/torii/pagination.rs +++ b/cli/src/torii/pagination.rs @@ -1,4 +1,4 @@ -use iroha_data_model::prelude::*; +use iroha_data_model::query::Pagination; /// Describes a collection to which pagination can be applied. /// Implemented for the [`Iterator`] implementors. @@ -7,53 +7,46 @@ pub trait Paginate: Iterator + Sized { fn paginate(self, pagination: Pagination) -> Paginated; } -impl Paginate for I { +impl Paginate for I { fn paginate(self, pagination: Pagination) -> Paginated { - Paginated { - pagination, - iter: self, - } + Paginated::new(pagination, self) } } /// Paginated [`Iterator`]. /// Not recommended to use directly, only use in iterator chains. #[derive(Debug)] -pub struct Paginated { - pagination: Pagination, - iter: I, +pub struct Paginated(core::iter::Take>); + +impl Paginated { + fn new(pagination: Pagination, iter: I) -> Self { + Self( + iter.skip(pagination.start.map_or_else( + || 0, + |start| start.get().try_into().expect("U64 should fit into usize"), + )) + .take(pagination.limit.map_or_else( + || usize::MAX, + |limit| limit.get().try_into().expect("U32 should fit into usize"), + )), + ) + } } impl Iterator for Paginated { type Item = I::Item; fn next(&mut self) -> Option { - if let Some(limit) = self.pagination.limit.as_mut() { - if *limit == 0 { - return None; - } - - *limit -= 1 - } - - #[allow(clippy::option_if_let_else)] - // Required because of E0524. 2 closures with unique refs to self - if let Some(start) = self.pagination.start.take() { - self.iter - .nth(start.try_into().expect("u32 should always fit in usize")) - } else { - self.iter.next() - } + self.0.next() } } -/// Filter for warp which extracts pagination -pub fn paginate() -> impl warp::Filter + Copy { - warp::query() -} - #[cfg(test)] mod tests { + use std::num::{NonZeroU32, NonZeroU64}; + + use iroha_data_model::query::pagination::Pagination; + use super::*; #[test] @@ -61,7 +54,10 @@ mod tests { assert_eq!( vec![1_i32, 2_i32, 3_i32] .into_iter() - .paginate(Pagination::new(None, None)) + .paginate(Pagination { + limit: None, + start: None + }) .collect::>(), vec![1_i32, 2_i32, 3_i32] ) @@ -72,21 +68,20 @@ mod tests { assert_eq!( vec![1_i32, 2_i32, 3_i32] .into_iter() - .paginate(Pagination::new(Some(0), None)) - .collect::>(), - vec![1_i32, 2_i32, 3_i32] - ); - assert_eq!( - vec![1_i32, 2_i32, 3_i32] - .into_iter() - .paginate(Pagination::new(Some(1), None)) + .paginate(Pagination { + limit: None, + start: NonZeroU64::new(1) + }) .collect::>(), vec![2_i32, 3_i32] ); assert_eq!( vec![1_i32, 2_i32, 3_i32] .into_iter() - .paginate(Pagination::new(Some(3), None)) + .paginate(Pagination { + limit: None, + start: NonZeroU64::new(3) + }) .collect::>(), Vec::::new() ); @@ -97,21 +92,20 @@ mod tests { assert_eq!( vec![1_i32, 2_i32, 3_i32] .into_iter() - .paginate(Pagination::new(None, Some(0))) - .collect::>(), - Vec::::new() - ); - assert_eq!( - vec![1_i32, 2_i32, 3_i32] - .into_iter() - .paginate(Pagination::new(None, Some(2))) + .paginate(Pagination { + limit: NonZeroU32::new(2), + start: None + }) .collect::>(), vec![1_i32, 2_i32] ); assert_eq!( vec![1_i32, 2_i32, 3_i32] .into_iter() - .paginate(Pagination::new(None, Some(4))) + .paginate(Pagination { + limit: NonZeroU32::new(4), + start: None + }) .collect::>(), vec![1_i32, 2_i32, 3_i32] ); @@ -122,7 +116,10 @@ mod tests { assert_eq!( vec![1_i32, 2_i32, 3_i32] .into_iter() - .paginate(Pagination::new(Some(1), Some(1))) + .paginate(Pagination { + limit: NonZeroU32::new(1), + start: NonZeroU64::new(1), + }) .collect::>(), vec![2_i32] ) diff --git a/cli/src/torii/routing.rs b/cli/src/torii/routing.rs index fa3a0c1bc3c..01003c30360 100644 --- a/cli/src/torii/routing.rs +++ b/cli/src/torii/routing.rs @@ -5,8 +5,9 @@ // FIXME: This can't be fixed, because one trait in `warp` is private. #![allow(opaque_hidden_inferred_bound)] -use std::cmp::Ordering; +use std::{cmp::Ordering, num::NonZeroUsize}; +use cursor::Batch; use eyre::WrapErr; use futures::TryStreamExt; use iroha_config::{ @@ -28,22 +29,34 @@ use iroha_data_model::{ VersionedCommittedBlock, }, prelude::*, + query::{ForwardCursor, Pagination, Sorting}, }; #[cfg(feature = "telemetry")] use iroha_telemetry::metrics::Status; -use pagination::{paginate, Paginate}; +use pagination::Paginate; +use parity_scale_codec::Encode; use tokio::task; use super::*; use crate::stream::{Sink, Stream}; /// Filter for warp which extracts sorting -pub fn sorting() -> impl warp::Filter + Copy { +fn sorting() -> impl warp::Filter + Copy { + warp::query() +} + +/// Filter for warp which extracts cursor +fn cursor() -> impl warp::Filter + Copy { + warp::query() +} + +/// Filter for warp which extracts pagination +fn paginate() -> impl warp::Filter + Copy { warp::query() } #[iroha_futures::telemetry_future] -pub(crate) async fn handle_instructions( +async fn handle_instructions( queue: Arc, sumeragi: SumeragiHandle, transaction: VersionedSignedTransaction, @@ -67,30 +80,52 @@ pub(crate) async fn handle_instructions( } #[iroha_futures::telemetry_future] -pub(crate) async fn handle_queries( +async fn handle_queries( sumeragi: SumeragiHandle, - pagination: Pagination, - sorting: Sorting, + query_store: Arc, + fetch_size: NonZeroUsize, + request: VersionedSignedQuery, -) -> Result> { - let mut wsv = sumeragi.wsv_clone(); + sorting: Sorting, + pagination: Pagination, - let valid_request = ValidQueryRequest::validate(request, &mut wsv)?; - let result = valid_request.execute(&wsv).map_err(ValidationFail::from)?; + cursor: ForwardCursor, +) -> Result> { + let (query_id, mut live_query) = if cursor.cursor.is_some() { + let query_id = (&request, &sorting, &pagination).encode(); + query_store.remove(&query_id).ok_or(Error::UnknownCursor)? + } else { + let mut wsv = sumeragi.wsv_clone(); - let result = match result { - LazyValue::Value(value) => value, - LazyValue::Iter(iter) => { - Value::Vec(apply_sorting_and_pagination(iter, &sorting, pagination)) + let valid_request = ValidQueryRequest::validate(request, &mut wsv)?; + let res = valid_request.execute(&wsv).map_err(ValidationFail::from)?; + + match res { + LazyValue::Value(result) => { + let cursor = ForwardCursor::default(); + let result = QueryResponse { result, cursor }; + return Ok(Scale(result.into())); + } + LazyValue::Iter(iter) => { + let query_id = (&valid_request, &sorting, &pagination).encode(); + let query = apply_sorting_and_pagination(iter, &sorting, pagination); + (query_id, query.batched(fetch_size)) + } } }; - let paginated_result = QueryResult { - result, - pagination, - sorting, + let (batch, next_cursor) = live_query.next_batch(cursor)?; + + if !live_query.is_depleted() { + query_store.insert(query_id, live_query); + } + + let query_response = QueryResponse { + result: Value::Vec(batch), + cursor: next_cursor, }; - Ok(Scale(paginated_result.into())) + + Ok(Scale(query_response.into())) } fn apply_sorting_and_pagination( @@ -155,6 +190,7 @@ async fn handle_pending_transactions( sumeragi: SumeragiHandle, pagination: Pagination, ) -> Result>> { + // TODO: Don't clone wsv here let wsv = sumeragi.wsv_clone(); Ok(Scale( queue @@ -162,6 +198,9 @@ async fn handle_pending_transactions( .map(Into::into) .paginate(pagination) .collect::>(), + // TODO: + // NOTE: batching is done after collecting the result of pagination + //.batched(fetch_size) )) } @@ -257,10 +296,10 @@ mod subscription { use crate::event; /// Type for any error during subscription handling - #[derive(thiserror::Error, displaydoc::Display, Debug)] + #[derive(Debug, displaydoc::Display, thiserror::Error)] enum Error { /// Event consumption resulted in an error - Consumer(#[source] Box), + Consumer(#[from] Box), /// Event reception error Event(#[from] tokio::sync::broadcast::error::RecvError), /// WebSocket error @@ -292,7 +331,10 @@ mod subscription { /// There should be a [`warp::filters::ws::Message::close()`] /// message to end subscription #[iroha_futures::telemetry_future] - pub async fn handle_subscription(events: EventsSender, stream: WebSocket) -> eyre::Result<()> { + pub(crate) async fn handle_subscription( + events: EventsSender, + stream: WebSocket, + ) -> eyre::Result<()> { let mut consumer = event::Consumer::new(stream).await?; match subscribe_forever(events, &mut consumer).await { @@ -404,6 +446,7 @@ impl Torii { queue, notify_shutdown, sumeragi, + query_store: Arc::default(), kura, } } @@ -443,9 +486,7 @@ impl Torii { } /// Helper function to create router. This router can tested without starting up an HTTP server - pub(crate) fn create_api_router( - &self, - ) -> impl warp::Filter + Clone + Send { + fn create_api_router(&self) -> impl warp::Filter + Clone + Send { let health_route = warp::get() .and(warp::path(uri::HEALTH)) .and_then(|| async { Ok::<_, Infallible>(handle_health()) }); @@ -480,13 +521,19 @@ impl Torii { )) .and(body::versioned()), ) - .or(endpoint4( + .or(endpoint7( handle_queries, warp::path(uri::QUERY) - .and(add_state!(self.sumeragi)) - .and(paginate()) + .and(add_state!( + self.sumeragi, + self.query_store, + NonZeroUsize::try_from(self.iroha_cfg.torii.fetch_size) + .expect("u64 should always fit into usize"), + )) + .and(body::versioned()) .and(sorting()) - .and(body::versioned()), + .and(paginate()) + .and(cursor()), )) .or(endpoint2( handle_post_configuration, @@ -612,13 +659,16 @@ impl Torii { /// # Errors /// Can fail due to listening to network or if http server fails #[iroha_futures::telemetry_future] - pub async fn start(self) -> eyre::Result<()> { - let mut handles = vec![]; + pub(crate) async fn start(self) -> eyre::Result<()> { + let query_idle_time = Duration::from_millis(self.iroha_cfg.torii.query_idle_time_ms.get()); let torii = Arc::new(self); + let mut handles = vec![]; + #[cfg(feature = "telemetry")] handles.extend(Arc::clone(&torii).start_telemetry()?); handles.extend(Arc::clone(&torii).start_api()?); + handles.push(Arc::clone(&torii.query_store).expired_query_cleanup(query_idle_time)); handles .into_iter() diff --git a/cli/src/torii/utils.rs b/cli/src/torii/utils.rs index 80c0d0028ab..77d31319c06 100644 --- a/cli/src/torii/utils.rs +++ b/cli/src/torii/utils.rs @@ -66,4 +66,4 @@ impl Reply for WarpResult { } } -iroha_cli_derive::generate_endpoints!(2, 3, 4, 5); +iroha_cli_derive::generate_endpoints!(2, 3, 4, 5, 7); diff --git a/client/benches/torii.rs b/client/benches/torii.rs index eb280c0496f..99fc7df5e5c 100644 --- a/client/benches/torii.rs +++ b/client/benches/torii.rs @@ -87,14 +87,19 @@ fn query_requests(criterion: &mut Criterion) { let mut failures_count = 0; let _dropable = group.throughput(Throughput::Bytes(request.encode().len() as u64)); let _dropable2 = group.bench_function("query", |b| { - b.iter(|| match iroha_client.request(request.clone()) { - Ok(assets) => { - assert!(!assets.is_empty()); - success_count += 1; - } - Err(e) => { - eprintln!("Query failed: {e}"); - failures_count += 1; + b.iter(|| { + match iroha_client + .request(request.clone()) + .and_then(|iter| iter.collect::, _>>()) + { + Ok(assets) => { + assert!(!assets.is_empty()); + success_count += 1; + } + Err(e) => { + eprintln!("Query failed: {e}"); + failures_count += 1; + } } }); }); diff --git a/client/src/client.rs b/client/src/client.rs index 9bf83301e3e..aa2e7bacf0d 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -21,8 +21,13 @@ use http_default::{AsyncWebSocketStream, WebSocketStream}; use iroha_config::{client::Configuration, torii::uri, GetConfiguration, PostConfiguration}; use iroha_crypto::{HashOf, KeyPair}; use iroha_data_model::{ - block::VersionedCommittedBlock, isi::Instruction, predicate::PredicateBox, prelude::*, - transaction::TransactionPayload, ValidationFail, + block::VersionedCommittedBlock, + isi::Instruction, + predicate::PredicateBox, + prelude::*, + query::{ForwardCursor, Pagination, Query, Sorting}, + transaction::TransactionPayload, + ValidationFail, }; use iroha_logger::prelude::*; use iroha_telemetry::metrics::Status; @@ -40,28 +45,25 @@ use crate::{ const APPLICATION_JSON: &str = "application/json"; -/// General trait for all response handlers -pub trait ResponseHandler> { - /// What is the output of the handler - type Output; - - /// Handles HTTP response - fn handle(self, response: Response) -> Self::Output; -} - /// Phantom struct that handles responses of Query API. /// Depending on input query struct, transforms a response into appropriate output. -#[derive(Clone, Copy)] -pub struct QueryResponseHandler(PhantomData); +#[derive(Debug, Clone, serde::Serialize)] +pub struct QueryResponseHandler { + query_request: QueryRequest, + _output_type: PhantomData, +} -impl Default for QueryResponseHandler { - fn default() -> Self { - Self(PhantomData) +impl QueryResponseHandler { + fn new(query_request: QueryRequest) -> Self { + Self { + query_request, + _output_type: PhantomData, + } } } /// `Result` with [`ClientQueryError`] as an error -pub type QueryHandlerResult = core::result::Result; +pub type QueryResult = core::result::Result; /// Trait for signing transactions pub trait Sign { @@ -94,21 +96,18 @@ impl Sign for VersionedSignedTransaction { } } -impl ResponseHandler for QueryResponseHandler +impl QueryResponseHandler where - R: Query + Debug, - >::Error: Into, + >::Error: Into, { - type Output = QueryHandlerResult>; - - fn handle(self, resp: Response>) -> Self::Output { + fn handle(&mut self, resp: &Response>) -> QueryResult { // Separate-compilation friendly response handling fn _handle_query_response_base( resp: &Response>, - ) -> QueryHandlerResult { + ) -> QueryResult { match resp.status() { StatusCode::OK => { - let res = VersionedQueryResult::decode_all_versioned(resp.body()); + let res = VersionedQueryResponse::decode_all_versioned(resp.body()); res.wrap_err( "Failed to decode response from Iroha. \ You are likely using a version of the client library \ @@ -143,9 +142,15 @@ where } } - _handle_query_response_base(&resp).and_then(|VersionedQueryResult::V1(result)| { - ClientQueryRequest::try_from(result).map_err(Into::into) - }) + let response = _handle_query_response_base(resp) + .map(|VersionedQueryResponse::V1(response)| response)?; + + let value = R::try_from(response.result) + .map_err(Into::into) + .wrap_err("Unexpected type")?; + + self.query_request.server_cursor = response.cursor; + Ok(value) } } @@ -167,17 +172,15 @@ impl From for ClientQueryError { /// Phantom struct that handles Transaction API HTTP response #[derive(Clone, Copy)] -pub struct TransactionResponseHandler; - -impl ResponseHandler for TransactionResponseHandler { - type Output = Result<()>; +struct TransactionResponseHandler; - fn handle(self, resp: Response>) -> Self::Output { +impl TransactionResponseHandler { + fn handle(resp: &Response>) -> Result<()> { if resp.status() == StatusCode::OK { Ok(()) } else { Err( - ResponseReport::with_msg("Unexpected transaction response", &resp) + ResponseReport::with_msg("Unexpected transaction response", resp) .unwrap_or_else(core::convert::identity) .into(), ) @@ -189,16 +192,12 @@ impl ResponseHandler for TransactionResponseHandler { #[derive(Clone, Copy)] pub struct StatusResponseHandler; -impl ResponseHandler for StatusResponseHandler { - type Output = Result; - - fn handle(self, resp: Response>) -> Self::Output { +impl StatusResponseHandler { + fn handle(resp: &Response>) -> Result { if resp.status() != StatusCode::OK { - return Err( - ResponseReport::with_msg("Unexpected status response", &resp) - .unwrap_or_else(core::convert::identity) - .into(), - ); + return Err(ResponseReport::with_msg("Unexpected status response", resp) + .unwrap_or_else(core::convert::identity) + .into()); } serde_json::from_slice(resp.body()).wrap_err("Failed to decode body") } @@ -212,10 +211,7 @@ impl ResponseReport { /// /// # Errors /// If response body isn't a valid utf-8 string - fn with_msg(msg: S, response: &Response>) -> Result - where - S: AsRef, - { + fn with_msg>(msg: S, response: &Response>) -> Result { let status = response.status(); let body = std::str::from_utf8(response.body()); let msg = msg.as_ref(); @@ -236,60 +232,99 @@ impl From for eyre::Report { } } -/// More convenient version of [`iroha_data_model::prelude::QueryResult`]. -/// The only difference is that this struct has `output` field extracted from the result -/// accordingly to the source query. -#[derive(Clone, Debug)] -pub struct ClientQueryRequest -where - R: Query + Debug, - >::Error: Into, -{ - /// Query output - pub output: R::Output, - /// See [`iroha_data_model::prelude::QueryResult`] - pub pagination: Pagination, - /// See [`iroha_data_model::prelude::QueryResult`] - pub sorting: Sorting, +/// Output of a query +pub trait QueryOutput: Into + TryFrom { + /// Type of the query output + type Target: Clone; + + /// Construct query output from query response + fn new(value: Self, query_request: QueryResponseHandler) -> Self::Target; +} + +/// Iterable query output +#[derive(Debug, Clone, serde::Serialize)] +pub struct ResultSet { + query_handler: QueryResponseHandler>, + + iter: Vec, + client_cursor: usize, } -impl ClientQueryRequest +impl Iterator for ResultSet where - R: Query + Debug, - >::Error: Into, + Vec: QueryOutput, + as TryFrom>::Error: Into, { - /// Extracts output as is - pub fn only_output(self) -> R::Output { - self.output + type Item = QueryResult; + + fn next(&mut self) -> Option { + if self.client_cursor >= self.iter.len() { + self.query_handler.query_request.server_cursor.get()?; + + let request = match self.query_handler.query_request.clone().assemble().build() { + Err(err) => return Some(Err(ClientQueryError::Other(err))), + Ok(ok) => ok, + }; + + let response = match request.send() { + Err(err) => return Some(Err(ClientQueryError::Other(err))), + Ok(ok) => ok, + }; + let value = match self.query_handler.handle(&response) { + Err(err) => return Some(Err(err)), + Ok(ok) => ok, + }; + self.iter = value; + self.client_cursor = 0; + } + + let item = Ok(self.iter.get(self.client_cursor).cloned()); + self.client_cursor += 1; + item.transpose() } } -impl TryFrom for ClientQueryRequest +impl QueryOutput for Vec where - R: Query + Debug, - >::Error: Into, + Self: Into + TryFrom, { - type Error = eyre::Report; - - fn try_from( - QueryResult { - result, - pagination, - sorting, - }: QueryResult, - ) -> Result { - let output = R::Output::try_from(result) - .map_err(Into::into) - .wrap_err("Unexpected type")?; + type Target = ResultSet; - Ok(Self { - output, - pagination, - sorting, - }) + fn new(value: Self, query_handler: QueryResponseHandler) -> Self::Target { + ResultSet { + query_handler, + iter: value, + client_cursor: 0, + } } } +macro_rules! impl_query_result { + ( $($ident:ty),+ $(,)? ) => { $( + impl QueryOutput for $ident { + type Target = Self; + + fn new(value: Self, _query_handler: QueryResponseHandler) -> Self::Target { + value + } + } )+ + }; +} +impl_query_result! { + bool, + iroha_data_model::Value, + iroha_data_model::numeric::NumericValue, + iroha_data_model::role::Role, + iroha_data_model::asset::Asset, + iroha_data_model::asset::AssetDefinition, + iroha_data_model::account::Account, + iroha_data_model::domain::Domain, + iroha_data_model::block::BlockHeader, + iroha_data_model::query::MetadataValue, + iroha_data_model::query::TransactionQueryOutput, + iroha_data_model::trigger::Trigger, +} + /// Iroha client #[derive(Clone, DebugCustom, Display)] #[debug( @@ -317,6 +352,42 @@ pub struct Client { add_transaction_nonce: bool, } +/// Query request +#[derive(Debug, Clone, serde::Serialize)] +pub struct QueryRequest { + torii_url: Url, + headers: HashMap, + request: Vec, + sorting: Sorting, + pagination: Pagination, + server_cursor: ForwardCursor, +} + +impl QueryRequest { + #[cfg(test)] + fn dummy() -> Self { + Self { + torii_url: uri::QUERY.parse().unwrap(), + headers: HashMap::new(), + request: Vec::new(), + sorting: Sorting::default(), + pagination: Pagination::default(), + server_cursor: ForwardCursor::default(), + } + } + fn assemble(self) -> DefaultRequestBuilder { + DefaultRequestBuilder::new( + HttpMethod::POST, + self.torii_url.join(uri::QUERY).expect("Valid URI"), + ) + .headers(self.headers) + .params(Vec::from(self.sorting)) + .params(Vec::from(self.pagination)) + .params(Vec::from(self.server_cursor)) + .body(self.request) + } +} + /// Representation of `Iroha` client. impl Client { /// Constructor for client from configuration @@ -411,7 +482,7 @@ impl Client { /// /// # Errors /// Fails if signature generation fails - pub fn sign_query(&self, query: QueryBuilder) -> Result { + pub fn sign_query(&self, query: QueryBuilder) -> Result { query .sign(self.key_pair.clone()) .wrap_err("Failed to sign query") @@ -422,10 +493,7 @@ impl Client { /// /// # Errors /// Fails if sending transaction to peer fails or if it response with error - pub fn submit( - &self, - instruction: impl Instruction + Debug, - ) -> Result> { + pub fn submit(&self, instruction: impl Instruction) -> Result> { let isi = instruction.into(); self.submit_all([isi]) } @@ -480,13 +548,12 @@ impl Client { transaction: &VersionedSignedTransaction, ) -> Result> { iroha_logger::trace!(tx=?transaction, "Submitting"); - let (req, hash, resp_handler) = - self.prepare_transaction_request::(transaction); + let (req, hash) = self.prepare_transaction_request::(transaction); let response = req .build()? .send() .wrap_err_with(|| format!("Failed to send transaction with hash {hash:?}"))?; - resp_handler.handle(response)?; + TransactionResponseHandler::handle(&response)?; Ok(hash) } @@ -589,10 +656,10 @@ impl Client { /// it is better to use a response handler anyway. It allows to abstract from implementation details. /// /// For general usage example see [`Client::prepare_query_request`]. - pub fn prepare_transaction_request( + fn prepare_transaction_request( &self, transaction: &VersionedSignedTransaction, - ) -> (B, HashOf, TransactionResponseHandler) { + ) -> (B, HashOf) { let transaction_bytes: Vec = transaction.encode_versioned(); ( @@ -603,7 +670,6 @@ impl Client { .headers(self.headers.clone()) .body(transaction_bytes), transaction.payload().hash(), - TransactionResponseHandler, ) } @@ -670,7 +736,7 @@ impl Client { /// ```ignore /// use eyre::Result; /// use iroha_client::{ - /// client::{Client, ResponseHandler}, + /// client::Client, /// http::{RequestBuilder, Response, Method}, /// }; /// use iroha_data_model::{predicate::PredicateBox, prelude::{Account, FindAllAccounts, Pagination}}; @@ -717,36 +783,34 @@ impl Client { /// // Handle response with the handler and get typed result /// let accounts = resp_handler.handle(resp)?; /// - /// Ok(accounts.only_output()) + /// Ok(accounts.output()) /// } /// ``` - pub fn prepare_query_request( + fn prepare_query_request( &self, request: R, filter: PredicateBox, pagination: Pagination, sorting: Sorting, - ) -> Result<(B, QueryResponseHandler)> + ) -> Result<(DefaultRequestBuilder, QueryResponseHandler)> where - R: Query + Debug, >::Error: Into, - B: RequestBuilder, { - let pagination: Vec<_> = pagination.into(); - let sorting: Vec<_> = sorting.into(); - let request = QueryBuilder::new(request, self.account_id.clone()).with_filter(filter); - let request: VersionedSignedQuery = self.sign_query(request)?.into(); + let query_builder = QueryBuilder::new(request, self.account_id.clone()).with_filter(filter); + let request = self.sign_query(query_builder)?.encode_versioned(); + + let query_request = QueryRequest { + torii_url: self.torii_url.clone(), + headers: self.headers.clone(), + request, + sorting, + pagination, + server_cursor: ForwardCursor::default(), + }; Ok(( - B::new( - HttpMethod::POST, - self.torii_url.join(uri::QUERY).expect("Valid URI"), - ) - .params(pagination) - .params(sorting) - .headers(self.headers.clone()) - .body(request.encode_versioned()), - QueryResponseHandler::default(), + query_request.clone().assemble(), + QueryResponseHandler::new(query_request), )) } @@ -754,37 +818,42 @@ impl Client { /// /// # Errors /// Fails if sending request fails - pub fn request_with_filter_and_pagination_and_sorting( + pub fn request_with_filter_and_pagination_and_sorting( &self, request: R, pagination: Pagination, sorting: Sorting, filter: PredicateBox, - ) -> QueryHandlerResult> + ) -> QueryResult<::Target> where - R: Query + Debug, - >::Error: Into, // Seems redundant + R::Output: QueryOutput, + >::Error: Into, { iroha_logger::trace!(?request, %pagination, ?sorting, ?filter); - let (req, resp_handler) = self.prepare_query_request::( - request, filter, pagination, sorting, - )?; - let response = req.build()?.send()?; - resp_handler.handle(response) + let (req, mut resp_handler) = + self.prepare_query_request::(request, filter, pagination, sorting)?; + + let kita = req.build()?; + //println!("Request: {kita:?}"); + let response = kita.send()?; + let value = resp_handler.handle(&response)?; + let output = QueryOutput::new(value, resp_handler); + + Ok(output) } /// Create a request with pagination and sorting. /// /// # Errors /// Fails if sending request fails - pub fn request_with_pagination_and_sorting( + pub fn request_with_pagination_and_sorting( &self, request: R, pagination: Pagination, sorting: Sorting, - ) -> QueryHandlerResult> + ) -> QueryResult<::Target> where - R: Query + Debug, + R::Output: QueryOutput, >::Error: Into, { self.request_with_filter_and_pagination_and_sorting( @@ -799,15 +868,15 @@ impl Client { /// /// # Errors /// Fails if sending request fails - pub fn request_with_filter_and_pagination( + pub fn request_with_filter_and_pagination( &self, request: R, pagination: Pagination, filter: PredicateBox, - ) -> QueryHandlerResult> + ) -> QueryResult<::Target> where - R: Query + Debug, - >::Error: Into, // Seems redundant + R::Output: QueryOutput, + >::Error: Into, { self.request_with_filter_and_pagination_and_sorting( request, @@ -821,15 +890,15 @@ impl Client { /// /// # Errors /// Fails if sending request fails - pub fn request_with_filter_and_sorting( + pub fn request_with_filter_and_sorting( &self, request: R, sorting: Sorting, filter: PredicateBox, - ) -> QueryHandlerResult> + ) -> QueryResult<::Target> where - R: Query + Debug, - >::Error: Into, // Seems redundant + R::Output: QueryOutput, + >::Error: Into, { self.request_with_filter_and_pagination_and_sorting( request, @@ -846,13 +915,13 @@ impl Client { /// /// # Errors /// Fails if sending request fails - pub fn request_with_filter( + pub fn request_with_filter( &self, request: R, filter: PredicateBox, - ) -> QueryHandlerResult> + ) -> QueryResult<::Target> where - R: Query + Debug, + R::Output: QueryOutput, >::Error: Into, { self.request_with_filter_and_pagination(request, Pagination::default(), filter) @@ -865,13 +934,13 @@ impl Client { /// /// # Errors /// Fails if sending request fails - pub fn request_with_pagination( + pub fn request_with_pagination( &self, request: R, pagination: Pagination, - ) -> QueryHandlerResult> + ) -> QueryResult<::Target> where - R: Query + Debug, + R::Output: QueryOutput, >::Error: Into, { self.request_with_filter_and_pagination(request, pagination, PredicateBox::default()) @@ -881,13 +950,13 @@ impl Client { /// /// # Errors /// Fails if sending request fails - pub fn request_with_sorting( + pub fn request_with_sorting( &self, request: R, sorting: Sorting, - ) -> QueryHandlerResult> + ) -> QueryResult<::Target> where - R: Query + Debug, + R::Output: QueryOutput, >::Error: Into, { self.request_with_pagination_and_sorting(request, Pagination::default(), sorting) @@ -897,13 +966,15 @@ impl Client { /// /// # Errors /// Fails if sending request fails - pub fn request(&self, request: R) -> QueryHandlerResult + pub fn request( + &self, + request: R, + ) -> QueryResult<::Target> where - R: Query + Debug, + R::Output: QueryOutput, >::Error: Into, { self.request_with_pagination(request, Pagination::default()) - .map(ClientQueryRequest::only_output) } /// Connect (through `WebSocket`) to listen for `Iroha` `pipeline` and `data` events. @@ -1131,9 +1202,9 @@ impl Client { /// # Errors /// Fails if sending request or decoding fails pub fn get_status(&self) -> Result { - let (req, resp_handler) = self.prepare_status_request::(); + let req = self.prepare_status_request::(); let resp = req.build()?.send()?; - resp_handler.handle(resp) + StatusResponseHandler::handle(&resp) } /// Prepares http-request to implement [`Self::get_status`] on your own. @@ -1142,18 +1213,12 @@ impl Client { /// /// # Errors /// Fails if request build fails - pub fn prepare_status_request(&self) -> (B, StatusResponseHandler) - where - B: RequestBuilder, - { - ( - B::new( - HttpMethod::GET, - self.telemetry_url.join(uri::STATUS).expect("Valid URI"), - ) - .headers(self.headers.clone()), - StatusResponseHandler, + pub fn prepare_status_request(&self) -> B { + B::new( + HttpMethod::GET, + self.telemetry_url.join(uri::STATUS).expect("Valid URI"), ) + .headers(self.headers.clone()) } } @@ -1256,11 +1321,10 @@ pub mod stream_api { /// - Sending failed /// - Message not received in stream during connection or subscription /// - Message is an error - pub async fn new(handler: I) -> Result> - where - I: Init + Send, - I::Next: Send, - { + #[allow(clippy::future_not_send)] + pub async fn new>( + handler: I, + ) -> Result> { trace!("Creating `AsyncStream`"); let InitData { first_message, @@ -1770,13 +1834,13 @@ mod tests { #[cfg(test)] mod query_errors_handling { use http::Response; - use iroha_data_model::{query::error::QueryExecutionFail, ValidationFail}; + use iroha_data_model::{asset::Asset, query::error::QueryExecutionFail, ValidationFail}; use super::*; #[test] fn certain_errors() -> Result<()> { - let sut = QueryResponseHandler::::default(); + let mut sut = QueryResponseHandler::>::new(QueryRequest::dummy()); let responses = vec![ ( StatusCode::UNAUTHORIZED, @@ -1796,7 +1860,7 @@ mod tests { for (status_code, err) in responses { let resp = Response::builder().status(status_code).body(err.encode())?; - match sut.handle(resp) { + match sut.handle(&resp) { Err(ClientQueryError::Validation(actual)) => { // PartialEq isn't implemented, so asserting by encoded repr assert_eq!(actual.encode(), err.encode()); @@ -1810,12 +1874,12 @@ mod tests { #[test] fn indeterminate() -> Result<()> { - let sut = QueryResponseHandler::::default(); + let mut sut = QueryResponseHandler::>::new(QueryRequest::dummy()); let response = Response::builder() .status(StatusCode::INTERNAL_SERVER_ERROR) .body(Vec::::new())?; - match sut.handle(response) { + match sut.handle(&response) { Err(ClientQueryError::Other(_)) => Ok(()), x => Err(eyre!("Expected indeterminate, found: {:?}", x)), } diff --git a/client/src/http.rs b/client/src/http.rs index f1396f61a65..1a70a381c07 100644 --- a/client/src/http.rs +++ b/client/src/http.rs @@ -28,14 +28,14 @@ pub trait RequestBuilder { { for pair in params { let (k, v) = pair.borrow(); - self = self.param(k, v.to_string()); + self = self.param(k, v); } self } /// Add a single query param #[must_use] - fn param(self, key: K, value: V) -> Self + fn param(self, key: K, value: &V) -> Self where K: AsRef, V: ToString; @@ -52,14 +52,14 @@ pub trait RequestBuilder { { for pair in headers { let (k, v) = pair.borrow(); - self = self.header(k, v.to_string()); + self = self.header(k, v); } self } /// Add a single header #[must_use] - fn header(self, name: N, value: V) -> Self + fn header(self, name: N, value: &V) -> Self where N: AsRef, V: ToString; @@ -166,11 +166,11 @@ pub mod ws { /// todo!() /// } /// - /// fn param, V: ToString>(self, _: K, _: V) -> Self { + /// fn param, V: ?Sized + ToString>(self, _: K, _: &V) -> Self { /// todo!() /// } /// - /// fn header, V: ToString>(self, _: N, _: V) -> Self { + /// fn header, V: ?Sized + ToString>(self, _: N, _: &V) -> Self { /// todo!() /// } /// diff --git a/client/src/http_default.rs b/client/src/http_default.rs index 46530a9b45a..2147c9b61ce 100644 --- a/client/src/http_default.rs +++ b/client/src/http_default.rs @@ -25,6 +25,7 @@ fn header_name_from_str(str: &str) -> Result { } /// Default request builder implemented on top of `attohttpc` crate. +#[derive(Debug)] pub struct DefaultRequestBuilder { inner: Result, body: Option>, @@ -50,6 +51,7 @@ impl DefaultRequestBuilder { } /// Request built by [`DefaultRequestBuilder`]. +#[derive(Debug)] pub struct DefaultRequest(AttoHttpRequestBuilderWithBytes); impl DefaultRequest { @@ -80,7 +82,7 @@ impl RequestBuilder for DefaultRequestBuilder { } } - fn header(self, key: K, value: V) -> Self + fn header(self, key: K, value: &V) -> Self where K: AsRef, V: ToString, @@ -90,12 +92,12 @@ impl RequestBuilder for DefaultRequestBuilder { }) } - fn param(self, key: K, value: V) -> Self + fn param(self, key: K, value: &V) -> Self where K: AsRef, V: ToString, { - self.and_then(|b| Ok(b.param(key, value))) + self.and_then(|b| Ok(b.param(key, value.to_string()))) } fn body(self, data: Vec) -> Self { @@ -150,11 +152,11 @@ impl RequestBuilder for DefaultWebSocketRequestBuilder { .uri(url.as_ref()))) } - fn param(self, _key: K, _val: V) -> Self { + fn param(self, _key: K, _val: &V) -> Self { Self(self.0.and(Err(eyre!("No params expected")))) } - fn header(self, name: N, value: V) -> Self + fn header(self, name: N, value: &V) -> Self where N: AsRef, V: ToString, diff --git a/client/tests/integration/asset.rs b/client/tests/integration/asset.rs index 0e4f179eb49..f055537a75a 100644 --- a/client/tests/integration/asset.rs +++ b/client/tests/integration/asset.rs @@ -3,7 +3,7 @@ use std::{str::FromStr as _, thread}; use eyre::Result; -use iroha_client::client; +use iroha_client::client::{self, QueryResult}; use iroha_crypto::{KeyPair, PublicKey}; use iroha_data_model::prelude::*; use iroha_primitives::fixed::Fixed; @@ -31,7 +31,9 @@ fn client_register_asset_should_add_asset_once_but_not_twice() -> Result<()> { // Registering an asset to an account which doesn't have one // should result in asset being created test_client.poll_request(client::asset::by_account_id(account_id), |result| { - result.iter().any(|asset| { + let assets = result.collect::>>().expect("Valid"); + + assets.iter().any(|asset| { asset.id().definition_id == asset_definition_id && *asset.value() == AssetValue::Quantity(0) }) @@ -61,7 +63,9 @@ fn unregister_asset_should_remove_asset_from_account() -> Result<()> { // Wait for asset to be registered test_client.poll_request(client::asset::by_account_id(account_id.clone()), |result| { - result + let assets = result.collect::>>().expect("Valid"); + + assets .iter() .any(|asset| asset.id().definition_id == asset_definition_id) })?; @@ -70,7 +74,9 @@ fn unregister_asset_should_remove_asset_from_account() -> Result<()> { // ... and check that it is removed after Unregister test_client.poll_request(client::asset::by_account_id(account_id), |result| { - result + let assets = result.collect::>>().expect("Valid"); + + assets .iter() .all(|asset| asset.id().definition_id != asset_definition_id) })?; @@ -101,7 +107,9 @@ fn client_add_asset_quantity_to_existing_asset_should_increase_asset_amount() -> let tx = test_client.build_transaction(instructions, metadata)?; test_client.submit_transaction(&tx)?; test_client.poll_request(client::asset::by_account_id(account_id), |result| { - result.iter().any(|asset| { + let assets = result.collect::>>().expect("Valid"); + + assets.iter().any(|asset| { asset.id().definition_id == asset_definition_id && *asset.value() == AssetValue::Quantity(quantity) }) @@ -132,7 +140,9 @@ fn client_add_big_asset_quantity_to_existing_asset_should_increase_asset_amount( let tx = test_client.build_transaction(instructions, metadata)?; test_client.submit_transaction(&tx)?; test_client.poll_request(client::asset::by_account_id(account_id), |result| { - result.iter().any(|asset| { + let assets = result.collect::>>().expect("Valid"); + + assets.iter().any(|asset| { asset.id().definition_id == asset_definition_id && *asset.value() == AssetValue::BigQuantity(quantity) }) @@ -165,7 +175,9 @@ fn client_add_asset_with_decimal_should_increase_asset_amount() -> Result<()> { let tx = test_client.build_transaction(instructions, metadata)?; test_client.submit_transaction(&tx)?; test_client.poll_request(client::asset::by_account_id(account_id.clone()), |result| { - result.iter().any(|asset| { + let assets = result.collect::>>().expect("Valid"); + + assets.iter().any(|asset| { asset.id().definition_id == asset_definition_id && *asset.value() == AssetValue::Fixed(quantity) }) @@ -185,7 +197,9 @@ fn client_add_asset_with_decimal_should_increase_asset_amount() -> Result<()> { .checked_add(quantity2) .map_err(|e| eyre::eyre!("{}", e))?; test_client.submit_till(mint, client::asset::by_account_id(account_id), |result| { - result.iter().any(|asset| { + let assets = result.collect::>>().expect("Valid"); + + assets.iter().any(|asset| { asset.id().definition_id == asset_definition_id && *asset.value() == AssetValue::Fixed(sum) }) @@ -218,19 +232,22 @@ fn client_add_asset_with_name_length_more_than_limit_should_not_commit_transacti iroha_logger::info!("Creating another asset"); thread::sleep(pipeline_time * 4); - let asset_definition_ids = test_client + let mut asset_definition_ids = test_client .request(client::asset::all_definitions()) .expect("Failed to execute request.") + .collect::>>() + .expect("Failed to execute request.") .into_iter() - .map(|asset| asset.id().clone()) - .collect::>(); + .map(|asset| asset.id().clone()); iroha_logger::debug!( "Collected asset definitions ID's: {:?}", &asset_definition_ids ); - assert!(asset_definition_ids.contains(&normal_asset_definition_id)); - assert!(!asset_definition_ids.contains(&incorrect_asset_definition_id)); + assert!(asset_definition_ids + .any(|asset_definition_id| asset_definition_id == normal_asset_definition_id)); + assert!(!asset_definition_ids + .any(|asset_definition_id| asset_definition_id == incorrect_asset_definition_id)); Ok(()) } diff --git a/client/tests/integration/asset_propagation.rs b/client/tests/integration/asset_propagation.rs index 4160de579e9..ddd34ee81b3 100644 --- a/client/tests/integration/asset_propagation.rs +++ b/client/tests/integration/asset_propagation.rs @@ -3,7 +3,7 @@ use std::{str::FromStr as _, thread}; use eyre::Result; -use iroha_client::client; +use iroha_client::client::{self, QueryResult}; use iroha_crypto::KeyPair; use iroha_data_model::{ parameter::{default::MAX_TRANSACTIONS_IN_BLOCK, ParametersBuilder}, @@ -51,7 +51,9 @@ fn client_add_asset_quantity_to_existing_asset_should_increase_asset_amount_on_a client::Client::test(&peer.api_address, &peer.telemetry_address).poll_request( client::asset::by_account_id(account_id), |result| { - result.iter().any(|asset| { + let assets = result.collect::>>().expect("Valid"); + + assets.iter().any(|asset| { asset.id().definition_id == asset_definition_id && *asset.value() == AssetValue::Quantity(quantity) }) diff --git a/client/tests/integration/multiple_blocks_created.rs b/client/tests/integration/multiple_blocks_created.rs index d00c10e14f1..0cbb55fdffc 100644 --- a/client/tests/integration/multiple_blocks_created.rs +++ b/client/tests/integration/multiple_blocks_created.rs @@ -3,7 +3,7 @@ use std::thread; use eyre::Result; -use iroha_client::client::{self, Client}; +use iroha_client::client::{self, Client, QueryResult}; use iroha_crypto::KeyPair; use iroha_data_model::{ parameter::{default::MAX_TRANSACTIONS_IN_BLOCK, ParametersBuilder}, @@ -63,7 +63,9 @@ fn long_multiple_blocks_created() -> Result<()> { Client::test(&peer.api_address, &peer.telemetry_address).poll_request( client::asset::by_account_id(account_id), |result| { - result.iter().any(|asset| { + let assets = result.collect::>>().expect("Valid"); + + assets.iter().any(|asset| { asset.id().definition_id == asset_definition_id && *asset.value() == AssetValue::Quantity(account_has_quantity) }) diff --git a/client/tests/integration/multisignature_account.rs b/client/tests/integration/multisignature_account.rs index 84aea34170b..dd5f1454328 100644 --- a/client/tests/integration/multisignature_account.rs +++ b/client/tests/integration/multisignature_account.rs @@ -3,7 +3,7 @@ use std::thread; use eyre::Result; -use iroha_client::client::{self, Client}; +use iroha_client::client::{self, Client, QueryResult}; use iroha_crypto::KeyPair; use iroha_data_model::prelude::*; use test_network::*; @@ -42,7 +42,9 @@ fn transaction_signed_by_new_signatory_of_account_should_pass() -> Result<()> { mint_asset, client::asset::by_account_id(account_id), |result| { - result.iter().any(|asset| { + let assets = result.collect::>>().expect("Valid"); + + assets.iter().any(|asset| { asset.id().definition_id == asset_definition_id && *asset.value() == AssetValue::Quantity(quantity) }) diff --git a/client/tests/integration/multisignature_transaction.rs b/client/tests/integration/multisignature_transaction.rs index 01ae20e47e9..cb2abe8ad8a 100644 --- a/client/tests/integration/multisignature_transaction.rs +++ b/client/tests/integration/multisignature_transaction.rs @@ -3,7 +3,7 @@ use std::{str::FromStr as _, thread, time::Duration}; use eyre::Result; -use iroha_client::client::{self, Client}; +use iroha_client::client::{self, Client, QueryResult}; use iroha_config::client::Configuration as ClientConfiguration; use iroha_crypto::KeyPair; use iroha_data_model::{ @@ -79,8 +79,11 @@ fn multisignature_transactions_should_wait_for_all_signatures() -> Result<()> { .unwrap(); let client_1 = Client::new(&client_configuration).expect("Invalid client configuration"); let request = client::asset::by_account_id(alice_id); + let assets = client_1 + .request(request.clone())? + .collect::>>()?; assert_eq!( - client_1.request(request.clone())?.len(), + assets.len(), 2 // Alice has roses and cabbage from Genesis ); let (public_key2, private_key2) = key_pair_2.into(); @@ -94,7 +97,9 @@ fn multisignature_transactions_should_wait_for_all_signatures() -> Result<()> { .expect("Found no pending transaction for this account."); client_2.submit_transaction(&client_2.sign_transaction(transaction)?)?; thread::sleep(pipeline_time); - let assets = client_1.request(request)?; + let assets = client_1 + .request(request)? + .collect::>>()?; assert!(!assets.is_empty()); let camomile_asset = assets .iter() diff --git a/client/tests/integration/non_mintable.rs b/client/tests/integration/non_mintable.rs index 6371c25d08d..404ad8d3192 100644 --- a/client/tests/integration/non_mintable.rs +++ b/client/tests/integration/non_mintable.rs @@ -3,7 +3,7 @@ use std::str::FromStr as _; use eyre::Result; -use iroha_client::client; +use iroha_client::client::{self, QueryResult}; use iroha_data_model::{metadata::UnlimitedMetadata, prelude::*}; use test_network::*; @@ -34,7 +34,8 @@ fn non_mintable_asset_can_be_minted_once_but_not_twice() -> Result<()> { // We can register and mint the non-mintable token test_client.submit_transaction(&tx)?; test_client.poll_request(client::asset::by_account_id(account_id.clone()), |result| { - result.iter().any(|asset| { + let assets = result.collect::>>().expect("Valid"); + assets.iter().any(|asset| { asset.id().definition_id == asset_definition_id && *asset.value() == AssetValue::Quantity(200_u32) }) @@ -46,7 +47,8 @@ fn non_mintable_asset_can_be_minted_once_but_not_twice() -> Result<()> { // However, this will fail assert!(test_client .poll_request(client::asset::by_account_id(account_id), |result| { - result.iter().any(|asset| { + let assets = result.collect::>>().expect("Valid"); + assets.iter().any(|asset| { asset.id().definition_id == asset_definition_id && *asset.value() == AssetValue::Quantity(400_u32) }) @@ -72,7 +74,8 @@ fn non_mintable_asset_cannot_be_minted_if_registered_with_non_zero_value() -> Re // We can register the non-mintable token test_client.submit_all([create_asset, register_asset.clone()])?; test_client.poll_request(client::asset::by_account_id(account_id), |result| { - result.iter().any(|asset| { + let assets = result.collect::>>().expect("Valid"); + assets.iter().any(|asset| { asset.id().definition_id == asset_definition_id && *asset.value() == AssetValue::Quantity(1_u32) }) @@ -108,7 +111,8 @@ fn non_mintable_asset_can_be_minted_if_registered_with_zero_value() -> Result<() [create_asset.into(), register_asset.into(), mint.into()]; test_client.submit_all(instructions)?; test_client.poll_request(client::asset::by_account_id(account_id), |result| { - result.iter().any(|asset| { + let assets = result.collect::>>().expect("Valid"); + assets.iter().any(|asset| { asset.id().definition_id == asset_definition_id && *asset.value() == AssetValue::Quantity(1_u32) }) diff --git a/client/tests/integration/offline_peers.rs b/client/tests/integration/offline_peers.rs index 13ed021b960..ee20b58ca4e 100644 --- a/client/tests/integration/offline_peers.rs +++ b/client/tests/integration/offline_peers.rs @@ -1,7 +1,7 @@ #![allow(clippy::restriction)] use eyre::Result; -use iroha_client::client; +use iroha_client::client::{self, QueryResult}; use iroha_data_model::{ parameter::{default::MAX_TRANSACTIONS_IN_BLOCK, ParametersBuilder}, prelude::*, @@ -33,7 +33,9 @@ fn genesis_block_is_committed_with_some_offline_peers() -> Result<()> { let alice_has_roses = 13; //Then - let assets = client.request(client::asset::by_account_id(alice_id))?; + let assets = client + .request(client::asset::by_account_id(alice_id))? + .collect::>>()?; let asset = assets .iter() .find(|asset| asset.id().definition_id == roses) diff --git a/client/tests/integration/pagination.rs b/client/tests/integration/pagination.rs index afa827a511e..e50251eb980 100644 --- a/client/tests/integration/pagination.rs +++ b/client/tests/integration/pagination.rs @@ -1,8 +1,10 @@ #![allow(clippy::restriction)] +use std::num::{NonZeroU32, NonZeroU64}; + use eyre::Result; -use iroha_client::client::asset; -use iroha_data_model::prelude::*; +use iroha_client::client::{asset, QueryResult}; +use iroha_data_model::{asset::AssetDefinition, prelude::*, query::Pagination}; use test_network::*; #[test] @@ -20,8 +22,14 @@ fn client_add_asset_quantity_to_existing_asset_should_increase_asset_amount() -> client.submit_all_blocking(register)?; let vec = client - .request_with_pagination(asset::all_definitions(), Pagination::new(Some(5), Some(5)))? - .only_output(); + .request_with_pagination( + asset::all_definitions(), + Pagination { + limit: NonZeroU32::new(5), + start: NonZeroU64::new(5), + }, + )? + .collect::>>()?; assert_eq!(vec.len(), 5); Ok(()) } diff --git a/client/tests/integration/permissions.rs b/client/tests/integration/permissions.rs index f38d880b9c8..99de7c45f0a 100644 --- a/client/tests/integration/permissions.rs +++ b/client/tests/integration/permissions.rs @@ -3,7 +3,7 @@ use std::{str::FromStr as _, thread}; use eyre::Result; -use iroha_client::client::{self, Client}; +use iroha_client::client::{self, Client, QueryResult}; use iroha_data_model::prelude::*; use test_network::{PeerBuilder, *}; @@ -13,6 +13,8 @@ fn get_assets(iroha_client: &mut Client, id: &::Id) -> iroha_client .request(client::asset::by_account_id(id.clone())) .expect("Failed to execute request.") + .collect::>>() + .expect("Failed to execute request.") } #[ignore = "ignore, more in #2851"] diff --git a/client/tests/integration/queries/account.rs b/client/tests/integration/queries/account.rs index 5e839d24e00..f9a07b84560 100644 --- a/client/tests/integration/queries/account.rs +++ b/client/tests/integration/queries/account.rs @@ -3,7 +3,7 @@ use std::{collections::HashSet, str::FromStr as _}; use eyre::Result; -use iroha_client::client; +use iroha_client::client::{self, QueryResult}; use iroha_data_model::prelude::*; use test_network::*; @@ -65,7 +65,9 @@ fn find_accounts_with_asset() -> Result<()> { AssetValueType::Quantity ); - let found_accounts = test_client.request(client::account::all_with_asset(definition_id))?; + let found_accounts = test_client + .request(client::account::all_with_asset(definition_id))? + .collect::>>()?; let found_ids = found_accounts .into_iter() .map(|account| account.id().clone()) diff --git a/client/tests/integration/queries/role.rs b/client/tests/integration/queries/role.rs index d3d83bb663e..73521d46403 100644 --- a/client/tests/integration/queries/role.rs +++ b/client/tests/integration/queries/role.rs @@ -3,7 +3,7 @@ use std::collections::HashSet; use eyre::Result; -use iroha_client::client; +use iroha_client::client::{self, QueryResult}; use iroha_data_model::{prelude::*, query::error::QueryExecutionFail}; use test_network::*; @@ -37,11 +37,14 @@ fn find_roles() -> Result<()> { // Checking results let found_role_ids = test_client .request(client::role::all())? - .into_iter() - .map(|role| role.id().clone()) - .collect::>(); + .collect::>>()? + .into_iter(); - assert!(role_ids.is_subset(&found_role_ids)); + assert!(role_ids.is_subset( + &found_role_ids + .map(|role| role.id().clone()) + .collect::>() + )); Ok(()) } @@ -64,7 +67,9 @@ fn find_role_ids() -> Result<()> { let role_ids = HashSet::from(role_ids); // Checking results - let found_role_ids = test_client.request(client::role::all_ids())?; + let found_role_ids = test_client + .request(client::role::all_ids())? + .collect::>>()?; let found_role_ids = found_role_ids.into_iter().collect::>(); assert!(role_ids.is_subset(&found_role_ids)); @@ -150,7 +155,9 @@ fn find_roles_by_account_id() -> Result<()> { let role_ids = HashSet::from(role_ids); // Checking results - let found_role_ids = test_client.request(client::role::by_account_id(alice_id))?; + let found_role_ids = test_client + .request(client::role::by_account_id(alice_id))? + .collect::>>()?; let found_role_ids = found_role_ids.into_iter().collect::>(); assert!(role_ids.is_subset(&found_role_ids)); diff --git a/client/tests/integration/restart_peer.rs b/client/tests/integration/restart_peer.rs index 02bc937841c..dbc4f2cd082 100644 --- a/client/tests/integration/restart_peer.rs +++ b/client/tests/integration/restart_peer.rs @@ -3,7 +3,7 @@ use std::{str::FromStr, sync::Arc}; use eyre::Result; -use iroha_client::client; +use iroha_client::client::{self, QueryResult}; use iroha_data_model::prelude::*; use tempfile::TempDir; use test_network::*; @@ -46,8 +46,10 @@ fn restarted_peer_should_have_the_same_asset_amount() -> Result<()> { ); iroha_client.submit_blocking(mint_asset)?; - let asset = iroha_client + let assets = iroha_client .request(client::asset::by_account_id(account_id.clone()))? + .collect::>>()?; + let asset = assets .into_iter() .find(|asset| asset.id().definition_id == asset_definition_id) .expect("Asset not found"); @@ -65,19 +67,17 @@ fn restarted_peer_should_have_the_same_asset_amount() -> Result<()> { ); wait_for_genesis_committed(&vec![iroha_client.clone()], 0); - let account_asset = iroha_client - .poll_request(client::asset::by_account_id(account_id), |assets| { - iroha_logger::error!(?assets); - assets - .iter() - .any(|asset| asset.id().definition_id == asset_definition_id) - }) - .expect("Valid") - .into_iter() - .find(|asset| asset.id().definition_id == asset_definition_id) - .expect("Asset not found"); + iroha_client.poll_request(client::asset::by_account_id(account_id), |result| { + let assets = result.collect::>>().expect("Valid"); + iroha_logger::error!(?assets); + + let account_asset = assets + .into_iter() + .find(|asset| asset.id().definition_id == asset_definition_id) + .expect("Asset not found"); - assert_eq!(AssetValue::Quantity(quantity), *account_asset.value()); + AssetValue::Quantity(quantity) == *account_asset.value() + })? } Ok(()) } diff --git a/client/tests/integration/roles.rs b/client/tests/integration/roles.rs index b6be99ddb07..b11d8012ffa 100644 --- a/client/tests/integration/roles.rs +++ b/client/tests/integration/roles.rs @@ -3,7 +3,7 @@ use std::str::FromStr as _; use eyre::Result; -use iroha_client::client; +use iroha_client::client::{self, QueryResult}; use iroha_data_model::prelude::*; use test_network::*; @@ -88,7 +88,9 @@ fn register_and_grant_role_for_metadata_access() -> Result<()> { test_client.submit_blocking(set_key_value)?; // Making request to find Alice's roles - let found_role_ids = test_client.request(client::role::by_account_id(alice_id))?; + let found_role_ids = test_client + .request(client::role::by_account_id(alice_id))? + .collect::>>()?; assert!(found_role_ids.contains(&role_id)); Ok(()) @@ -121,7 +123,9 @@ fn unregistered_role_removed_from_account() -> Result<()> { test_client.submit_blocking(grant_role)?; // Check that Mouse has root role - let found_mouse_roles = test_client.request(client::role::by_account_id(mouse_id.clone()))?; + let found_mouse_roles = test_client + .request(client::role::by_account_id(mouse_id.clone()))? + .collect::>>()?; assert!(found_mouse_roles.contains(&role_id)); // Unregister root role @@ -129,7 +133,9 @@ fn unregistered_role_removed_from_account() -> Result<()> { test_client.submit_blocking(unregister_role)?; // Check that Mouse doesn't have the root role - let found_mouse_roles = test_client.request(client::role::by_account_id(mouse_id))?; + let found_mouse_roles = test_client + .request(client::role::by_account_id(mouse_id))? + .collect::>>()?; assert!(!found_mouse_roles.contains(&role_id)); Ok(()) diff --git a/client/tests/integration/set_parameter.rs b/client/tests/integration/set_parameter.rs index 24963610265..a9533f5f541 100644 --- a/client/tests/integration/set_parameter.rs +++ b/client/tests/integration/set_parameter.rs @@ -3,7 +3,7 @@ use std::str::FromStr; use eyre::Result; -use iroha_client::client; +use iroha_client::client::{self, QueryResult}; use iroha_data_model::prelude::*; use test_network::*; @@ -16,7 +16,9 @@ fn can_change_parameter_value() -> Result<()> { let parameter_id = ParameterId::from_str("BlockTime")?; let param_box = SetParameterBox::new(parameter); - let old_params = test_client.request(client::parameter::all())?; + let old_params = test_client + .request(client::parameter::all())? + .collect::>>()?; let param_val_old = old_params .iter() .find(|param| param.id() == ¶meter_id) @@ -25,8 +27,9 @@ fn can_change_parameter_value() -> Result<()> { test_client.submit_blocking(param_box)?; - let new_params = test_client.request(client::parameter::all())?; - + let new_params = test_client + .request(client::parameter::all())? + .collect::>>()?; let param_val_new = new_params .iter() .find(|param| param.id() == ¶meter_id) diff --git a/client/tests/integration/sorting.rs b/client/tests/integration/sorting.rs index ae59bb2e643..bed460847a9 100644 --- a/client/tests/integration/sorting.rs +++ b/client/tests/integration/sorting.rs @@ -1,12 +1,18 @@ #![allow(clippy::restriction, clippy::pedantic)] -use std::{collections::HashSet, str::FromStr as _}; +use std::{ + collections::HashSet, + num::{NonZeroU32, NonZeroU64}, + str::FromStr as _, +}; use eyre::{Result, WrapErr as _}; -use iroha_client::client; +use iroha_client::client::{self, QueryResult}; use iroha_data_model::{ + account::Account, predicate::{string, value, PredicateBox}, prelude::*, + query::{Pagination, Sorting}, }; use test_network::*; @@ -21,7 +27,7 @@ fn correct_pagination_assets_after_creating_new_one() { let mut assets = vec![]; let mut instructions = vec![]; - for i in 0..10_u128 { + for i in 0..20_u128 { let asset_definition_id = AssetDefinitionId::from_str(&format!("xor{i}#wonderland")).expect("Valid"); let asset_definition = AssetDefinition::store(asset_definition_id.clone()); @@ -56,30 +62,31 @@ fn correct_pagination_assets_after_creating_new_one() { let res = test_client .request_with_pagination_and_sorting( client::asset::by_account_id(account_id.clone()), - Pagination::new(None, Some(5)), + Pagination { + limit: NonZeroU32::new(5), + start: None, + }, sorting.clone(), ) + .expect("Valid") + .collect::>>() .expect("Valid"); - assert_eq!( - res.output - .iter() - .map(|asset| asset.id().definition_id.name.clone()) - .collect::>(), - assets + assert!(res + .iter() + .map(|asset| &asset.id().definition_id.name) + .eq(assets .iter() .take(5) - .map(|asset| asset.id().definition_id.name.clone()) - .collect::>() - ); + .map(|asset| &asset.id().definition_id.name))); - let new_asset_definition_id = AssetDefinitionId::from_str("xor10#wonderland").expect("Valid"); + let new_asset_definition_id = AssetDefinitionId::from_str("xor20#wonderland").expect("Valid"); let new_asset_definition = AssetDefinition::store(new_asset_definition_id.clone()); let mut new_asset_metadata = Metadata::new(); new_asset_metadata .insert_with_limits( sort_by_metadata_key, - 10_u128.to_value(), + 20_u128.to_value(), MetadataLimits::new(10, 23), ) .expect("Valid"); @@ -98,25 +105,24 @@ fn correct_pagination_assets_after_creating_new_one() { let res = test_client .request_with_pagination_and_sorting( client::asset::by_account_id(account_id), - Pagination::new(Some(5), Some(6)), + Pagination { + limit: NonZeroU32::new(13), + start: NonZeroU64::new(8), + }, sorting, ) + .expect("Valid") + .collect::>>() .expect("Valid"); - let mut right = assets.into_iter().skip(5).take(5).collect::>(); - - right.push(new_asset); - - assert_eq!( - res.output - .into_iter() - .map(|asset| asset.id().definition_id.name.clone()) - .collect::>(), - right - .into_iter() - .map(|asset| asset.id().definition_id.name.clone()) - .collect::>() - ); + assert!(res + .iter() + .map(|asset| &asset.id().definition_id.name) + .eq(assets + .iter() + .skip(8) + .chain(core::iter::once(&new_asset)) + .map(|asset| &asset.id().definition_id.name))); } #[test] @@ -164,15 +170,15 @@ fn correct_sorting_of_entities() { string::StringPredicate::starts_with("xor_"), )), ) + .expect("Valid") + .collect::>>() .expect("Valid"); assert!(res - .output .iter() .map(Identifiable::id) .eq(asset_definitions.iter().rev())); assert!(res - .output .iter() .map(|asset_definition| asset_definition.metadata()) .eq(assets_metadata.iter().rev())); @@ -215,15 +221,12 @@ fn correct_sorting_of_entities() { string::StringPredicate::starts_with("charlie"), )), ) + .expect("Valid") + .collect::>>() .expect("Valid"); + assert!(res.iter().map(Identifiable::id).eq(accounts.iter().rev())); assert!(res - .output - .iter() - .map(Identifiable::id) - .eq(accounts.iter().rev())); - assert!(res - .output .iter() .map(|account| account.metadata()) .eq(accounts_metadata.iter().rev())); @@ -266,15 +269,12 @@ fn correct_sorting_of_entities() { string::StringPredicate::starts_with("neverland"), )), ) + .expect("Valid") + .collect::>>() .expect("Valid"); + assert!(res.iter().map(Identifiable::id).eq(domains.iter().rev())); assert!(res - .output - .iter() - .map(Identifiable::id) - .eq(domains.iter().rev())); - assert!(res - .output .iter() .map(|domain| domain.metadata()) .eq(domains_metadata.iter().rev())); @@ -316,14 +316,16 @@ fn correct_sorting_of_entities() { Sorting::by_metadata_key(sort_by_metadata_key), filter, ) + .expect("Valid") + .collect::>>() .expect("Valid"); - assert_eq!(res.output[0].id(), &domains[1]); - assert_eq!(res.output[1].id(), &domains[0]); - assert_eq!(res.output[2].id(), &domains[2]); - assert_eq!(res.output[0].metadata(), &domains_metadata[1]); - assert_eq!(res.output[1].metadata(), &domains_metadata[0]); - assert_eq!(res.output[2].metadata(), &domains_metadata[2]); + assert_eq!(res[0].id(), &domains[1]); + assert_eq!(res[1].id(), &domains[0]); + assert_eq!(res[2].id(), &domains[2]); + assert_eq!(res[0].metadata(), &domains_metadata[1]); + assert_eq!(res[1].metadata(), &domains_metadata[0]); + assert_eq!(res[2].metadata(), &domains_metadata[2]); } #[test] @@ -377,15 +379,11 @@ fn sort_only_elements_which_have_sorting_key() -> Result<()> { string::StringPredicate::starts_with("charlie"), )), ) - .wrap_err("Failed to submit request")?; + .wrap_err("Failed to submit request")? + .collect::>>()?; - let accounts = accounts_a.into_iter().rev().chain(accounts_b.into_iter()); - assert!(res - .output - .iter() - .map(Identifiable::id) - .cloned() - .eq(accounts)); + let accounts = accounts_a.iter().rev().chain(accounts_b.iter()); + assert!(res.iter().map(Identifiable::id).eq(accounts)); Ok(()) } diff --git a/client/tests/integration/transfer_asset.rs b/client/tests/integration/transfer_asset.rs index b7d8022b048..674a1fa405e 100644 --- a/client/tests/integration/transfer_asset.rs +++ b/client/tests/integration/transfer_asset.rs @@ -1,6 +1,6 @@ #![allow(clippy::restriction, clippy::pedantic)] -use iroha_client::client; +use iroha_client::client::{self, QueryResult}; use iroha_crypto::KeyPair; use iroha_data_model::{prelude::*, Registered}; use iroha_primitives::fixed::Fixed; @@ -90,7 +90,9 @@ fn simulate_transfer< transfer_asset, client::asset::by_account_id(mouse_id.clone()), |result| { - result.iter().any(|asset| { + let assets = result.collect::>>().expect("Valid"); + + assets.iter().any(|asset| { asset.id().definition_id == asset_definition_id && *asset.value() == amount_to_transfer.clone().into() && asset.id().account_id == mouse_id diff --git a/client/tests/integration/triggers/time_trigger.rs b/client/tests/integration/triggers/time_trigger.rs index 0543f4a7361..b2f77b8a600 100644 --- a/client/tests/integration/triggers/time_trigger.rs +++ b/client/tests/integration/triggers/time_trigger.rs @@ -3,7 +3,7 @@ use std::{str::FromStr as _, time::Duration}; use eyre::Result; -use iroha_client::client::{self, Client}; +use iroha_client::client::{self, Client, QueryResult}; use iroha_config::sumeragi::default::DEFAULT_CONSENSUS_ESTIMATION_MS; use iroha_data_model::{prelude::*, transaction::WasmSmartContract}; use iroha_logger::info; @@ -246,7 +246,9 @@ fn mint_nft_for_every_user_every_1_sec() -> Result<()> { for account_id in accounts { let start_pattern = "nft_number_"; let end_pattern = format!("_for_{}#{}", account_id.name, account_id.domain_id); - let assets = test_client.request(client::asset::by_account_id(account_id.clone()))?; + let assets = test_client + .request(client::asset::by_account_id(account_id.clone()))? + .collect::>>()?; let count: u64 = assets .into_iter() .filter(|asset| { diff --git a/client/tests/integration/tx_history.rs b/client/tests/integration/tx_history.rs index 45ea0a8eb26..af47084ca8f 100644 --- a/client/tests/integration/tx_history.rs +++ b/client/tests/integration/tx_history.rs @@ -1,10 +1,14 @@ #![allow(clippy::restriction)] -use std::{str::FromStr as _, thread}; +use std::{ + num::{NonZeroU32, NonZeroU64}, + str::FromStr as _, + thread, +}; use eyre::Result; -use iroha_client::client::transaction; -use iroha_data_model::prelude::*; +use iroha_client::client::{transaction, QueryResult}; +use iroha_data_model::{prelude::*, query::Pagination}; use test_network::*; use super::Configuration; @@ -52,9 +56,12 @@ fn client_has_rejected_and_acepted_txs_should_return_tx_history() -> Result<()> let transactions = client .request_with_pagination( transaction::by_account_id(account_id.clone()), - Pagination::new(Some(1), Some(50)), + Pagination { + limit: NonZeroU32::new(50), + start: NonZeroU64::new(1), + }, )? - .only_output(); + .collect::>>()?; assert_eq!(transactions.len(), 50); let mut prev_creation_time = core::time::Duration::from_millis(0); diff --git a/client/tests/integration/tx_rollback.rs b/client/tests/integration/tx_rollback.rs index 7d366f3f8bb..e65037e9d7d 100644 --- a/client/tests/integration/tx_rollback.rs +++ b/client/tests/integration/tx_rollback.rs @@ -3,7 +3,7 @@ use std::str::FromStr as _; use eyre::Result; -use iroha_client::client; +use iroha_client::client::{self, QueryResult}; use iroha_data_model::prelude::*; use test_network::*; @@ -30,11 +30,13 @@ fn client_sends_transaction_with_invalid_instruction_should_not_see_any_changes( //Then let request = client::asset::by_account_id(account_id); - let query_result = client.request(request)?; + let query_result = client.request(request)?.collect::>>()?; assert!(query_result .iter() .all(|asset| asset.id().definition_id != wrong_asset_definition_id)); - let definition_query_result = client.request(client::asset::all_definitions())?; + let definition_query_result = client + .request(client::asset::all_definitions())? + .collect::>>()?; assert!(definition_query_result .iter() .all(|asset| *asset.id() != wrong_asset_definition_id)); diff --git a/client/tests/integration/unregister_peer.rs b/client/tests/integration/unregister_peer.rs index e46a421a9cf..817e90ded4e 100644 --- a/client/tests/integration/unregister_peer.rs +++ b/client/tests/integration/unregister_peer.rs @@ -2,7 +2,7 @@ use std::thread; use eyre::Result; -use iroha_client::client; +use iroha_client::client::{self, QueryResult}; use iroha_crypto::KeyPair; use iroha_data_model::{ parameter::{default::MAX_TRANSACTIONS_IN_BLOCK, ParametersBuilder}, @@ -64,7 +64,9 @@ fn check_assets( Configuration::block_sync_gossip_time(), 15, |result| { - result.iter().any(|asset| { + let assets = result.collect::>>().expect("Valid"); + + assets.iter().any(|asset| { asset.id().definition_id == *asset_definition_id && *asset.value() == AssetValue::Quantity(quantity) }) diff --git a/client/tests/integration/unstable_network.rs b/client/tests/integration/unstable_network.rs index 79579f26c78..b14522b2f9b 100644 --- a/client/tests/integration/unstable_network.rs +++ b/client/tests/integration/unstable_network.rs @@ -3,7 +3,7 @@ use core::sync::atomic::Ordering; use std::thread; -use iroha_client::client::{self, Client}; +use iroha_client::client::{self, Client, QueryResult}; use iroha_data_model::prelude::*; use iroha_logger::Level; use rand::seq::SliceRandom; @@ -122,7 +122,9 @@ fn unstable_network( Configuration::pipeline_time(), 4, |result| { - result.iter().any(|asset| { + let assets = result.collect::>>().expect("Valid"); + + assets.iter().any(|asset| { asset.id().definition_id == asset_definition_id && *asset.value() == AssetValue::Quantity(account_has_quantity) }) diff --git a/client/tests/integration/upgrade.rs b/client/tests/integration/upgrade.rs index 278b6ca2c65..d79b90063dc 100644 --- a/client/tests/integration/upgrade.rs +++ b/client/tests/integration/upgrade.rs @@ -3,7 +3,7 @@ use std::path::Path; use eyre::Result; -use iroha_client::client::Client; +use iroha_client::client::{Client, QueryResult}; use iroha_crypto::KeyPair; use iroha_data_model::{prelude::*, query::permission::FindAllPermissionTokenDefinitions}; use iroha_logger::info; @@ -63,7 +63,7 @@ fn validator_upgrade_should_update_tokens() -> Result<()> { let definitions = client.request(FindAllPermissionTokenDefinitions)?; assert!(definitions .into_iter() - .any(|definition| definition.id() == &can_unregister_domain_token_id)); + .any(|definition| definition.unwrap().id() == &can_unregister_domain_token_id)); upgrade_validator( &client, @@ -71,7 +71,9 @@ fn validator_upgrade_should_update_tokens() -> Result<()> { )?; // Check that `can_unregister_domain` doesn't exist - let definitions = client.request(FindAllPermissionTokenDefinitions)?; + let definitions = client + .request(FindAllPermissionTokenDefinitions)? + .collect::>>()?; assert!(!definitions .iter() .any(|definition| definition.id() == &can_unregister_domain_token_id)); diff --git a/config/Cargo.toml b/config/Cargo.toml index 13879d1cab7..93dccade084 100644 --- a/config/Cargo.toml +++ b/config/Cargo.toml @@ -27,6 +27,7 @@ displaydoc = { workspace = true } derive_more = { workspace = true } cfg-if = { workspace = true } path-absolutize = { workspace = true } +once_cell = "1.16.0" [dev-dependencies] proptest = { workspace = true } diff --git a/config/src/torii.rs b/config/src/torii.rs index 81ad7d67701..1797d8e7e07 100644 --- a/config/src/torii.rs +++ b/config/src/torii.rs @@ -1,5 +1,7 @@ //! `Torii` configuration as well as the default values for the URLs used for the main endpoints: `p2p`, `telemetry`, but not `api`. #![allow(clippy::std_instead_of_core, clippy::arithmetic_side_effects)] +use std::num::NonZeroU64; + use iroha_config_base::derive::{Documented, Proxy}; use iroha_primitives::addr::{socket_addr, SocketAddr}; use serde::{Deserialize, Serialize}; @@ -12,6 +14,12 @@ pub const DEFAULT_TORII_TELEMETRY_ADDR: SocketAddr = socket_addr!(127.0.0.1:8180 pub const DEFAULT_TORII_MAX_TRANSACTION_SIZE: u32 = 2_u32.pow(15); /// Default upper bound on `content-length` specified in the HTTP request header pub const DEFAULT_TORII_MAX_CONTENT_LENGTH: u32 = 2_u32.pow(12) * 4000; +/// Default max size of a single batch of results from a query +pub static DEFAULT_TORII_FETCH_SIZE: once_cell::sync::Lazy = + once_cell::sync::Lazy::new(|| NonZeroU64::new(10).unwrap()); +/// Default max time a query can remain in the store unaccessed +pub static DEFAULT_TORII_QUERY_IDLE_TIME_MS: once_cell::sync::Lazy = + once_cell::sync::Lazy::new(|| NonZeroU64::new(30_000).unwrap()); /// Structure that defines the configuration parameters of `Torii` which is the routing module. /// For example the `p2p_addr`, which is used for consensus and block-synchronisation purposes, @@ -33,6 +41,10 @@ pub struct Configuration { pub max_transaction_size: u32, /// Maximum number of bytes in raw message. Used to prevent from DOS attacks. pub max_content_len: u32, + /// How many query results are returned in one batch + pub fetch_size: NonZeroU64, + /// Time query can remain in the store if unaccessed + pub query_idle_time_ms: NonZeroU64, } impl Default for ConfigurationProxy { @@ -43,6 +55,8 @@ impl Default for ConfigurationProxy { telemetry_url: None, max_transaction_size: Some(DEFAULT_TORII_MAX_TRANSACTION_SIZE), max_content_len: Some(DEFAULT_TORII_MAX_CONTENT_LENGTH), + fetch_size: Some(*DEFAULT_TORII_FETCH_SIZE), + query_idle_time_ms: Some(*DEFAULT_TORII_QUERY_IDLE_TIME_MS), } } } @@ -96,9 +110,11 @@ pub mod tests { telemetry_url in prop::option::of(Just(DEFAULT_TORII_TELEMETRY_ADDR)), max_transaction_size in prop::option::of(Just(DEFAULT_TORII_MAX_TRANSACTION_SIZE)), max_content_len in prop::option::of(Just(DEFAULT_TORII_MAX_CONTENT_LENGTH)), + fetch_size in prop::option::of(Just(*DEFAULT_TORII_FETCH_SIZE)), + query_idle_time_ms in prop::option::of(Just(*DEFAULT_TORII_QUERY_IDLE_TIME_MS)), ) -> ConfigurationProxy { - ConfigurationProxy { p2p_addr, api_url, telemetry_url, max_transaction_size, max_content_len } + ConfigurationProxy { p2p_addr, api_url, telemetry_url, max_transaction_size, max_content_len, fetch_size, query_idle_time_ms } } } } diff --git a/configs/peer/config.json b/configs/peer/config.json index 1737604b1b3..56c74756874 100644 --- a/configs/peer/config.json +++ b/configs/peer/config.json @@ -25,7 +25,9 @@ "API_URL": null, "TELEMETRY_URL": null, "MAX_TRANSACTION_SIZE": 32768, - "MAX_CONTENT_LEN": 16384000 + "MAX_CONTENT_LEN": 16384000, + "FETCH_SIZE": 10, + "QUERY_IDLE_TIME_MS": 30000 }, "BLOCK_SYNC": { "GOSSIP_PERIOD_MS": 10000, diff --git a/configs/peer/validator.wasm b/configs/peer/validator.wasm index cdb40f90b8639561876597e3a1da636f6ff0586e..9bd5e976537853cca46aa35b597c1cbed9f7acd0 100644 GIT binary patch delta 112611 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 delta 113580 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 ^#daWc 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 R4=x|B7UBB7eB=P-{4eqc;~M|~ diff --git a/core/benches/apply_blocks/apply_blocks.rs b/core/benches/apply_blocks/apply_blocks.rs index 7c434066a69..917d29dee22 100644 --- a/core/benches/apply_blocks/apply_blocks.rs +++ b/core/benches/apply_blocks/apply_blocks.rs @@ -51,7 +51,7 @@ fn create_block( let pending_block = PendingBlock { header, transactions: vec![TransactionValue { - tx: transaction, + value: transaction, error: None, }], event_recommendations: Vec::new(), diff --git a/core/src/queue.rs b/core/src/queue.rs index ba80068cb1f..681a899bb3c 100644 --- a/core/src/queue.rs +++ b/core/src/queue.rs @@ -170,8 +170,8 @@ impl Queue { } /// Returns all pending transactions. - pub fn all_transactions<'q: 'wsv, 'wsv>( - &'q self, + pub fn all_transactions<'wsv>( + &'wsv self, wsv: &'wsv WorldStateView, ) -> impl Iterator + 'wsv { self.txs.iter().filter_map(|tx| { diff --git a/core/src/smartcontracts/isi/account.rs b/core/src/smartcontracts/isi/account.rs index 77f5d8b6a69..d6f2bf4f267 100644 --- a/core/src/smartcontracts/isi/account.rs +++ b/core/src/smartcontracts/isi/account.rs @@ -475,18 +475,20 @@ pub mod query { use eyre::{Result, WrapErr}; use iroha_data_model::{ - evaluate::ExpressionEvaluator, query::error::QueryExecutionFail as Error, + account::Account, + evaluate::ExpressionEvaluator, + permission::PermissionToken, + query::{error::QueryExecutionFail as Error, MetadataValue}, }; use super::*; - use crate::smartcontracts::query::Lazy; impl ValidQuery for FindRolesByAccountId { #[metrics(+"find_roles_by_account_id")] fn execute<'wsv>( &self, wsv: &'wsv WorldStateView, - ) -> Result<::Lazy<'wsv>, Error> { + ) -> Result + 'wsv>, Error> { let account_id = wsv .evaluate(&self.id) .wrap_err("Failed to evaluate account id") @@ -505,7 +507,7 @@ pub mod query { fn execute<'wsv>( &self, wsv: &'wsv WorldStateView, - ) -> Result<::Lazy<'wsv>, Error> { + ) -> Result + 'wsv>, Error> { let account_id = wsv .evaluate(&self.id) .wrap_err("Failed to evaluate account id") @@ -522,7 +524,7 @@ pub mod query { fn execute<'wsv>( &self, wsv: &'wsv WorldStateView, - ) -> Result<::Lazy<'wsv>, Error> { + ) -> Result + 'wsv>, Error> { Ok(Box::new( wsv.domains() .values() @@ -534,10 +536,7 @@ pub mod query { impl ValidQuery for FindAccountById { #[metrics(+"find_account_by_id")] - fn execute<'wsv>( - &self, - wsv: &'wsv WorldStateView, - ) -> Result<::Lazy<'wsv>, Error> { + fn execute(&self, wsv: &WorldStateView) -> Result { let id = wsv .evaluate(&self.id) .wrap_err("Failed to get id") @@ -552,7 +551,7 @@ pub mod query { fn execute<'wsv>( &self, wsv: &'wsv WorldStateView, - ) -> Result<::Lazy<'wsv>, Error> { + ) -> Result + 'wsv>, Error> { let name = wsv .evaluate(&self.name) .wrap_err("Failed to get account name") @@ -579,7 +578,7 @@ pub mod query { fn execute<'wsv>( &self, wsv: &'wsv WorldStateView, - ) -> Result<::Lazy<'wsv>, Error> { + ) -> Result + 'wsv>, Error> { let id = wsv .evaluate(&self.domain_id) .wrap_err("Failed to get domain id") @@ -592,10 +591,7 @@ pub mod query { impl ValidQuery for FindAccountKeyValueByIdAndKey { #[metrics(+"find_account_key_value_by_id_and_key")] - fn execute<'wsv>( - &self, - wsv: &'wsv WorldStateView, - ) -> Result<::Lazy<'wsv>, Error> { + fn execute(&self, wsv: &WorldStateView) -> Result { let id = wsv .evaluate(&self.id) .wrap_err("Failed to get account id") @@ -616,7 +612,7 @@ pub mod query { fn execute<'wsv>( &self, wsv: &'wsv WorldStateView, - ) -> Result<::Lazy<'wsv>, Error> { + ) -> Result + 'wsv>, Error> { let asset_definition_id = wsv .evaluate(&self.asset_definition_id) .wrap_err("Failed to get asset id") diff --git a/core/src/smartcontracts/isi/asset.rs b/core/src/smartcontracts/isi/asset.rs index 36b58bb8571..62554ffdd38 100644 --- a/core/src/smartcontracts/isi/asset.rs +++ b/core/src/smartcontracts/isi/asset.rs @@ -436,19 +436,19 @@ pub mod isi { /// Asset-related query implementations. pub mod query { use eyre::{Result, WrapErr as _}; - use iroha_data_model::query::{ - asset::IsAssetDefinitionOwner, error::QueryExecutionFail as Error, + use iroha_data_model::{ + asset::{Asset, AssetDefinition}, + query::{asset::IsAssetDefinitionOwner, error::QueryExecutionFail as Error, MetadataValue}, }; use super::*; - use crate::smartcontracts::query::Lazy; impl ValidQuery for FindAllAssets { #[metrics(+"find_all_assets")] fn execute<'wsv>( &self, wsv: &'wsv WorldStateView, - ) -> Result<::Lazy<'wsv>, Error> { + ) -> Result + 'wsv>, Error> { Ok(Box::new( wsv.domains() .values() @@ -468,7 +468,7 @@ pub mod query { fn execute<'wsv>( &self, wsv: &'wsv WorldStateView, - ) -> Result<::Lazy<'wsv>, Error> { + ) -> Result + 'wsv>, Error> { Ok(Box::new( wsv.domains() .values() @@ -480,10 +480,7 @@ pub mod query { impl ValidQuery for FindAssetById { #[metrics(+"find_asset_by_id")] - fn execute<'wsv>( - &self, - wsv: &'wsv WorldStateView, - ) -> Result<::Lazy<'wsv>, Error> { + fn execute(&self, wsv: &WorldStateView) -> Result { let id = wsv .evaluate(&self.id) .wrap_err("Failed to get asset id") @@ -501,10 +498,7 @@ pub mod query { impl ValidQuery for FindAssetDefinitionById { #[metrics(+"find_asset_defintion_by_id")] - fn execute<'wsv>( - &self, - wsv: &'wsv WorldStateView, - ) -> Result<::Lazy<'wsv>, Error> { + fn execute(&self, wsv: &WorldStateView) -> Result { let id = wsv .evaluate(&self.id) .wrap_err("Failed to get asset definition id") @@ -521,7 +515,7 @@ pub mod query { fn execute<'wsv>( &self, wsv: &'wsv WorldStateView, - ) -> Result<::Lazy<'wsv>, Error> { + ) -> Result + 'wsv>, Error> { let name = wsv .evaluate(&self.name) .wrap_err("Failed to get asset name") @@ -552,13 +546,13 @@ pub mod query { fn execute<'wsv>( &self, wsv: &'wsv WorldStateView, - ) -> Result<::Lazy<'wsv>, Error> { + ) -> Result + 'wsv>, Error> { let id = wsv .evaluate(&self.account_id) .wrap_err("Failed to get account id") .map_err(|e| Error::Evaluate(e.to_string()))?; iroha_logger::trace!(%id); - Ok(Box::new(wsv.account_assets(&id)?)) + Ok(Box::new(wsv.account_assets(&id)?.cloned())) } } @@ -567,7 +561,7 @@ pub mod query { fn execute<'wsv>( &self, wsv: &'wsv WorldStateView, - ) -> Result<::Lazy<'wsv>, Error> { + ) -> Result + 'wsv>, Error> { let id = wsv .evaluate(&self.asset_definition_id) .wrap_err("Failed to get asset definition id") @@ -598,7 +592,7 @@ pub mod query { fn execute<'wsv>( &self, wsv: &'wsv WorldStateView, - ) -> Result<::Lazy<'wsv>, Error> { + ) -> Result + 'wsv>, Error> { let id = wsv .evaluate(&self.domain_id) .wrap_err("Failed to get domain id") @@ -619,7 +613,7 @@ pub mod query { fn execute<'wsv>( &self, wsv: &'wsv WorldStateView, - ) -> Result<::Lazy<'wsv>, Error> { + ) -> Result + 'wsv>, Error> { let domain_id = wsv .evaluate(&self.domain_id) .wrap_err("Failed to get domain id") @@ -654,10 +648,7 @@ pub mod query { impl ValidQuery for FindAssetQuantityById { #[metrics(+"find_asset_quantity_by_id")] - fn execute<'wsv>( - &self, - wsv: &'wsv WorldStateView, - ) -> Result<::Lazy<'wsv>, Error> { + fn execute(&self, wsv: &WorldStateView) -> Result { let id = wsv .evaluate(&self.id) .wrap_err("Failed to get asset id") @@ -681,10 +672,7 @@ pub mod query { impl ValidQuery for FindTotalAssetQuantityByAssetDefinitionId { #[metrics(+"find_total_asset_quantity_by_asset_definition_id")] - fn execute<'wsv>( - &self, - wsv: &'wsv WorldStateView, - ) -> Result<::Lazy<'wsv>, Error> { + fn execute(&self, wsv: &WorldStateView) -> Result { let id = wsv .evaluate(&self.id) .wrap_err("Failed to get asset definition id") @@ -697,10 +685,7 @@ pub mod query { impl ValidQuery for FindAssetKeyValueByIdAndKey { #[metrics(+"find_asset_key_value_by_id_and_key")] - fn execute<'wsv>( - &self, - wsv: &'wsv WorldStateView, - ) -> Result<::Lazy<'wsv>, Error> { + fn execute(&self, wsv: &WorldStateView) -> Result { let id = wsv .evaluate(&self.id) .wrap_err("Failed to get asset id") @@ -732,10 +717,7 @@ pub mod query { impl ValidQuery for IsAssetDefinitionOwner { #[metrics("is_asset_definition_owner")] - fn execute<'wsv>( - &self, - wsv: &'wsv WorldStateView, - ) -> Result<::Lazy<'wsv>, Error> { + fn execute(&self, wsv: &WorldStateView) -> Result { let asset_definition_id = wsv .evaluate(&self.asset_definition_id) .wrap_err("Failed to get asset definition id") diff --git a/core/src/smartcontracts/isi/block.rs b/core/src/smartcontracts/isi/block.rs index 43ffd893a17..017f03a4a6d 100644 --- a/core/src/smartcontracts/isi/block.rs +++ b/core/src/smartcontracts/isi/block.rs @@ -1,6 +1,7 @@ //! This module contains trait implementations related to block queries use eyre::{Result, WrapErr}; use iroha_data_model::{ + block::{BlockHeader, VersionedCommittedBlock}, evaluate::ExpressionEvaluator, query::{ block::FindBlockHeaderByHash, @@ -10,14 +11,13 @@ use iroha_data_model::{ use iroha_telemetry::metrics; use super::*; -use crate::smartcontracts::query::Lazy; impl ValidQuery for FindAllBlocks { #[metrics(+"find_all_blocks")] fn execute<'wsv>( &self, wsv: &'wsv WorldStateView, - ) -> Result<::Lazy<'wsv>, QueryExecutionFail> { + ) -> Result + 'wsv>, QueryExecutionFail> { Ok(Box::new( wsv.all_blocks().rev().map(|block| Clone::clone(&*block)), )) @@ -29,7 +29,7 @@ impl ValidQuery for FindAllBlockHeaders { fn execute<'wsv>( &self, wsv: &'wsv WorldStateView, - ) -> Result<::Lazy<'wsv>, QueryExecutionFail> { + ) -> Result + 'wsv>, QueryExecutionFail> { Ok(Box::new( wsv.all_blocks() .rev() @@ -40,10 +40,7 @@ impl ValidQuery for FindAllBlockHeaders { impl ValidQuery for FindBlockHeaderByHash { #[metrics(+"find_block_header")] - fn execute<'wsv>( - &self, - wsv: &'wsv WorldStateView, - ) -> Result<::Lazy<'wsv>, QueryExecutionFail> { + fn execute(&self, wsv: &WorldStateView) -> Result { let hash = wsv .evaluate(&self.hash) .wrap_err("Failed to evaluate hash") diff --git a/core/src/smartcontracts/isi/domain.rs b/core/src/smartcontracts/isi/domain.rs index e208457a9dc..2f82fec7c3c 100644 --- a/core/src/smartcontracts/isi/domain.rs +++ b/core/src/smartcontracts/isi/domain.rs @@ -288,27 +288,26 @@ pub mod isi { /// Query module provides [`Query`] Domain related implementations. pub mod query { use eyre::{Result, WrapErr}; - use iroha_data_model::query::error::QueryExecutionFail as Error; + use iroha_data_model::{ + domain::Domain, + query::{error::QueryExecutionFail as Error, MetadataValue}, + }; use super::*; - use crate::smartcontracts::query::Lazy; impl ValidQuery for FindAllDomains { #[metrics(+"find_all_domains")] fn execute<'wsv>( &self, wsv: &'wsv WorldStateView, - ) -> Result<::Lazy<'wsv>, Error> { + ) -> Result + 'wsv>, Error> { Ok(Box::new(wsv.domains().values().cloned())) } } impl ValidQuery for FindDomainById { #[metrics(+"find_domain_by_id")] - fn execute<'wsv>( - &self, - wsv: &'wsv WorldStateView, - ) -> Result<::Lazy<'wsv>, Error> { + fn execute(&self, wsv: &WorldStateView) -> Result { let id = wsv .evaluate(&self.id) .wrap_err("Failed to get domain id") @@ -320,10 +319,7 @@ pub mod query { impl ValidQuery for FindDomainKeyValueByIdAndKey { #[metrics(+"find_domain_key_value_by_id_and_key")] - fn execute<'wsv>( - &self, - wsv: &'wsv WorldStateView, - ) -> Result<::Lazy<'wsv>, Error> { + fn execute(&self, wsv: &WorldStateView) -> Result { let id = wsv .evaluate(&self.id) .wrap_err("Failed to get domain id") @@ -341,10 +337,7 @@ pub mod query { impl ValidQuery for FindAssetDefinitionKeyValueByIdAndKey { #[metrics(+"find_asset_definition_key_value_by_id_and_key")] - fn execute<'wsv>( - &self, - wsv: &'wsv WorldStateView, - ) -> Result<::Lazy<'wsv>, Error> { + fn execute(&self, wsv: &WorldStateView) -> Result { let id = wsv .evaluate(&self.id) .wrap_err("Failed to get asset definition id") diff --git a/core/src/smartcontracts/isi/query.rs b/core/src/smartcontracts/isi/query.rs index 7ff9d8737bd..72dcc09aa11 100644 --- a/core/src/smartcontracts/isi/query.rs +++ b/core/src/smartcontracts/isi/query.rs @@ -42,7 +42,7 @@ macro_rules! impl_lazy { } impl_lazy! { bool, - NumericValue, + iroha_data_model::numeric::NumericValue, iroha_data_model::role::Role, iroha_data_model::asset::Asset, iroha_data_model::asset::AssetDefinition, @@ -50,12 +50,13 @@ impl_lazy! { iroha_data_model::domain::Domain, iroha_data_model::block::BlockHeader, iroha_data_model::query::MetadataValue, - iroha_data_model::query::TransactionQueryResult, + iroha_data_model::query::TransactionQueryOutput, iroha_data_model::trigger::Trigger, } /// Query Request statefully validated on the Iroha node side. #[derive(Debug, Decode, Encode)] +#[repr(transparent)] pub struct ValidQueryRequest(VersionedSignedQuery); impl ValidQueryRequest { @@ -110,10 +111,7 @@ impl ValidQueryRequest { } impl ValidQuery for QueryBox { - fn execute<'wsv>( - &self, - wsv: &'wsv WorldStateView, - ) -> Result<::Lazy<'wsv>, Error> { + fn execute<'wsv>(&self, wsv: &'wsv WorldStateView) -> Result, Error> { iroha_logger::debug!(query=%self, "Executing"); macro_rules! match_all { @@ -474,7 +472,7 @@ mod tests { if found_accepted.transaction.error.is_none() { assert_eq!( va_tx.hash().transmute(), - found_accepted.transaction.tx.hash() + found_accepted.transaction.value.hash() ) } Ok(()) diff --git a/core/src/smartcontracts/isi/triggers/mod.rs b/core/src/smartcontracts/isi/triggers/mod.rs index d45693ce5c1..f2397a4a60a 100644 --- a/core/src/smartcontracts/isi/triggers/mod.rs +++ b/core/src/smartcontracts/isi/triggers/mod.rs @@ -195,27 +195,31 @@ pub mod isi { pub mod query { //! Queries associated to triggers. - use iroha_data_model::query::error::QueryExecutionFail as Error; + use iroha_data_model::{ + events::FilterBox, + query::{error::QueryExecutionFail as Error, MetadataValue}, + trigger::{OptimizedExecutable, Trigger, TriggerId}, + }; use super::*; - use crate::{prelude::*, smartcontracts::query::Lazy}; + use crate::prelude::*; impl ValidQuery for FindAllActiveTriggerIds { #[metrics(+"find_all_active_triggers")] fn execute<'wsv>( &self, wsv: &'wsv WorldStateView, - ) -> Result<::Lazy<'wsv>, Error> { + ) -> Result + 'wsv>, Error> { Ok(Box::new(wsv.triggers().ids().cloned())) } } impl ValidQuery for FindTriggerById { #[metrics(+"find_trigger_by_id")] - fn execute<'wsv>( + fn execute( &self, - wsv: &'wsv WorldStateView, - ) -> Result<::Lazy<'wsv>, Error> { + wsv: &WorldStateView, + ) -> Result, Error> { let id = wsv .evaluate(&self.id) .map_err(|e| Error::Evaluate(format!("Failed to evaluate trigger id. {e}")))?; @@ -243,10 +247,7 @@ pub mod query { impl ValidQuery for FindTriggerKeyValueByIdAndKey { #[metrics(+"find_trigger_key_value_by_id_and_key")] - fn execute<'wsv>( - &self, - wsv: &'wsv WorldStateView, - ) -> Result<::Lazy<'wsv>, Error> { + fn execute(&self, wsv: &WorldStateView) -> Result { let id = wsv .evaluate(&self.id) .map_err(|e| Error::Evaluate(format!("Failed to evaluate trigger id. {e}")))?; @@ -272,7 +273,10 @@ pub mod query { fn execute<'wsv>( &self, wsv: &'wsv WorldStateView, - ) -> eyre::Result<::Lazy<'wsv>, Error> { + ) -> eyre::Result< + Box> + 'wsv>, + Error, + > { let domain_id = wsv .evaluate(&self.domain_id) .map_err(|e| Error::Evaluate(format!("Failed to evaluate domain id. {e}")))?; diff --git a/core/src/smartcontracts/isi/triggers/set.rs b/core/src/smartcontracts/isi/triggers/set.rs index a3bffebae01..396975b3418 100644 --- a/core/src/smartcontracts/isi/triggers/set.rs +++ b/core/src/smartcontracts/isi/triggers/set.rs @@ -208,54 +208,48 @@ impl Set { /// Apply `f` to triggers that belong to the given [`DomainId`] /// /// Return an empty list if [`Set`] doesn't contain any triggers belonging to [`DomainId`]. - pub fn inspect_by_domain_id( - &self, + pub fn inspect_by_domain_id<'a, F: 'a, R>( + &'a self, domain_id: &DomainId, f: F, - ) -> impl ExactSizeIterator + ) -> impl Iterator + '_ where F: Fn(&TriggerId, &dyn ActionTrait) -> R, { - self.ids - .iter() - .filter_map(|(id, event_type)| { - let trigger_domain_id = id.domain_id.as_ref()?; + let domain_id = domain_id.clone(); - if trigger_domain_id != domain_id { - return None; - } + self.ids.iter().filter_map(move |(id, event_type)| { + let trigger_domain_id = id.domain_id.as_ref()?; - let result = match event_type { - EventType::Data => self - .data_triggers - .get(id) - .map(|trigger| f(id, trigger)) - .expect("`Set::data_triggers` doesn't contain required id. This is a bug"), - EventType::Pipeline => self - .pipeline_triggers - .get(id) - .map(|trigger| f(id, trigger)) - .expect( - "`Set::pipeline_triggers` doesn't contain required id. This is a bug", - ), - EventType::Time => self - .time_triggers - .get(id) - .map(|trigger| f(id, trigger)) - .expect("`Set::time_triggers` doesn't contain required id. This is a bug"), - EventType::ExecuteTrigger => self - .by_call_triggers - .get(id) - .map(|trigger| f(id, trigger)) - .expect( - "`Set::by_call_triggers` doesn't contain required id. This is a bug", - ), - }; - - Some(result) - }) - .collect::>() - .into_iter() + if *trigger_domain_id != domain_id { + return None; + } + + let result = match event_type { + EventType::Data => self + .data_triggers + .get(id) + .map(|trigger| f(id, trigger)) + .expect("`Set::data_triggers` doesn't contain required id. This is a bug"), + EventType::Pipeline => self + .pipeline_triggers + .get(id) + .map(|trigger| f(id, trigger)) + .expect("`Set::pipeline_triggers` doesn't contain required id. This is a bug"), + EventType::Time => self + .time_triggers + .get(id) + .map(|trigger| f(id, trigger)) + .expect("`Set::time_triggers` doesn't contain required id. This is a bug"), + EventType::ExecuteTrigger => self + .by_call_triggers + .get(id) + .map(|trigger| f(id, trigger)) + .expect("`Set::by_call_triggers` doesn't contain required id. This is a bug"), + }; + + Some(result) + }) } /// Apply `f` to the trigger identified by `id`. diff --git a/core/src/smartcontracts/isi/tx.rs b/core/src/smartcontracts/isi/tx.rs index 2c87fa781cb..894c965333e 100644 --- a/core/src/smartcontracts/isi/tx.rs +++ b/core/src/smartcontracts/isi/tx.rs @@ -10,13 +10,13 @@ use iroha_data_model::{ prelude::*, query::{ error::{FindError, QueryExecutionFail}, - TransactionQueryResult, + TransactionQueryOutput, }, transaction::TransactionValue, }; use iroha_telemetry::metrics; -use super::{query::Lazy, *}; +use super::*; pub(crate) struct BlockTransactionIter(Arc, usize); pub(crate) struct BlockTransactionRef(Arc, usize); @@ -61,11 +61,11 @@ impl ValidQuery for FindAllTransactions { fn execute<'wsv>( &self, wsv: &'wsv WorldStateView, - ) -> Result<::Lazy<'wsv>, QueryExecutionFail> { + ) -> Result + 'wsv>, QueryExecutionFail> { Ok(Box::new( wsv.all_blocks() .flat_map(BlockTransactionIter::new) - .map(|tx| TransactionQueryResult { + .map(|tx| TransactionQueryOutput { block_hash: tx.block_hash(), transaction: tx.value(), }), @@ -78,7 +78,7 @@ impl ValidQuery for FindTransactionsByAccountId { fn execute<'wsv>( &self, wsv: &'wsv WorldStateView, - ) -> Result<::Lazy<'wsv>, QueryExecutionFail> { + ) -> Result + 'wsv>, QueryExecutionFail> { let account_id = wsv .evaluate(&self.account_id) .wrap_err("Failed to get account id") @@ -88,7 +88,7 @@ impl ValidQuery for FindTransactionsByAccountId { wsv.all_blocks() .flat_map(BlockTransactionIter::new) .filter(move |tx| *tx.authority() == account_id) - .map(|tx| TransactionQueryResult { + .map(|tx| TransactionQueryOutput { block_hash: tx.block_hash(), transaction: tx.value(), }), @@ -98,10 +98,7 @@ impl ValidQuery for FindTransactionsByAccountId { impl ValidQuery for FindTransactionByHash { #[metrics(+"find_transaction_by_hash")] - fn execute<'wsv>( - &self, - wsv: &'wsv WorldStateView, - ) -> Result<::Lazy<'wsv>, QueryExecutionFail> { + fn execute(&self, wsv: &WorldStateView) -> Result { let tx_hash = wsv .evaluate(&self.hash) .wrap_err("Failed to get hash") @@ -122,7 +119,7 @@ impl ValidQuery for FindTransactionByHash { .iter() .find(|transaction| transaction.value.hash() == tx_hash) .cloned() - .map(|transaction| TransactionQueryResult { + .map(|transaction| TransactionQueryOutput { block_hash, transaction, }) diff --git a/core/src/smartcontracts/isi/world.rs b/core/src/smartcontracts/isi/world.rs index 51222761155..13df421a02f 100644 --- a/core/src/smartcontracts/isi/world.rs +++ b/core/src/smartcontracts/isi/world.rs @@ -278,7 +278,7 @@ pub mod isi { for account_id in account_ids { accounts_with_token.insert( account_id.clone(), - wsv.account_inherent_permission_tokens(account_id)? + wsv.account_inherent_permission_tokens(account_id) .filter(|token| token.definition_id == *target_definition_id) .cloned() .collect::>(), @@ -413,22 +413,25 @@ pub mod isi { pub mod query { use eyre::Result; use iroha_data_model::{ + parameter::Parameter, + peer::Peer, + permission::PermissionTokenDefinition, prelude::*, query::{ error::{FindError, QueryExecutionFail as Error}, permission::DoesAccountHavePermissionToken, }, + role::{Role, RoleId}, }; use super::*; - use crate::smartcontracts::query::Lazy; impl ValidQuery for FindAllRoles { #[metrics(+"find_all_roles")] fn execute<'wsv>( &self, wsv: &'wsv WorldStateView, - ) -> Result<::Lazy<'wsv>, Error> { + ) -> Result + 'wsv>, Error> { Ok(Box::new(wsv.world.roles.values().cloned())) } } @@ -438,7 +441,7 @@ pub mod query { fn execute<'wsv>( &self, wsv: &'wsv WorldStateView, - ) -> Result<::Lazy<'wsv>, Error> { + ) -> Result + 'wsv>, Error> { Ok(Box::new(wsv .world .roles @@ -451,10 +454,7 @@ pub mod query { impl ValidQuery for FindRoleByRoleId { #[metrics(+"find_role_by_role_id")] - fn execute<'wsv>( - &self, - wsv: &'wsv WorldStateView, - ) -> Result<::Lazy<'wsv>, Error> { + fn execute(&self, wsv: &WorldStateView) -> Result { let role_id = wsv .evaluate(&self.id) .map_err(|e| Error::Evaluate(e.to_string()))?; @@ -472,7 +472,7 @@ pub mod query { fn execute<'wsv>( &self, wsv: &'wsv WorldStateView, - ) -> Result<::Lazy<'wsv>, Error> { + ) -> Result + 'wsv>, Error> { Ok(Box::new(wsv.peers().cloned().map(Peer::new))) } } @@ -482,7 +482,7 @@ pub mod query { fn execute<'wsv>( &self, wsv: &'wsv WorldStateView, - ) -> Result<::Lazy<'wsv>, Error> { + ) -> Result + 'wsv>, Error> { Ok(Box::new( wsv.permission_token_definitions().values().cloned(), )) @@ -494,17 +494,14 @@ pub mod query { fn execute<'wsv>( &self, wsv: &'wsv WorldStateView, - ) -> Result<::Lazy<'wsv>, Error> { + ) -> Result + 'wsv>, Error> { Ok(Box::new(wsv.parameters().cloned())) } } impl ValidQuery for DoesAccountHavePermissionToken { #[metrics("does_account_have_permission")] - fn execute<'wsv>( - &self, - wsv: &'wsv WorldStateView, - ) -> Result<::Lazy<'wsv>, Error> { + fn execute(&self, wsv: &WorldStateView) -> Result { let authority = wsv .evaluate(&self.account_id) .map_err(|e| Error::Evaluate(e.to_string()))?; diff --git a/core/src/smartcontracts/mod.rs b/core/src/smartcontracts/mod.rs index ebab9f86afe..056dc8a6ab8 100644 --- a/core/src/smartcontracts/mod.rs +++ b/core/src/smartcontracts/mod.rs @@ -28,7 +28,7 @@ pub trait Execute { } /// This trait should be implemented for all Iroha Queries. -pub trait ValidQuery: Query +pub trait ValidQuery: iroha_data_model::query::Query where Self::Output: Lazy, { @@ -76,8 +76,8 @@ impl iroha_data_model::evaluate::Context for Context<'_> { .execute(self.wsv) .map(|value| match value { LazyValue::Value(value) => value, - // NOTE: This will only be executed from the validator/executor. - // Handing out references to the host system is a security risk + // NOTE: This will only be executed when evaluating an expression for an + // instruction, i.e. it will only be executed from the validator/executor. LazyValue::Iter(iter) => Value::Vec(iter.collect()), }) .map_err(Into::into) diff --git a/core/src/smartcontracts/wasm.rs b/core/src/smartcontracts/wasm.rs index d398d1613bd..95fe1f37cbc 100644 --- a/core/src/smartcontracts/wasm.rs +++ b/core/src/smartcontracts/wasm.rs @@ -685,8 +685,7 @@ impl<'wrld, S: state::GetCommon<'wrld>, R: DefaultExecute> ExecuteOperations<'wr .map_err(Into::into) .map(|lazy_value| match lazy_value { LazyValue::Value(value) => value, - // NOTE: Returning references to the host system is a security risk - LazyValue::Iter(iter) => Value::Vec(iter.collect::>()), + LazyValue::Iter(iter) => Value::Vec(iter.collect()), }) } @@ -868,8 +867,7 @@ impl<'wrld> ExecuteOperations<'wrld, state::Validator<'wrld>> for Runtime value, - // NOTE: Returning references to the host system is a security risk - LazyValue::Iter(iter) => Value::Vec(iter.collect::>()), + LazyValue::Iter(iter) => Value::Vec(iter.collect()), }) } diff --git a/core/src/sumeragi/main_loop.rs b/core/src/sumeragi/main_loop.rs index ec2835fb9d8..c2536ec3598 100644 --- a/core/src/sumeragi/main_loop.rs +++ b/core/src/sumeragi/main_loop.rs @@ -384,7 +384,6 @@ impl Sumeragi { // https://github.com/hyperledger/iroha/issues/3396 // Kura should store the block only upon successful application to the internal WSV to avoid storing a corrupted block. // Public-facing WSV update should happen after that and be followed by `BlockCommited` event to prevent client access to uncommitted data. - // TODO: Redundant clone Strategy::kura_store_block(&self.kura, committed_block); // Update WSV copy that is public facing diff --git a/core/src/wsv.rs b/core/src/wsv.rs index 2dbda577b00..77b25def56d 100644 --- a/core/src/wsv.rs +++ b/core/src/wsv.rs @@ -158,8 +158,8 @@ impl WorldStateView { pub fn account_assets( &self, id: &AccountId, - ) -> Result + '_, QueryExecutionFail> { - self.map_account(id, |account| account.assets.values().cloned()) + ) -> Result, QueryExecutionFail> { + self.map_account(id, |account| account.assets.values()) } /// Return a set of all permission tokens granted to this account. @@ -174,7 +174,7 @@ impl WorldStateView { let account = self.account(account_id)?; let mut tokens = self - .account_inherent_permission_tokens(account_id)? + .account_inherent_permission_tokens(account_id) .collect::>(); for role_id in &account.roles { @@ -194,12 +194,11 @@ impl WorldStateView { pub fn account_inherent_permission_tokens( &self, account_id: &AccountId, - ) -> Result, FindError> { + ) -> impl ExactSizeIterator { self.world .account_permission_tokens .get(account_id) - .ok_or_else(|| FindError::Account(account_id.clone())) - .map(std::collections::BTreeSet::iter) + .map_or_else(Default::default, std::collections::BTreeSet::iter) } /// Return `true` if [`Account`] contains a permission token not associated with any role. diff --git a/core/test_network/Cargo.toml b/core/test_network/Cargo.toml index cba81346fb7..713c1e3f596 100644 --- a/core/test_network/Cargo.toml +++ b/core/test_network/Cargo.toml @@ -9,6 +9,7 @@ license.workspace = true [dependencies] iroha = { workspace = true, features = ["test-network"] } +iroha_crypto = { workspace = true } iroha_client = { workspace = true } iroha_core = { workspace = true } iroha_config = { workspace = true } diff --git a/core/test_network/src/lib.rs b/core/test_network/src/lib.rs index 2ad7cbe6058..08e3fc981ae 100644 --- a/core/test_network/src/lib.rs +++ b/core/test_network/src/lib.rs @@ -9,10 +9,10 @@ use std::{ thread, }; -use eyre::{Error, Result}; +use eyre::Result; use futures::{prelude::*, stream::FuturesUnordered}; use iroha::Iroha; -use iroha_client::client::Client; +use iroha_client::client::{Client, QueryOutput}; use iroha_config::{ base::proxy::{LoadFromEnv, Override}, client::Configuration as ClientConfiguration, @@ -20,8 +20,8 @@ use iroha_config::{ sumeragi::Configuration as SumeragiConfiguration, torii::Configuration as ToriiConfiguration, }; -use iroha_core::{prelude::*, smartcontracts::query::Lazy}; -use iroha_data_model::{isi::Instruction, peer::Peer as DataModelPeer, prelude::*}; +use iroha_crypto::prelude::*; +use iroha_data_model::{isi::Instruction, peer::Peer as DataModelPeer, prelude::*, query::Query}; use iroha_genesis::{GenesisNetwork, RawGenesisBlock}; use iroha_logger::{Configuration as LoggerConfiguration, InstrumentFutures}; use iroha_primitives::addr::{socket_addr, SocketAddr}; @@ -660,7 +660,7 @@ impl PeerBuilder { type PeerWithRuntimeAndClient = (Runtime, Peer, Client); fn local_unique_port() -> Result { - Ok(socket_addr!(127.0.0.1: unique_port::get_unique_free_port().map_err(Error::msg)?)) + Ok(socket_addr!(127.0.0.1: unique_port::get_unique_free_port().map_err(eyre::Error::msg)?)) } /// Runtime used for testing. @@ -708,61 +708,61 @@ pub trait TestClient: Sized { /// /// # Errors /// If predicate is not satisfied, after maximum retries. - fn submit_till( + fn submit_till( &mut self, - instruction: impl Instruction + Debug, + instruction: impl Instruction + Debug + Clone, request: R, - f: impl Fn(&R::Output) -> bool, - ) -> eyre::Result + f: impl Fn(::Target) -> bool, + ) -> eyre::Result<()> where - R: ValidQuery + Into + Debug + Clone, - >::Error: Into, - R::Output: Lazy + Clone + Debug; + R::Output: QueryOutput, + ::Target: core::fmt::Debug, + >::Error: Into; /// Submits instructions with polling /// /// # Errors /// If predicate is not satisfied, after maximum retries. - fn submit_all_till( + fn submit_all_till( &mut self, instructions: Vec, request: R, - f: impl Fn(&R::Output) -> bool, - ) -> eyre::Result + f: impl Fn(::Target) -> bool, + ) -> eyre::Result<()> where - R: ValidQuery + Into + Debug + Clone, - >::Error: Into, - R::Output: Lazy + Clone + Debug; + R::Output: QueryOutput, + ::Target: core::fmt::Debug, + >::Error: Into; /// Polls request till predicate `f` is satisfied, with default period and max attempts. /// /// # Errors /// If predicate is not satisfied after maximum retries. - fn poll_request( + fn poll_request( &mut self, request: R, - f: impl Fn(&R::Output) -> bool, - ) -> eyre::Result + f: impl Fn(::Target) -> bool, + ) -> eyre::Result<()> where - R: ValidQuery + Into + Debug + Clone, - >::Error: Into, - R::Output: Lazy + Clone + Debug; + R::Output: QueryOutput, + ::Target: core::fmt::Debug, + >::Error: Into; /// Polls request till predicate `f` is satisfied with `period` and `max_attempts` supplied. /// /// # Errors /// If predicate is not satisfied after maximum retries. - fn poll_request_with_period( + fn poll_request_with_period( &mut self, request: R, period: Duration, max_attempts: u32, - f: impl Fn(&R::Output) -> bool, - ) -> eyre::Result + f: impl Fn(::Target) -> bool, + ) -> eyre::Result<()> where - R: ValidQuery + Into + Debug + Clone, - >::Error: Into, - R::Output: Lazy + Clone + Debug; + R::Output: QueryOutput, + ::Target: core::fmt::Debug, + >::Error: Into; } impl TestRuntime for Runtime { @@ -848,54 +848,54 @@ impl TestClient for Client { } } - fn submit_till( + fn submit_till( &mut self, - instruction: impl Instruction + Debug, + instruction: impl Instruction + Debug + Clone, request: R, - f: impl Fn(&R::Output) -> bool, - ) -> eyre::Result + f: impl Fn(::Target) -> bool, + ) -> eyre::Result<()> where - R: ValidQuery + Into + Debug + Clone, - >::Error: Into, - R::Output: Lazy + Clone + Debug, + R::Output: QueryOutput, + ::Target: core::fmt::Debug, + >::Error: Into, { self.submit(instruction) .expect("Failed to submit instruction."); self.poll_request(request, f) } - fn submit_all_till( + fn submit_all_till( &mut self, instructions: Vec, request: R, - f: impl Fn(&R::Output) -> bool, - ) -> eyre::Result + f: impl Fn(::Target) -> bool, + ) -> eyre::Result<()> where - R: ValidQuery + Into + Debug + Clone, - >::Error: Into, - R::Output: Lazy + Clone + Debug, + R::Output: QueryOutput, + ::Target: core::fmt::Debug, + >::Error: Into, { self.submit_all(instructions) .expect("Failed to submit instruction."); self.poll_request(request, f) } - fn poll_request_with_period( + fn poll_request_with_period( &mut self, request: R, period: Duration, max_attempts: u32, - f: impl Fn(&R::Output) -> bool, - ) -> eyre::Result + f: impl Fn(::Target) -> bool, + ) -> eyre::Result<()> where - R: ValidQuery + Into + Debug + Clone, - >::Error: Into, - R::Output: Lazy + Clone + Debug, + R::Output: QueryOutput, + ::Target: core::fmt::Debug, + >::Error: Into, { let mut query_result = None; for _ in 0..max_attempts { query_result = match self.request(request.clone()) { - Ok(result) if f(&result) => return Ok(result), + Ok(result) if f(result.clone()) => return Ok(()), result => Some(result), }; thread::sleep(period); @@ -903,15 +903,15 @@ impl TestClient for Client { Err(eyre::eyre!("Failed to wait for query request completion that would satisfy specified closure. Got this query result instead: {:?}", &query_result)) } - fn poll_request( + fn poll_request( &mut self, request: R, - f: impl Fn(&R::Output) -> bool, - ) -> eyre::Result + f: impl Fn(::Target) -> bool, + ) -> eyre::Result<()> where - R: ValidQuery + Into + Debug + Clone, - >::Error: Into, - R::Output: Lazy + Clone + Debug, + R::Output: QueryOutput, + ::Target: core::fmt::Debug, + >::Error: Into, { self.poll_request_with_period(request, Configuration::pipeline_time() / 2, 10, f) } diff --git a/data_model/cbindgen.toml b/data_model/cbindgen.toml new file mode 100644 index 00000000000..5d6b21a6ac3 --- /dev/null +++ b/data_model/cbindgen.toml @@ -0,0 +1,3 @@ +[parse.expand] +crates = ["iroha_data_model"] +features = ["ffi_export"] diff --git a/data_model/derive/tests/ui_fail/transparent_api_private_item.stderr b/data_model/derive/tests/ui_fail/transparent_api_private_item.stderr index 2ff8feb4121..74adf898403 100644 --- a/data_model/derive/tests/ui_fail/transparent_api_private_item.stderr +++ b/data_model/derive/tests/ui_fail/transparent_api_private_item.stderr @@ -5,7 +5,7 @@ error[E0603]: struct `QueryPayload` is private | ^^^^^^^^^^^^ private struct | note: the struct `QueryPayload` is defined here - --> $WORKSPACE/data_model/src/query.rs + --> $WORKSPACE/data_model/src/query/mod.rs | | pub use self::model::*; | ^^^^^^^^^^^ diff --git a/data_model/src/lib.rs b/data_model/src/lib.rs index b8a1e1dcf50..9d052542140 100644 --- a/data_model/src/lib.rs +++ b/data_model/src/lib.rs @@ -38,7 +38,6 @@ use evaluate::Evaluate; use events::FilterBox; use getset::Getters; use iroha_crypto::{HashOf, PublicKey}; -pub use iroha_crypto::{SignatureOf, SignaturesOf}; use iroha_data_model_derive::{ model, IdEqOrdHash, PartiallyTaggedDeserialize, PartiallyTaggedSerialize, VariantDiscriminant, }; @@ -50,7 +49,7 @@ use iroha_primitives::{ use iroha_schema::IntoSchema; pub use numeric::model::NumericValue; use parity_scale_codec::{Decode, Encode}; -use prelude::{Executable, TransactionQueryResult}; +use prelude::{Executable, TransactionQueryOutput}; use serde::{Deserialize, Serialize}; use serde_with::{DeserializeFromStr, SerializeDisplay}; use strum::EnumDiscriminants; @@ -71,16 +70,12 @@ pub mod isi; pub mod metadata; pub mod name; pub mod numeric; -#[cfg(feature = "http")] -pub mod pagination; pub mod peer; pub mod permission; #[cfg(feature = "http")] pub mod predicate; pub mod query; pub mod role; -#[cfg(feature = "http")] -pub mod sorting; pub mod transaction; pub mod trigger; pub mod validator; @@ -829,7 +824,7 @@ pub mod model { Identifiable(IdentifiableBox), PublicKey(PublicKey), SignatureCheckCondition(SignatureCheckCondition), - TransactionQueryResult(TransactionQueryResult), + TransactionQueryOutput(TransactionQueryOutput), PermissionToken(permission::PermissionToken), Hash(HashValue), Block(VersionedCommittedBlockWrapper), @@ -1114,7 +1109,7 @@ impl fmt::Display for Value { Value::Identifiable(v) => fmt::Display::fmt(&v, f), Value::PublicKey(v) => fmt::Display::fmt(&v, f), Value::SignatureCheckCondition(v) => fmt::Display::fmt(&v, f), - Value::TransactionQueryResult(_) => write!(f, "TransactionQueryResult"), + Value::TransactionQueryOutput(_) => write!(f, "TransactionQueryOutput"), Value::PermissionToken(v) => fmt::Display::fmt(&v, f), Value::Hash(v) => fmt::Display::fmt(&v, f), Value::Block(v) => fmt::Display::fmt(&**v, f), @@ -1143,7 +1138,7 @@ impl Value { | Identifiable(_) | String(_) | Name(_) - | TransactionQueryResult(_) + | TransactionQueryOutput(_) | PermissionToken(_) | Hash(_) | Block(_) @@ -1941,6 +1936,4 @@ pub mod prelude { LengthLimits, NumericValue, PredicateTrait, RegistrableBox, ToValue, TriggerBox, TryAsMut, TryAsRef, TryToValue, UpgradableBox, ValidationFail, ValidatorDeny, Value, }; - #[cfg(feature = "http")] - pub use super::{pagination::prelude::*, sorting::prelude::*}; } diff --git a/data_model/src/numeric.rs b/data_model/src/numeric.rs index 8164d5a8c34..f85a7131eb1 100644 --- a/data_model/src/numeric.rs +++ b/data_model/src/numeric.rs @@ -17,7 +17,7 @@ use serde::{ Deserializer, }; -use self::model::NumericValue; +pub use self::model::*; use super::{ DebugCustom, Decode, Deserialize, Display, Encode, FromVariant, IntoSchema, Serialize, }; diff --git a/data_model/src/pagination.rs b/data_model/src/pagination.rs deleted file mode 100644 index 44dfdaf831f..00000000000 --- a/data_model/src/pagination.rs +++ /dev/null @@ -1,122 +0,0 @@ -//! Structures and traits related to pagination. - -#[cfg(not(feature = "std"))] -use alloc::{ - borrow::ToOwned as _, - collections::btree_map, - format, - string::{String, ToString as _}, - vec, - vec::Vec, -}; -#[cfg(feature = "std")] -use std::collections::btree_map; - -use derive_more::{Constructor, Display}; -use iroha_data_model_derive::model; -use iroha_schema::IntoSchema; -use iroha_version::{Decode, Encode}; -use serde::{Deserialize, Serialize}; -use warp::{ - http::StatusCode, - reply::{self, Response}, - Reply, -}; - -pub use self::model::*; - -const PAGINATION_START: &str = "start"; -const PAGINATION_LIMIT: &str = "limit"; - -#[model] -pub mod model { - use super::*; - - /// Structure for pagination requests - #[derive( - Debug, - Display, - Clone, - Copy, - PartialEq, - Eq, - Default, - Constructor, - Deserialize, - Serialize, - Decode, - Encode, - IntoSchema, - )] - #[display( - fmt = "{}--{}", - "start.unwrap_or(0)", - "limit.map_or(\".inf\".to_owned(), |n| n.to_string())" - )] - pub struct Pagination { - /// start of indexing - pub start: Option, - /// limit of indexing - pub limit: Option, - } -} - -/// Error for pagination -#[derive(Debug, Display, Clone, Eq, PartialEq)] -#[display(fmt = "Failed to decode pagination. Error: {_0}")] -pub struct PaginateError(pub core::num::ParseIntError); - -#[cfg(feature = "std")] -impl std::error::Error for PaginateError {} - -impl Reply for PaginateError { - fn into_response(self) -> Response { - reply::with_status(self.to_string(), StatusCode::BAD_REQUEST).into_response() - } -} - -impl From for btree_map::BTreeMap { - fn from(pagination: Pagination) -> Self { - let mut query_params = Self::new(); - if let Some(start) = pagination.start { - query_params.insert(String::from(PAGINATION_START), start.to_string()); - } - if let Some(limit) = pagination.limit { - query_params.insert(String::from(PAGINATION_LIMIT), limit.to_string()); - } - query_params - } -} - -impl From for Vec<(&'static str, usize)> { - fn from(pagination: Pagination) -> Self { - match (pagination.start, pagination.limit) { - (Some(start), Some(limit)) => { - vec![ - ( - PAGINATION_START, - start.try_into().expect("u32 should always fit in usize"), - ), - ( - PAGINATION_LIMIT, - limit.try_into().expect("u32 should always fit in usize"), - ), - ] - } - (Some(start), None) => vec![( - PAGINATION_START, - start.try_into().expect("u32 should always fit in usize"), - )], - (None, Some(limit)) => vec![( - PAGINATION_LIMIT, - limit.try_into().expect("u32 should always fit in usize"), - )], - (None, None) => Vec::new(), - } - } -} - -pub mod prelude { - //! Prelude: re-export most commonly used traits, structs and macros from this module. - pub use super::*; -} diff --git a/data_model/src/predicate.rs b/data_model/src/predicate.rs index 49f89440b7a..a14c75de318 100644 --- a/data_model/src/predicate.rs +++ b/data_model/src/predicate.rs @@ -10,7 +10,7 @@ use crate::{IdBox, Name, Value}; mod nontrivial { use super::*; /// Struct representing a sequence with at least three elements. - #[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, IntoSchema)] + #[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] pub struct NonTrivial(Vec); impl NonTrivial { @@ -73,7 +73,7 @@ macro_rules! nontrivial { } /// Predicate combinator enum. -#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, IntoSchema)] +#[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] // Ideally we would enforce `P: PredicateTrait` here, but I // couldn't find a way to do it without polluting everything // downstream with explicit lifetimes, since we would need to @@ -282,7 +282,7 @@ pub mod string { use super::*; /// Predicate useful for processing [`String`]s and [`Name`]s. - #[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, IntoSchema)] + #[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] pub enum StringPredicate { /// Forward to [`str::contains()`] Contains(String), @@ -559,7 +559,7 @@ pub mod numerical { use super::*; /// A lower-inclusive range predicate. - #[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, IntoSchema)] + #[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] pub struct SemiInterval { /// The start of the range (inclusive) start: T, @@ -583,7 +583,7 @@ pub mod numerical { impl Copy for SemiInterval {} /// A both-inclusive range predicate - #[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, IntoSchema)] + #[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] pub struct Interval { /// The start of the range (inclusive) start: T, @@ -624,7 +624,7 @@ pub mod numerical { /// [`Self`] only applies to `Values` that are variants of /// compatible types. If the [`Range`] variant and the [`Value`] /// variant don't match defaults to `false`. - #[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, IntoSchema)] + #[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] pub enum SemiRange { /// 32-bit U32(SemiInterval), @@ -642,7 +642,7 @@ pub mod numerical { /// [`Self`] only applies to `Values` that are variants of /// compatible types. If the [`Range`] variant and the [`Value`] /// variant don't match defaults to `false`. - #[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, IntoSchema)] + #[derive(Debug, Clone, Decode, Encode, Deserialize, Serialize, IntoSchema)] pub enum Range { /// 32-bit U32(Interval), @@ -971,7 +971,7 @@ pub mod value { use super::*; /// A predicate designed for general processing of `Value`. - #[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, IntoSchema)] + #[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] pub enum ValuePredicate { /// Apply predicate to the [`Identifiable::Id`] and/or [`IdBox`]. Identifiable(string::StringPredicate), @@ -1107,14 +1107,14 @@ pub mod value { } /// A predicate that targets the particular `index` of a collection. - #[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, IntoSchema)] + #[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] pub struct AtIndex { index: u32, predicate: Box, } /// A predicate that targets the particular `key` of a collection. - #[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, IntoSchema)] + #[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] pub struct ValueOfKey { key: Name, predicate: Box, @@ -1124,7 +1124,7 @@ pub mod value { /// working with containers. Currently only /// [`Metadata`](crate::metadata::Metadata) and [`Vec`] are /// supported. - #[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, IntoSchema)] + #[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] pub enum Container { /// Forward to [`Iterator::any`] Any(Box), @@ -1267,7 +1267,9 @@ pub mod ip_addr { /// A Predicate containing independent octuplet masks to be /// applied to all elements of an IP version 4 address. - #[derive(Debug, Clone, Copy, Encode, Decode, IntoSchema, Serialize, Deserialize)] + #[derive( + Debug, Clone, Copy, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema, + )] pub struct Ipv4Predicate([Mask; 4]); impl PredicateTrait for Ipv4Predicate { @@ -1302,7 +1304,9 @@ pub mod ip_addr { /// A Predicate containing independent _hexadecuplets_ (u16 /// groups) masks to be applied to all elements of an IP version 6 /// address. - #[derive(Debug, Clone, Copy, Encode, Decode, IntoSchema, Serialize, Deserialize)] + #[derive( + Debug, Clone, Copy, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema, + )] pub struct Ipv6Predicate([Mask; 8]); impl PredicateTrait for Ipv6Predicate { diff --git a/data_model/src/query/cursor.rs b/data_model/src/query/cursor.rs new file mode 100644 index 00000000000..331adb73701 --- /dev/null +++ b/data_model/src/query/cursor.rs @@ -0,0 +1,57 @@ +//! Structures and traits related to server-side cursor. + +use core::num::{NonZeroU64, NonZeroUsize}; + +use iroha_data_model_derive::model; +use iroha_schema::IntoSchema; +use parity_scale_codec::{Decode, Encode}; +use serde::{Deserialize, Serialize}; + +pub use self::model::*; + +const CURSOR: &str = "cursor"; + +#[model] +pub mod model { + use super::*; + + /// Forward-only (a.k.a non-scrollable) cursor + #[derive( + Debug, + Clone, + Copy, + PartialEq, + Eq, + Default, + Decode, + Encode, + Deserialize, + Serialize, + IntoSchema, + )] + pub struct ForwardCursor { + pub cursor: Option, + } +} + +impl ForwardCursor { + /// Get cursor position + pub fn get(self) -> Option { + self.cursor + } +} + +impl From for Vec<(&'static str, NonZeroU64)> { + fn from(cursor: ForwardCursor) -> Self { + if let Some(cursor) = cursor.cursor { + return vec![(CURSOR, cursor)]; + } + + Vec::new() + } +} + +pub mod prelude { + //! Prelude: re-export most commonly used traits, structs and macros from this module. + pub use super::*; +} diff --git a/data_model/src/query.rs b/data_model/src/query/mod.rs similarity index 94% rename from data_model/src/query.rs rename to data_model/src/query/mod.rs index cff8fd721ff..7014edce06f 100644 --- a/data_model/src/query.rs +++ b/data_model/src/query/mod.rs @@ -6,14 +6,20 @@ use alloc::{boxed::Box, format, string::String, vec::Vec}; use core::cmp::Ordering; +#[cfg(feature = "http")] +pub use cursor::ForwardCursor; use derive_more::Display; -use iroha_crypto::SignatureOf; +use iroha_crypto::{PublicKey, SignatureOf}; use iroha_data_model_derive::model; use iroha_macro::FromVariant; use iroha_schema::IntoSchema; use iroha_version::prelude::*; +#[cfg(feature = "http")] +pub use pagination::Pagination; use parity_scale_codec::{Decode, Encode}; use serde::{Deserialize, Serialize}; +#[cfg(feature = "http")] +pub use sorting::Sorting; pub use self::model::*; use self::{ @@ -28,6 +34,13 @@ use crate::{ Identifiable, Value, }; +#[cfg(feature = "http")] +pub mod cursor; +#[cfg(feature = "http")] +pub mod pagination; +#[cfg(feature = "http")] +pub mod sorting; + macro_rules! queries { ($($($meta:meta)* $item:item)+) => { pub use self::model::*; @@ -170,24 +183,40 @@ pub mod model { )] #[getset(get = "pub")] #[ffi_type] - pub struct TransactionQueryResult { + pub struct TransactionQueryOutput { /// Transaction pub transaction: TransactionValue, /// The hash of the block to which `tx` belongs to pub block_hash: HashOf, } -} -/// Type returned from [`Metadata`] queries -pub struct MetadataValue(Value); + /// Type returned from [`Metadata`] queries + #[derive( + Debug, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Decode, + Encode, + Deserialize, + Serialize, + IntoSchema, + )] + #[ffi_type] + pub struct MetadataValue(pub Value); +} impl From for Value { + #[inline] fn from(source: MetadataValue) -> Self { source.0 } } impl From for MetadataValue { + #[inline] fn from(source: Value) -> Self { Self(source) } @@ -197,7 +226,7 @@ impl Query for QueryBox { type Output = Value; } -impl TransactionQueryResult { +impl TransactionQueryOutput { #[inline] /// Return payload of the transaction pub fn payload(&self) -> &TransactionPayload { @@ -205,14 +234,14 @@ impl TransactionQueryResult { } } -impl PartialOrd for TransactionQueryResult { +impl PartialOrd for TransactionQueryOutput { #[inline] fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } -impl Ord for TransactionQueryResult { +impl Ord for TransactionQueryOutput { #[inline] fn cmp(&self, other: &Self) -> Ordering { self.payload() @@ -229,6 +258,7 @@ pub mod role { use derive_more::Display; + use super::Query; use crate::prelude::*; queries! { @@ -315,6 +345,7 @@ pub mod permission { use derive_more::Display; + use super::Query; use crate::{permission, prelude::*}; queries! { @@ -399,7 +430,7 @@ pub mod account { use derive_more::Display; - use super::MetadataValue; + use super::{MetadataValue, Query}; use crate::prelude::*; queries! { @@ -561,7 +592,7 @@ pub mod asset { use iroha_data_model_derive::model; pub use self::model::*; - use super::MetadataValue; + use super::{MetadataValue, Query}; use crate::prelude::*; queries! { @@ -910,7 +941,7 @@ pub mod domain { use derive_more::Display; - use super::MetadataValue; + use super::{MetadataValue, Query}; use crate::prelude::*; queries! { @@ -1151,7 +1182,7 @@ pub mod transaction { use derive_more::Display; use iroha_crypto::HashOf; - use super::{Query, TransactionQueryResult}; + use super::{Query, TransactionQueryOutput}; use crate::{ account::AccountId, expression::EvaluatesTo, prelude::Account, transaction::VersionedSignedTransaction, @@ -1190,15 +1221,15 @@ pub mod transaction { } impl Query for FindAllTransactions { - type Output = Vec; + type Output = Vec; } impl Query for FindTransactionsByAccountId { - type Output = Vec; + type Output = Vec; } impl Query for FindTransactionByHash { - type Output = TransactionQueryResult; + type Output = TransactionQueryOutput; } impl FindTransactionsByAccountId { @@ -1296,19 +1327,23 @@ pub mod block { pub mod http { //! Structures related to sending queries over HTTP + use getset::Getters; use iroha_data_model_derive::model; pub use self::model::*; use super::*; - use crate::{ - account::AccountId, pagination::prelude::*, predicate::PredicateBox, sorting::prelude::*, - }; + use crate::{account::AccountId, predicate::PredicateBox}; + + // TODO: Could we make a variant of `Value` that holds only query results? + type QueryResult = Value; declare_versioned_with_scale!(VersionedSignedQuery 1..2, Debug, Clone, iroha_macro::FromVariant, IntoSchema); - declare_versioned_with_scale!(VersionedQueryResult 1..2, Debug, Clone, iroha_macro::FromVariant, IntoSchema); + declare_versioned_with_scale!(VersionedQueryResponse 1..2, Debug, Clone, iroha_macro::FromVariant, IntoSchema); #[model] pub mod model { + use core::num::NonZeroU64; + use super::*; /// I/O ready structure to send queries. @@ -1321,15 +1356,17 @@ pub mod http { } /// Payload of a query. - #[derive(Debug, Clone, Decode, Encode, Deserialize, Serialize, IntoSchema)] + #[derive( + Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema, + )] pub(crate) struct QueryPayload { /// Timestamp of the query creation. #[codec(compact)] pub timestamp_ms: u128, - /// Query definition. - pub query: QueryBox, /// Account id of the user who will sign this query. pub authority: AccountId, + /// Query definition. + pub query: QueryBox, /// The filter applied to the result on the server-side. pub filter: PredicateBox, } @@ -1338,10 +1375,23 @@ pub mod http { #[derive(Debug, Clone, Encode, Serialize, IntoSchema)] #[version_with_scale(n = 1, versioned = "VersionedSignedQuery")] pub struct SignedQuery { - /// Payload - pub payload: QueryPayload, /// Signature of the client who sends this query. pub signature: SignatureOf, + /// Payload + pub payload: QueryPayload, + } + + /// [`SignedQuery`] response + #[derive(Debug, Clone, Getters, Decode, Encode, Deserialize, Serialize, IntoSchema)] + #[version_with_scale(n = 1, versioned = "VersionedQueryResponse")] + #[getset(get = "pub")] + pub struct QueryResponse { + /// The result of the query execution. + #[getset(skip)] + pub result: QueryResult, + /// Index of the next element in the result set. Client will use this value + /// in the next request to continue fetching results of the original query + pub cursor: cursor::ForwardCursor, } } @@ -1352,8 +1402,8 @@ pub mod http { #[derive(Decode, Deserialize)] struct SignedQueryCandidate { - payload: QueryPayload, signature: SignatureOf, + payload: QueryPayload, } impl SignedQueryCandidate { @@ -1369,6 +1419,7 @@ pub mod http { }) } } + impl Decode for SignedQuery { fn decode(input: &mut I) -> Result { SignedQueryCandidate::decode(input)? @@ -1376,6 +1427,7 @@ pub mod http { .map_err(Into::into) } } + impl<'de> Deserialize<'de> for SignedQuery { fn deserialize(deserializer: D) -> Result where @@ -1414,19 +1466,6 @@ pub mod http { } } - /// Paginated Query Result - // TODO: This is the only structure whose inner fields are exposed. Wrap it in model macro? - #[derive(Debug, Clone, Decode, Encode, Deserialize, Serialize, IntoSchema)] - #[version_with_scale(n = 1, versioned = "VersionedQueryResult")] - pub struct QueryResult { - /// The result of the query execution. - pub result: Value, - /// pagination - pub pagination: Pagination, - /// sorting - pub sorting: Sorting, - } - impl QueryBuilder { /// Construct a new request with the `query`. pub fn new(query: impl Into, authority: AccountId) -> Self { @@ -1457,11 +1496,20 @@ pub mod http { pub fn sign( self, key_pair: iroha_crypto::KeyPair, - ) -> Result { - SignatureOf::new(key_pair, &self.payload).map(|signature| SignedQuery { - payload: self.payload, - signature, - }) + ) -> Result { + SignatureOf::new(key_pair, &self.payload) + .map(|signature| SignedQuery { + payload: self.payload, + signature, + }) + .map(Into::into) + } + } + + impl From for Value { + #[inline] + fn from(source: QueryResponse) -> Self { + source.result } } @@ -1469,7 +1517,7 @@ pub mod http { //! The prelude re-exports most commonly used traits, structs and macros from this crate. pub use super::{ - QueryBuilder, QueryResult, SignedQuery, VersionedQueryResult, VersionedSignedQuery, + QueryBuilder, QueryResponse, SignedQuery, VersionedQueryResponse, VersionedSignedQuery, }; } } @@ -1618,6 +1666,6 @@ pub mod prelude { pub use super::{ account::prelude::*, asset::prelude::*, block::prelude::*, domain::prelude::*, peer::prelude::*, permission::prelude::*, role::prelude::*, transaction::*, - trigger::prelude::*, Query, QueryBox, TransactionQueryResult, + trigger::prelude::*, QueryBox, TransactionQueryOutput, }; } diff --git a/data_model/src/query/pagination.rs b/data_model/src/query/pagination.rs new file mode 100644 index 00000000000..71a12d95f8c --- /dev/null +++ b/data_model/src/query/pagination.rs @@ -0,0 +1,60 @@ +//! Structures and traits related to pagination. + +#[cfg(not(feature = "std"))] +use alloc::{ + borrow::ToOwned as _, + collections::btree_map, + format, + string::{String, ToString as _}, + vec, + vec::Vec, +}; +use core::num::{NonZeroU32, NonZeroU64, NonZeroUsize}; +#[cfg(feature = "std")] +use std::collections::btree_map; + +use derive_more::{Constructor, Display}; +use iroha_data_model_derive::model; +use parity_scale_codec::{Decode, Encode}; +use serde::{Deserialize, Serialize}; +use warp::{ + http::StatusCode, + reply::{self, Response}, + Reply, +}; + +const PAGINATION_START: &str = "start"; +const PAGINATION_LIMIT: &str = "limit"; + +/// Structure for pagination requests +#[derive(Debug, Display, Clone, Copy, Default, Decode, Encode, Deserialize, Serialize)] +#[display( + fmt = "{}--{}", + "start.map(NonZeroU64::get).unwrap_or(0)", + "limit.map_or(\".inf\".to_owned(), |n| n.to_string())" +)] +pub struct Pagination { + /// limit of indexing + pub limit: Option, + /// start of indexing + // TODO: Rename to offset + pub start: Option, +} + +impl From for Vec<(&'static str, NonZeroU64)> { + fn from(pagination: Pagination) -> Self { + match (pagination.start, pagination.limit) { + (Some(start), Some(limit)) => { + vec![(PAGINATION_LIMIT, limit.into()), (PAGINATION_START, start)] + } + (Some(start), None) => vec![(PAGINATION_START, start)], + (None, Some(limit)) => vec![(PAGINATION_LIMIT, limit.into())], + (None, None) => Vec::new(), + } + } +} + +pub mod prelude { + //! Prelude: re-export most commonly used traits, structs and macros from this module. + pub use super::*; +} diff --git a/data_model/src/sorting.rs b/data_model/src/query/sorting.rs similarity index 77% rename from data_model/src/sorting.rs rename to data_model/src/query/sorting.rs index 0ceac5a86e2..9ccbc83c610 100644 --- a/data_model/src/sorting.rs +++ b/data_model/src/query/sorting.rs @@ -8,12 +8,11 @@ use alloc::{ }; use iroha_data_model_derive::model; -use iroha_schema::IntoSchema; -use iroha_version::{Decode, Encode}; +use parity_scale_codec::{Decode, Encode}; use serde::{Deserialize, Serialize}; pub use self::model::*; -use crate::prelude::*; +use crate::{name::Name, prelude::*}; const SORT_BY_KEY: &str = "sort_by_metadata_key"; @@ -21,8 +20,8 @@ const SORT_BY_KEY: &str = "sort_by_metadata_key"; pub mod model { use super::*; - /// Enum for sorting requests - #[derive(Debug, Clone, Default, Decode, Encode, Deserialize, Serialize, IntoSchema)] + /// Struct for sorting requests + #[derive(Debug, Clone, Default, Decode, Encode, Deserialize, Serialize)] pub struct Sorting { /// Sort query result using [`Name`] of the key in [`Asset`]'s metadata. pub sort_by_metadata_key: Option, @@ -38,13 +37,13 @@ impl Sorting { } } -impl From for Vec<(&'static str, String)> { +impl From for Vec<(&'static str, Name)> { fn from(sorting: Sorting) -> Self { - let mut vec = Vec::new(); if let Some(key) = sorting.sort_by_metadata_key { - vec.push((SORT_BY_KEY, key.to_string())); + return vec![(SORT_BY_KEY, key)]; } - vec + + Vec::new() } } diff --git a/data_model/src/transaction.rs b/data_model/src/transaction.rs index 482051477e5..43551f1b44f 100644 --- a/data_model/src/transaction.rs +++ b/data_model/src/transaction.rs @@ -102,20 +102,20 @@ pub mod model { #[getset(get = "pub")] #[ffi_type] pub struct TransactionPayload { - /// Account ID of transaction creator. - pub authority: AccountId, /// Creation timestamp (unix time in milliseconds). #[getset(skip)] pub creation_time_ms: u64, + /// Account ID of transaction creator. + pub authority: AccountId, + /// ISI or a `WebAssembly` smartcontract. + pub instructions: Executable, + /// If transaction is not committed by this time it will be dropped. + #[getset(skip)] + pub time_to_live_ms: Option, /// Random value to make different hashes for transactions which occur repeatedly and simultaneously. // TODO: Only temporary #[getset(skip)] pub nonce: Option, - /// If transaction is not committed by this time it will be dropped. - #[getset(skip)] - pub time_to_live_ms: Option, - /// ISI or a `WebAssembly` smartcontract. - pub instructions: Executable, /// Store for additional information. #[getset(skip)] pub metadata: UnlimitedMetadata, @@ -163,10 +163,10 @@ pub mod model { #[ffi_type] // TODO: All fields in this struct should be private pub struct SignedTransaction { - /// [`Transaction`] payload. - pub payload: TransactionPayload, /// [`iroha_crypto::SignatureOf`]<[`TransactionPayload`]>. pub signatures: SignaturesOf, + /// [`Transaction`] payload. + pub payload: TransactionPayload, } /// Transaction Value used in Instructions and Queries @@ -374,8 +374,8 @@ mod candidate { #[derive(Decode, Deserialize)] struct SignedTransactionCandidate { - payload: TransactionPayload, signatures: SignaturesOf, + payload: TransactionPayload, } impl SignedTransactionCandidate { diff --git a/data_model/src/visit.rs b/data_model/src/visit.rs index 3f60589144a..18416c7a732 100644 --- a/data_model/src/visit.rs +++ b/data_model/src/visit.rs @@ -4,6 +4,8 @@ #[cfg(not(feature = "std"))] use alloc::format; +use iroha_crypto::PublicKey; + use crate::{evaluate::ExpressionEvaluator, prelude::*, NumericValue}; macro_rules! delegate { diff --git a/docs/source/references/config.md b/docs/source/references/config.md index 4b781ccfbde..463433b3a81 100644 --- a/docs/source/references/config.md +++ b/docs/source/references/config.md @@ -56,7 +56,9 @@ The following is the default configuration used by Iroha. "API_URL": null, "TELEMETRY_URL": null, "MAX_TRANSACTION_SIZE": 32768, - "MAX_CONTENT_LEN": 16384000 + "MAX_CONTENT_LEN": 16384000, + "FETCH_SIZE": 10, + "QUERY_IDLE_TIME_MS": 30000 }, "BLOCK_SYNC": { "GOSSIP_PERIOD_MS": 10000, @@ -624,9 +626,11 @@ Has type `Option`[^1]. Can be configured via environm ```json { "API_URL": null, + "FETCH_SIZE": 10, "MAX_CONTENT_LEN": 16384000, "MAX_TRANSACTION_SIZE": 32768, "P2P_ADDR": null, + "QUERY_IDLE_TIME_MS": 30000, "TELEMETRY_URL": null } ``` @@ -641,6 +645,16 @@ Has type `Option`[^1]. Can be configured via environment variable `T null ``` +### `torii.fetch_size` + +How many query results are returned in one batch + +Has type `Option`[^1]. Can be configured via environment variable `TORII_FETCH_SIZE` + +```json +10 +``` + ### `torii.max_content_len` Maximum number of bytes in raw message. Used to prevent from DOS attacks. @@ -671,6 +685,16 @@ Has type `Option`[^1]. Can be configured via environment variable `T null ``` +### `torii.query_idle_time_ms` + +Time query can remain in the store if unaccessed + +Has type `Option`[^1]. Can be configured via environment variable `TORII_QUERY_IDLE_TIME_MS` + +```json +30000 +``` + ### `torii.telemetry_url` Torii address for reporting internal status and metrics for administration. diff --git a/docs/source/references/schema.json b/docs/source/references/schema.json index 725369c0324..810f9700839 100644 --- a/docs/source/references/schema.json +++ b/docs/source/references/schema.json @@ -2067,6 +2067,14 @@ "decimal_places": 9 } }, + "ForwardCursor": { + "Struct": [ + { + "name": "cursor", + "type": "Option>" + } + ] + }, "GenericPredicateBox": { "Enum": [ { @@ -2998,9 +3006,6 @@ "Option": { "Option": "IpfsPath" }, - "Option": { - "Option": "Name" - }, "Option>": { "Option": "NonZero" }, @@ -3019,9 +3024,6 @@ "Option": { "Option": "TransactionRejectionReason" }, - "Option": { - "Option": "u32" - }, "Or": { "Struct": [ { @@ -3041,18 +3043,6 @@ "OriginFilter": "PeerId", "OriginFilter": "RoleId", "OriginFilter": "TriggerId", - "Pagination": { - "Struct": [ - { - "name": "start", - "type": "Option" - }, - { - "name": "limit", - "type": "Option" - } - ] - }, "Pair": { "Struct": [ { @@ -3564,33 +3554,29 @@ "name": "timestamp_ms", "type": "Compact" }, - { - "name": "query", - "type": "QueryBox" - }, { "name": "authority", "type": "AccountId" }, + { + "name": "query", + "type": "QueryBox" + }, { "name": "filter", "type": "GenericPredicateBox" } ] }, - "QueryResult": { + "QueryResponse": { "Struct": [ { "name": "result", "type": "Value" }, { - "name": "pagination", - "type": "Pagination" - }, - { - "name": "sorting", - "type": "Sorting" + "name": "cursor", + "type": "ForwardCursor" } ] }, @@ -3914,25 +3900,25 @@ }, "SignedQuery": { "Struct": [ - { - "name": "payload", - "type": "QueryPayload" - }, { "name": "signature", "type": "SignatureOf" + }, + { + "name": "payload", + "type": "QueryPayload" } ] }, "SignedTransaction": { "Struct": [ - { - "name": "payload", - "type": "TransactionPayload" - }, { "name": "signatures", "type": "SignaturesOf" + }, + { + "name": "payload", + "type": "TransactionPayload" } ] }, @@ -4060,14 +4046,6 @@ "SortedVec>": { "Vec": "SignatureOf" }, - "Sorting": { - "Struct": [ - { - "name": "sort_by_metadata_key", - "type": "Option" - } - ] - }, "String": "String", "StringPredicate": { "Enum": [ @@ -4152,25 +4130,25 @@ }, "TransactionPayload": { "Struct": [ - { - "name": "authority", - "type": "AccountId" - }, { "name": "creation_time_ms", "type": "u64" }, { - "name": "nonce", - "type": "Option>" + "name": "authority", + "type": "AccountId" + }, + { + "name": "instructions", + "type": "Executable" }, { "name": "time_to_live_ms", "type": "Option>" }, { - "name": "instructions", - "type": "Executable" + "name": "nonce", + "type": "Option>" }, { "name": "metadata", @@ -4178,7 +4156,7 @@ } ] }, - "TransactionQueryResult": { + "TransactionQueryOutput": { "Struct": [ { "name": "transaction", @@ -4230,7 +4208,7 @@ "TransactionValue": { "Struct": [ { - "name": "tx", + "name": "value", "type": "VersionedSignedTransaction" }, { @@ -4537,9 +4515,9 @@ "type": "SignatureCheckCondition" }, { - "tag": "TransactionQueryResult", + "tag": "TransactionQueryOutput", "discriminant": 12, - "type": "TransactionQueryResult" + "type": "TransactionQueryOutput" }, { "tag": "PermissionToken", @@ -4634,7 +4612,7 @@ "discriminant": 11 }, { - "tag": "TransactionQueryResult", + "tag": "TransactionQueryOutput", "discriminant": 12 }, { @@ -4795,12 +4773,12 @@ } ] }, - "VersionedQueryResult": { + "VersionedQueryResponse": { "Enum": [ { "tag": "V1", "discriminant": 1, - "type": "QueryResult" + "type": "QueryResponse" } ] }, diff --git a/lints.toml b/lints.toml index 6548fba5b6a..b36eb51c845 100644 --- a/lints.toml +++ b/lints.toml @@ -59,16 +59,8 @@ allow = [ 'clippy::let_underscore_must_use', 'clippy::match_wildcard_for_single_variants', 'clippy::missing_docs_in_private_items', - # Not all public items should be inline. We only inline **trivial** functions. - 'clippy::missing_inline_in_public_items', 'clippy::module_name_repetitions', - 'clippy::must_use_candidate', 'clippy::pattern_type_mismatch', - 'clippy::semicolon_if_nothing_returned', - 'clippy::non-ascii-literal', - 'clippy::wildcard_enum_match_arm', - 'clippy::wildcard_imports', - 'clippy::pub_use', 'clippy::shadow_reuse', 'clippy::shadow_same', @@ -76,14 +68,11 @@ allow = [ 'clippy::unwrap_in_result', 'clippy::expect_used', 'clippy::unreachable', - 'clippy::use_self', 'clippy::wildcard_enum_match_arm', 'clippy::wildcard_imports', - 'elided_lifetimes_in_paths', # Our preferred style. 'clippy::non-ascii-literal', 'clippy::std_instead_of_core', - 'clippy::uninlined_format_args', # This lint could be useful in theory. The trade-off of making # refactoring away from references difficult isn't worth it in all diff --git a/macro/derive/src/lib.rs b/macro/derive/src/lib.rs index 54f548203b1..acc2406b009 100644 --- a/macro/derive/src/lib.rs +++ b/macro/derive/src/lib.rs @@ -176,9 +176,9 @@ fn try_into_variant( quote! { impl #impl_generics TryFrom<#enum_ty #ty_generics> for #variant_ty #where_clause { - type Error = iroha_macro::error::ErrorTryFromEnum<#enum_ty, Self>; + type Error = iroha_macro::error::ErrorTryFromEnum<#enum_ty #ty_generics, Self>; - fn try_from(origin: #enum_ty) -> core::result::Result { + fn try_from(origin: #enum_ty #ty_generics) -> core::result::Result { if let #enum_ty :: #variant(variant) = origin { Ok(variant) } else { diff --git a/macro/utils/src/lib.rs b/macro/utils/src/lib.rs index 6cf0d0eda78..9fdfd48e247 100644 --- a/macro/utils/src/lib.rs +++ b/macro/utils/src/lib.rs @@ -1,9 +1,7 @@ //! Module for various functions and structs to build macros in iroha. -use syn::parse::Parse; - /// Trait for attribute parsing generalization -pub trait AttrParser { +pub trait AttrParser { /// Attribute identifier `#[IDENT...]` const IDENT: &'static str; diff --git a/schema/gen/src/lib.rs b/schema/gen/src/lib.rs index 8543817b32b..e10bd37fa65 100644 --- a/schema/gen/src/lib.rs +++ b/schema/gen/src/lib.rs @@ -47,7 +47,7 @@ pub fn build_schemas() -> MetaMap { VersionedBlockSubscriptionRequest, VersionedEventMessage, VersionedEventSubscriptionRequest, - VersionedQueryResult, + VersionedQueryResponse, VersionedSignedQuery, // Never referenced, but present in type signature. Like `PhantomData` @@ -282,8 +282,7 @@ types!( OriginFilter, OriginFilter, OriginFilter, - QueryResult, - Pagination, + QueryResponse, Pair, Parameter, ParameterId, @@ -337,7 +336,6 @@ types!( SignaturesOf, SignedQuery, SignedTransaction, - Sorting, String, StringPredicate, Subtract, @@ -348,7 +346,7 @@ types!( TransactionLimitError, TransactionLimits, TransactionPayload, - TransactionQueryResult, + TransactionQueryOutput, TransactionRejectionReason, TransactionValue, TransferBox, @@ -378,7 +376,7 @@ types!( VersionedCommittedBlockWrapper, VersionedEventMessage, VersionedEventSubscriptionRequest, - VersionedQueryResult, + VersionedQueryResponse, VersionedSignedQuery, VersionedSignedTransaction, WasmExecutionFail, diff --git a/tools/kagami/src/docs.rs b/tools/kagami/src/docs.rs index 2c6ac4d5ae4..b734e9c1726 100644 --- a/tools/kagami/src/docs.rs +++ b/tools/kagami/src/docs.rs @@ -29,8 +29,8 @@ where { fn get_markdown(writer: &mut W) -> color_eyre::Result<()> { let Value::Object(docs) = Self::get_docs() else { - unreachable!("As top level structure is always object") - }; + unreachable!("As top level structure is always object") + }; let mut vec = Vec::new(); let defaults = serde_json::to_string_pretty(&Self::default())?; diff --git a/version/derive/src/lib.rs b/version/derive/src/lib.rs index eacafabf91f..91d7867260b 100644 --- a/version/derive/src/lib.rs +++ b/version/derive/src/lib.rs @@ -112,13 +112,18 @@ pub fn declare_versioned_with_json(input: TokenStream) -> TokenStream { } fn impl_version(args: Vec, item: TokenStream) -> TokenStream2 { - let (item, struct_name) = if let Ok(item_struct) = syn::parse::(item.clone()) { - (quote!(#item_struct), item_struct.ident) - } else if let Ok(item_enum) = syn::parse::(item) { - (quote!(#item_enum), item_enum.ident) - } else { - abort_call_site!("The attribute should be attached to either struct or enum."); - }; + let (item, struct_name, generics) = + if let Ok(item_struct) = syn::parse::(item.clone()) { + ( + quote!(#item_struct), + item_struct.ident, + item_struct.generics, + ) + } else if let Ok(item_enum) = syn::parse::(item) { + (quote!(#item_enum), item_enum.ident, item_enum.generics) + } else { + abort_call_site!("The attribute should be attached to either struct or enum."); + }; let args_map: FxHashMap<_, _> = args .into_iter() .filter_map(|meta| { @@ -173,10 +178,12 @@ fn impl_version(args: Vec, item: TokenStream) -> TokenStream2 { ) }; let alias_type_name = format_ident!("_{}V{}", versioned_struct_name, version_number); + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + quote!( /// Autogenerated alias type to link versioned item to its container. #[allow(clippy::redundant_pub_crate)] - pub(crate) type #alias_type_name = #struct_name; + pub(crate) type #alias_type_name #impl_generics = #struct_name #ty_generics #where_clause; #item ) @@ -236,10 +243,24 @@ impl Parse for DeclareVersionedArgs { } fn impl_decode_versioned(enum_name: &Ident, generics: &syn::Generics) -> proc_macro2::TokenStream { - let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + let mut decode_where_clause = generics + .where_clause + .clone() + .unwrap_or_else(|| syn::parse_quote!(where)); + decode_where_clause + .predicates + .push(syn::parse_quote!(Self: parity_scale_codec::DecodeAll)); + let mut encode_where_clause = generics + .where_clause + .clone() + .unwrap_or_else(|| syn::parse_quote!(where)); + encode_where_clause + .predicates + .push(syn::parse_quote!(Self: parity_scale_codec::Encode)); + let (impl_generics, ty_generics, _) = generics.split_for_impl(); quote! ( - impl #impl_generics iroha_version::scale::DecodeVersioned for #enum_name #ty_generics #where_clause { + impl #impl_generics iroha_version::scale::DecodeVersioned for #enum_name #ty_generics #decode_where_clause { fn decode_all_versioned(input: &[u8]) -> iroha_version::error::Result { use iroha_version::{error::Error, Version, UnsupportedVersion, RawVersioned}; use parity_scale_codec::DecodeAll; @@ -260,7 +281,7 @@ fn impl_decode_versioned(enum_name: &Ident, generics: &syn::Generics) -> proc_ma } } - impl #impl_generics iroha_version::scale::EncodeVersioned for #enum_name #ty_generics #where_clause { + impl #impl_generics iroha_version::scale::EncodeVersioned for #enum_name #ty_generics #encode_where_clause { fn encode_versioned(&self) -> Vec { use parity_scale_codec::Encode; diff --git a/version/src/lib.rs b/version/src/lib.rs index 8299e01a9e9..841197968f8 100644 --- a/version/src/lib.rs +++ b/version/src/lib.rs @@ -197,12 +197,12 @@ pub mod scale { #[cfg(not(feature = "std"))] use alloc::vec::Vec; - use parity_scale_codec::{Decode, Encode}; + use parity_scale_codec::{DecodeAll, Encode}; use super::{error::Result, Version}; /// [`Decode`] versioned analog. - pub trait DecodeVersioned: Decode + Version { + pub trait DecodeVersioned: DecodeAll + Version { /// Use this function for versioned objects instead of `decode_all`. /// /// # Errors diff --git a/wasm/src/lib.rs b/wasm/src/lib.rs index 96b5b4f2762..c79d3a4b1ea 100644 --- a/wasm/src/lib.rs +++ b/wasm/src/lib.rs @@ -14,7 +14,12 @@ extern crate alloc; use alloc::{boxed::Box, collections::BTreeMap, format, vec::Vec}; use core::ops::RangeFrom; -use data_model::{isi::Instruction, prelude::*, query::QueryBox, validator::NeedsValidationBox}; +use data_model::{ + isi::Instruction, + prelude::*, + query::{Query, QueryBox}, + validator::NeedsValidationBox, +}; use debug::DebugExpectExt as _; pub use iroha_data_model as data_model; pub use iroha_wasm_derive::main; diff --git a/wasm/validator/derive/src/validate.rs b/wasm/validator/derive/src/validate.rs index ae8f9ee74a2..881a47294bc 100644 --- a/wasm/validator/derive/src/validate.rs +++ b/wasm/validator/derive/src/validate.rs @@ -58,7 +58,9 @@ impl ValidateAttribute { continue; } - let Some(proc_macro2::TokenTree::Group(group))= attribute.tokens.clone().into_iter().next() else { + let Some(proc_macro2::TokenTree::Group(group)) = + attribute.tokens.clone().into_iter().next() + else { panic!("Expected parentheses group"); }; assert!(