diff --git a/Cargo.lock b/Cargo.lock index c808f60f5cff62c148dc3c7fef81e6175427f6f2..55b25b2160f0040f036f0748530228a78dbe3811 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -98,6 +98,12 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +[[package]] +name = "ascii" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eab1c04a571841102f5345a8fc0f6bb3d31c315dec879b5c6e42e40ce7ffa34e" + [[package]] name = "async-lock" version = "2.5.0" @@ -342,6 +348,19 @@ dependencies = [ "os_str_bytes", ] +[[package]] +name = "combine" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3da6baa321ec19e1cc41d31bf599f00c783d0517095cdaf0332e3fe8d20680" +dependencies = [ + "ascii", + "byteorder", + "either", + "memchr", + "unreachable", +] + [[package]] name = "constant_time_eq" version = "0.1.5" @@ -583,6 +602,15 @@ version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" +[[package]] +name = "encoding_rs" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +dependencies = [ + "cfg-if", +] + [[package]] name = "env_logger" version = "0.9.0" @@ -614,6 +642,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +[[package]] +name = "fastrand" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +dependencies = [ + "instant", +] + [[package]] name = "fixed-hash" version = "0.7.0" @@ -632,6 +669,31 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + [[package]] name = "frame-metadata" version = "15.0.0" @@ -753,9 +815,12 @@ dependencies = [ "anyhow", "clap", "env_logger", + "graphql_client", "hex", "logs", "parity-scale-codec", + "reqwest", + "serde", "serde_json", "sp-core", "subxt", @@ -811,6 +876,84 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" +[[package]] +name = "graphql-introspection-query" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2a4732cf5140bd6c082434494f785a19cfb566ab07d1382c3671f5812fed6d" +dependencies = [ + "serde", +] + +[[package]] +name = "graphql-parser" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ebc8013b4426d5b81a4364c419a95ed0b404af2b82e2457de52d9348f0e474" +dependencies = [ + "combine", + "thiserror", +] + +[[package]] +name = "graphql_client" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fc16d75d169fddb720d8f1c7aed6413e329e1584079b9734ff07266a193f5bc" +dependencies = [ + "graphql_query_derive", + "reqwest", + "serde", + "serde_json", +] + +[[package]] +name = "graphql_client_codegen" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f290ecfa3bea3e8a157899dc8a1d96ee7dd6405c18c8ddd213fc58939d18a0e9" +dependencies = [ + "graphql-introspection-query", + "graphql-parser", + "heck", + "lazy_static", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn", +] + +[[package]] +name = "graphql_query_derive" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a755cc59cda2641ea3037b4f9f7ef40471c329f55c1fa2db6fa0bb7ae6c1f7ce" +dependencies = [ + "graphql_client_codegen", + "proc-macro2", + "syn", +] + +[[package]] +name = "h2" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hash-db" version = "0.15.2" @@ -898,18 +1041,72 @@ dependencies = [ "itoa 1.0.3", ] +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + [[package]] name = "httparse" version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "hyper" +version = "0.14.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa 1.0.3", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "iana-time-zone" version = "0.1.41" @@ -929,6 +1126,17 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "impl-codec" version = "0.6.0" @@ -968,6 +1176,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + [[package]] name = "integer-sqrt" version = "0.1.5" @@ -977,6 +1194,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "ipnet" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" + [[package]] name = "itoa" version = "0.4.8" @@ -1168,6 +1391,12 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + [[package]] name = "memchr" version = "2.5.0" @@ -1203,6 +1432,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + [[package]] name = "miniz_oxide" version = "0.5.3" @@ -1224,6 +1459,24 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "native-tls" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nodrop" version = "0.1.14" @@ -1319,12 +1572,51 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "openssl" +version = "0.10.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "618febf65336490dfcf20b73f885f5651a0c89c64c2d4a8c3662585a70bf5bd0" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "openssl-probe" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "openssl-sys" +version = "0.9.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5f9bd0c2710541a3cda73d6f9ac4f1b240de4ae261065d309dbe73d9dceb42f" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "os_str_bytes" version = "6.2.0" @@ -1436,6 +1728,12 @@ dependencies = [ "crypto-mac 0.11.1", ] +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + [[package]] name = "pin-project" version = "1.0.11" @@ -1468,6 +1766,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" + [[package]] name = "ppv-lite86" version = "0.2.16" @@ -1682,6 +1986,52 @@ version = "0.6.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "reqwest" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75aa69a3f06bbcc66ede33af2af253c6f7a86b1ca0033f60c580a27074fbf92" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "lazy_static", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + [[package]] name = "ring" version = "0.16.20" @@ -1905,6 +2255,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa 1.0.3", + "ryu", + "serde", +] + [[package]] name = "sha-1" version = "0.9.8" @@ -2454,6 +2816,20 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + [[package]] name = "termcolor" version = "1.1.3" @@ -2550,7 +2926,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581" dependencies = [ "autocfg", + "bytes", "libc", + "memchr", "mio", "once_cell", "pin-project-lite", @@ -2570,6 +2948,16 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.23.4" @@ -2593,6 +2981,7 @@ dependencies = [ "futures-sink", "pin-project-lite", "tokio", + "tracing", ] [[package]] @@ -2604,6 +2993,12 @@ dependencies = [ "serde", ] +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + [[package]] name = "tracing" version = "0.1.36" @@ -2702,6 +3097,12 @@ dependencies = [ "hash-db", ] +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + [[package]] name = "twox-hash" version = "1.6.3" @@ -2732,6 +3133,12 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + [[package]] name = "unicode-ident" version = "1.0.3" @@ -2753,24 +3160,67 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" +[[package]] +name = "unreachable" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" +dependencies = [ + "void", +] + [[package]] name = "untrusted" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + [[package]] name = "valuable" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" @@ -2814,6 +3264,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa76fb221a1f8acddf5b54ace85912606980ad661ac7a503b4570ffd3a624dad" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.82" @@ -2970,6 +3432,15 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + [[package]] name = "wyz" version = "0.5.0" diff --git a/Cargo.toml b/Cargo.toml index 4db670852ac85208845f874b0b9d7af8875c35c5..532712fc22be46f5204885b5c52c973b4076004a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,9 +10,12 @@ version = "0.1.0" anyhow = "1.0" clap = { version = "3.0", features = ["derive"] } env_logger = "0.9.0" +graphql_client = { version = "0.11.0", features = ["reqwest"] } hex = "0.4.3" logs = "0.5" codec = { package = "parity-scale-codec", version = "3.1.5" } +reqwest = "0.11.11" +serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.64" sp-core = { git = "https://github.com/duniter/substrate", branch = "duniter-substrate-v0.9.23" } subxt = { git = 'https://github.com/duniter/subxt.git', branch = 'duniter-substrate-v0.9.23' } diff --git a/README.md b/README.md index ad00aeb7193d2626799825f05f729ca4a0852547..8bf013f029d98b68a1fdf99ff36355f8f9db8429 100644 --- a/README.md +++ b/README.md @@ -11,3 +11,7 @@ Send 10 ÄžD from Alice to Ferdie: If using a different runtime, update the metadata for the client to compile: subxt metadata -f bytes > res/metadata.scale + +List certifications and session keys that will expire within one month: + + cargo run -- --url wss://gdev.librelois.fr:443/ws --secret //Alice expire --blocks 432000 diff --git a/res/indexer-queries.graphql b/res/indexer-queries.graphql new file mode 100644 index 0000000000000000000000000000000000000000..0e979ae09a076c346ad6848380f9c702e64ebabe --- /dev/null +++ b/res/indexer-queries.graphql @@ -0,0 +1,5 @@ +query IdentityNameByPubkey($pubkey: String!) { + identity_by_pk(id: $pubkey) { + name + } +} diff --git a/res/indexer-schema.graphql b/res/indexer-schema.graphql new file mode 100644 index 0000000000000000000000000000000000000000..4295c5b1087e564d3b38acf902d6f1eace0e7084 --- /dev/null +++ b/res/indexer-schema.graphql @@ -0,0 +1,2026 @@ +schema { + query: query_root + subscription: subscription_root +} + +"""whether this query should be cached (Hasura Cloud only)""" +directive @cached( + """measured in seconds""" + ttl: Int! = 60 + + """refresh the cache entry""" + refresh: Boolean! = false +) on QUERY + +"""Table of accounts.""" +type account { + """An object relationship""" + block: block! + created_at: timestamptz! + + """Block number where account was created.""" + created_on: Int! + + """ + A computed field, executes function "has_identity" + """ + has_identity: Boolean + + """Pubkey of the account.""" + id: String! + + """An object relationship""" + identity: identity + killed_at: timestamptz + + """Block number where account was killed.""" + killed_on: Int + + """An array relationship""" + transactions_issued( + """distinct select on columns""" + distinct_on: [transaction_select_column!] + + """limit the number of rows returned""" + limit: Int + + """skip the first n rows. Use only with order_by""" + offset: Int + + """sort the rows by one or more columns""" + order_by: [transaction_order_by!] + + """filter the rows returned""" + where: transaction_bool_exp + ): [transaction!]! + + """An aggregate relationship""" + transactions_issued_aggregate( + """distinct select on columns""" + distinct_on: [transaction_select_column!] + + """limit the number of rows returned""" + limit: Int + + """skip the first n rows. Use only with order_by""" + offset: Int + + """sort the rows by one or more columns""" + order_by: [transaction_order_by!] + + """filter the rows returned""" + where: transaction_bool_exp + ): transaction_aggregate! + + """An array relationship""" + transactions_received( + """distinct select on columns""" + distinct_on: [transaction_select_column!] + + """limit the number of rows returned""" + limit: Int + + """skip the first n rows. Use only with order_by""" + offset: Int + + """sort the rows by one or more columns""" + order_by: [transaction_order_by!] + + """filter the rows returned""" + where: transaction_bool_exp + ): [transaction!]! + + """An aggregate relationship""" + transactions_received_aggregate( + """distinct select on columns""" + distinct_on: [transaction_select_column!] + + """limit the number of rows returned""" + limit: Int + + """skip the first n rows. Use only with order_by""" + offset: Int + + """sort the rows by one or more columns""" + order_by: [transaction_order_by!] + + """filter the rows returned""" + where: transaction_bool_exp + ): transaction_aggregate! +} + +""" +Boolean expression to filter rows from the table "account". All fields are combined with a logical 'AND'. +""" +input account_bool_exp { + _and: [account_bool_exp!] + _not: account_bool_exp + _or: [account_bool_exp!] + block: block_bool_exp + created_at: timestamptz_comparison_exp + created_on: Int_comparison_exp + has_identity: Boolean_comparison_exp + id: String_comparison_exp + identity: identity_bool_exp + killed_at: timestamptz_comparison_exp + killed_on: Int_comparison_exp + transactions_issued: transaction_bool_exp + transactions_received: transaction_bool_exp +} + +"""Ordering options when selecting data from "account".""" +input account_order_by { + block: block_order_by + created_at: order_by + created_on: order_by + has_identity: order_by + id: order_by + identity: identity_order_by + killed_at: order_by + killed_on: order_by + transactions_issued_aggregate: transaction_aggregate_order_by + transactions_received_aggregate: transaction_aggregate_order_by +} + +""" +select columns of table "account" +""" +enum account_select_column { + """column name""" + created_at + + """column name""" + created_on + + """column name""" + id + + """column name""" + killed_at + + """column name""" + killed_on +} + +""" +Streaming cursor of the table "account" +""" +input account_stream_cursor_input { + """Stream column input with initial value""" + initial_value: account_stream_cursor_value_input! + + """cursor ordering""" + ordering: cursor_ordering +} + +"""Initial value of the column from where the streaming should start""" +input account_stream_cursor_value_input { + created_at: timestamptz + + """Block number where account was created.""" + created_on: Int + + """Pubkey of the account.""" + id: String + killed_at: timestamptz + + """Block number where account was killed.""" + killed_on: Int +} + +"""Table of blocks.""" +type block { + created_at: timestamp! + + """ + Data contains `extrinsics` and `events` of the block. Exemple for querying + specific data: `data(path: "extrinsics[0].isSigned")`. + """ + data( + """JSON select path""" + path: String + ): jsonb + hash: String! + + """Index of the block in substrate.""" + index: Int! +} + +""" +Boolean expression to filter rows from the table "block". All fields are combined with a logical 'AND'. +""" +input block_bool_exp { + _and: [block_bool_exp!] + _not: block_bool_exp + _or: [block_bool_exp!] + created_at: timestamp_comparison_exp + data: jsonb_comparison_exp + hash: String_comparison_exp + index: Int_comparison_exp +} + +"""Ordering options when selecting data from "block".""" +input block_order_by { + created_at: order_by + data: order_by + hash: order_by + index: order_by +} + +""" +select columns of table "block" +""" +enum block_select_column { + """column name""" + created_at + + """column name""" + data + + """column name""" + hash + + """column name""" + index +} + +""" +Streaming cursor of the table "block" +""" +input block_stream_cursor_input { + """Stream column input with initial value""" + initial_value: block_stream_cursor_value_input! + + """cursor ordering""" + ordering: cursor_ordering +} + +"""Initial value of the column from where the streaming should start""" +input block_stream_cursor_value_input { + created_at: timestamp + + """ + Data contains `extrinsics` and `events` of the block. Exemple for querying + specific data: `data(path: "extrinsics[0].isSigned")`. + """ + data: jsonb + hash: String + + """Index of the block in substrate.""" + index: Int +} + +""" +Boolean expression to compare columns of type "Boolean". All fields are combined with logical 'AND'. +""" +input Boolean_comparison_exp { + _eq: Boolean + _gt: Boolean + _gte: Boolean + _in: [Boolean!] + _is_null: Boolean + _lt: Boolean + _lte: Boolean + _neq: Boolean + _nin: [Boolean!] +} + +"""Table of certifications.""" +type certification { + created_at: timestamptz! + + """Block number where certification was created.""" + created_on: Int! + + """An object relationship""" + created_on_block: block! + + """An object relationship""" + issuer: identity! + issuer_id: String! + + """An object relationship""" + receiver: identity! + receiver_id: String! +} + +""" +aggregated selection of "certification" +""" +type certification_aggregate { + aggregate: certification_aggregate_fields + nodes: [certification!]! +} + +""" +aggregate fields of "certification" +""" +type certification_aggregate_fields { + avg: certification_avg_fields + count(columns: [certification_select_column!], distinct: Boolean): Int! + max: certification_max_fields + min: certification_min_fields + stddev: certification_stddev_fields + stddev_pop: certification_stddev_pop_fields + stddev_samp: certification_stddev_samp_fields + sum: certification_sum_fields + var_pop: certification_var_pop_fields + var_samp: certification_var_samp_fields + variance: certification_variance_fields +} + +""" +order by aggregate values of table "certification" +""" +input certification_aggregate_order_by { + avg: certification_avg_order_by + count: order_by + max: certification_max_order_by + min: certification_min_order_by + stddev: certification_stddev_order_by + stddev_pop: certification_stddev_pop_order_by + stddev_samp: certification_stddev_samp_order_by + sum: certification_sum_order_by + var_pop: certification_var_pop_order_by + var_samp: certification_var_samp_order_by + variance: certification_variance_order_by +} + +"""aggregate avg on columns""" +type certification_avg_fields { + """Block number where certification was created.""" + created_on: Float +} + +""" +order by avg() on columns of table "certification" +""" +input certification_avg_order_by { + """Block number where certification was created.""" + created_on: order_by +} + +""" +Boolean expression to filter rows from the table "certification". All fields are combined with a logical 'AND'. +""" +input certification_bool_exp { + _and: [certification_bool_exp!] + _not: certification_bool_exp + _or: [certification_bool_exp!] + created_at: timestamptz_comparison_exp + created_on: Int_comparison_exp + created_on_block: block_bool_exp + issuer: identity_bool_exp + issuer_id: String_comparison_exp + receiver: identity_bool_exp + receiver_id: String_comparison_exp +} + +"""aggregate max on columns""" +type certification_max_fields { + created_at: timestamptz + + """Block number where certification was created.""" + created_on: Int + issuer_id: String + receiver_id: String +} + +""" +order by max() on columns of table "certification" +""" +input certification_max_order_by { + created_at: order_by + + """Block number where certification was created.""" + created_on: order_by + issuer_id: order_by + receiver_id: order_by +} + +"""aggregate min on columns""" +type certification_min_fields { + created_at: timestamptz + + """Block number where certification was created.""" + created_on: Int + issuer_id: String + receiver_id: String +} + +""" +order by min() on columns of table "certification" +""" +input certification_min_order_by { + created_at: order_by + + """Block number where certification was created.""" + created_on: order_by + issuer_id: order_by + receiver_id: order_by +} + +"""Ordering options when selecting data from "certification".""" +input certification_order_by { + created_at: order_by + created_on: order_by + created_on_block: block_order_by + issuer: identity_order_by + issuer_id: order_by + receiver: identity_order_by + receiver_id: order_by +} + +""" +select columns of table "certification" +""" +enum certification_select_column { + """column name""" + created_at + + """column name""" + created_on + + """column name""" + issuer_id + + """column name""" + receiver_id +} + +"""aggregate stddev on columns""" +type certification_stddev_fields { + """Block number where certification was created.""" + created_on: Float +} + +""" +order by stddev() on columns of table "certification" +""" +input certification_stddev_order_by { + """Block number where certification was created.""" + created_on: order_by +} + +"""aggregate stddev_pop on columns""" +type certification_stddev_pop_fields { + """Block number where certification was created.""" + created_on: Float +} + +""" +order by stddev_pop() on columns of table "certification" +""" +input certification_stddev_pop_order_by { + """Block number where certification was created.""" + created_on: order_by +} + +"""aggregate stddev_samp on columns""" +type certification_stddev_samp_fields { + """Block number where certification was created.""" + created_on: Float +} + +""" +order by stddev_samp() on columns of table "certification" +""" +input certification_stddev_samp_order_by { + """Block number where certification was created.""" + created_on: order_by +} + +""" +Streaming cursor of the table "certification" +""" +input certification_stream_cursor_input { + """Stream column input with initial value""" + initial_value: certification_stream_cursor_value_input! + + """cursor ordering""" + ordering: cursor_ordering +} + +"""Initial value of the column from where the streaming should start""" +input certification_stream_cursor_value_input { + created_at: timestamptz + + """Block number where certification was created.""" + created_on: Int + issuer_id: String + receiver_id: String +} + +"""aggregate sum on columns""" +type certification_sum_fields { + """Block number where certification was created.""" + created_on: Int +} + +""" +order by sum() on columns of table "certification" +""" +input certification_sum_order_by { + """Block number where certification was created.""" + created_on: order_by +} + +"""aggregate var_pop on columns""" +type certification_var_pop_fields { + """Block number where certification was created.""" + created_on: Float +} + +""" +order by var_pop() on columns of table "certification" +""" +input certification_var_pop_order_by { + """Block number where certification was created.""" + created_on: order_by +} + +"""aggregate var_samp on columns""" +type certification_var_samp_fields { + """Block number where certification was created.""" + created_on: Float +} + +""" +order by var_samp() on columns of table "certification" +""" +input certification_var_samp_order_by { + """Block number where certification was created.""" + created_on: order_by +} + +"""aggregate variance on columns""" +type certification_variance_fields { + """Block number where certification was created.""" + created_on: Float +} + +""" +order by variance() on columns of table "certification" +""" +input certification_variance_order_by { + """Block number where certification was created.""" + created_on: order_by +} + +"""ordering argument of a cursor""" +enum cursor_ordering { + """ascending ordering of the cursor""" + ASC + + """descending ordering of the cursor""" + DESC +} + +""" +Boolean expression to compare columns of type "Float". All fields are combined with logical 'AND'. +""" +input Float_comparison_exp { + _eq: Float + _gt: Float + _gte: Float + _in: [Float!] + _is_null: Boolean + _lt: Float + _lte: Float + _neq: Float + _nin: [Float!] +} + +"""Table of identities.""" +type identity { + """An object relationship""" + account: account + + """An array relationship""" + certifications_issued( + """distinct select on columns""" + distinct_on: [certification_select_column!] + + """limit the number of rows returned""" + limit: Int + + """skip the first n rows. Use only with order_by""" + offset: Int + + """sort the rows by one or more columns""" + order_by: [certification_order_by!] + + """filter the rows returned""" + where: certification_bool_exp + ): [certification!]! + + """An aggregate relationship""" + certifications_issued_aggregate( + """distinct select on columns""" + distinct_on: [certification_select_column!] + + """limit the number of rows returned""" + limit: Int + + """skip the first n rows. Use only with order_by""" + offset: Int + + """sort the rows by one or more columns""" + order_by: [certification_order_by!] + + """filter the rows returned""" + where: certification_bool_exp + ): certification_aggregate! + + """An array relationship""" + certifications_received( + """distinct select on columns""" + distinct_on: [certification_select_column!] + + """limit the number of rows returned""" + limit: Int + + """skip the first n rows. Use only with order_by""" + offset: Int + + """sort the rows by one or more columns""" + order_by: [certification_order_by!] + + """filter the rows returned""" + where: certification_bool_exp + ): [certification!]! + + """An aggregate relationship""" + certifications_received_aggregate( + """distinct select on columns""" + distinct_on: [certification_select_column!] + + """limit the number of rows returned""" + limit: Int + + """skip the first n rows. Use only with order_by""" + offset: Int + + """sort the rows by one or more columns""" + order_by: [certification_order_by!] + + """filter the rows returned""" + where: certification_bool_exp + ): certification_aggregate! + confirmed_at: timestamptz + + """Block number where identity was confirmed.""" + confirmed_on: Int + + """An object relationship""" + confirmed_on_block: block + created_at: timestamptz! + + """Block number where identity was created.""" + created_on: Int! + + """An object relationship""" + created_on_block: block! + + """Pubkey of the account associated to this identity.""" + id: String! + + """Name of the identity.""" + name: String + revoked_at: timestamptz + + """Block number where identity was revoked.""" + revoked_on: Int + + """An object relationship""" + revoked_on_block: block + validated_at: timestamptz + + """Block number where identity was validated.""" + validated_on: Int + + """An object relationship""" + validated_on_block: block +} + +""" +Boolean expression to filter rows from the table "identity". All fields are combined with a logical 'AND'. +""" +input identity_bool_exp { + _and: [identity_bool_exp!] + _not: identity_bool_exp + _or: [identity_bool_exp!] + account: account_bool_exp + certifications_issued: certification_bool_exp + certifications_received: certification_bool_exp + confirmed_at: timestamptz_comparison_exp + confirmed_on: Int_comparison_exp + confirmed_on_block: block_bool_exp + created_at: timestamptz_comparison_exp + created_on: Int_comparison_exp + created_on_block: block_bool_exp + id: String_comparison_exp + name: String_comparison_exp + revoked_at: timestamptz_comparison_exp + revoked_on: Int_comparison_exp + revoked_on_block: block_bool_exp + validated_at: timestamptz_comparison_exp + validated_on: Int_comparison_exp + validated_on_block: block_bool_exp +} + +"""Ordering options when selecting data from "identity".""" +input identity_order_by { + account: account_order_by + certifications_issued_aggregate: certification_aggregate_order_by + certifications_received_aggregate: certification_aggregate_order_by + confirmed_at: order_by + confirmed_on: order_by + confirmed_on_block: block_order_by + created_at: order_by + created_on: order_by + created_on_block: block_order_by + id: order_by + name: order_by + revoked_at: order_by + revoked_on: order_by + revoked_on_block: block_order_by + validated_at: order_by + validated_on: order_by + validated_on_block: block_order_by +} + +""" +select columns of table "identity" +""" +enum identity_select_column { + """column name""" + confirmed_at + + """column name""" + confirmed_on + + """column name""" + created_at + + """column name""" + created_on + + """column name""" + id + + """column name""" + name + + """column name""" + revoked_at + + """column name""" + revoked_on + + """column name""" + validated_at + + """column name""" + validated_on +} + +""" +Streaming cursor of the table "identity" +""" +input identity_stream_cursor_input { + """Stream column input with initial value""" + initial_value: identity_stream_cursor_value_input! + + """cursor ordering""" + ordering: cursor_ordering +} + +"""Initial value of the column from where the streaming should start""" +input identity_stream_cursor_value_input { + confirmed_at: timestamptz + + """Block number where identity was confirmed.""" + confirmed_on: Int + created_at: timestamptz + + """Block number where identity was created.""" + created_on: Int + + """Pubkey of the account associated to this identity.""" + id: String + + """Name of the identity.""" + name: String + revoked_at: timestamptz + + """Block number where identity was revoked.""" + revoked_on: Int + validated_at: timestamptz + + """Block number where identity was validated.""" + validated_on: Int +} + +""" +Boolean expression to compare columns of type "Int". All fields are combined with logical 'AND'. +""" +input Int_comparison_exp { + _eq: Int + _gt: Int + _gte: Int + _in: [Int!] + _is_null: Boolean + _lt: Int + _lte: Int + _neq: Int + _nin: [Int!] +} + +scalar jsonb + +input jsonb_cast_exp { + String: String_comparison_exp +} + +""" +Boolean expression to compare columns of type "jsonb". All fields are combined with logical 'AND'. +""" +input jsonb_comparison_exp { + _cast: jsonb_cast_exp + + """is the column contained in the given json value""" + _contained_in: jsonb + + """does the column contain the given json value at the top level""" + _contains: jsonb + _eq: jsonb + _gt: jsonb + _gte: jsonb + + """does the string exist as a top-level key in the column""" + _has_key: String + + """do all of these strings exist as top-level keys in the column""" + _has_keys_all: [String!] + + """do any of these strings exist as top-level keys in the column""" + _has_keys_any: [String!] + _in: [jsonb!] + _is_null: Boolean + _lt: jsonb + _lte: jsonb + _neq: jsonb + _nin: [jsonb!] +} + +"""column ordering options""" +enum order_by { + """in ascending order, nulls last""" + asc + + """in ascending order, nulls first""" + asc_nulls_first + + """in ascending order, nulls last""" + asc_nulls_last + + """in descending order, nulls first""" + desc + + """in descending order, nulls first""" + desc_nulls_first + + """in descending order, nulls last""" + desc_nulls_last +} + +"""Table of key/value parameters.""" +type parameters { + key: String! + value( + """JSON select path""" + path: String + ): jsonb +} + +""" +Boolean expression to filter rows from the table "parameters". All fields are combined with a logical 'AND'. +""" +input parameters_bool_exp { + _and: [parameters_bool_exp!] + _not: parameters_bool_exp + _or: [parameters_bool_exp!] + key: String_comparison_exp + value: jsonb_comparison_exp +} + +"""Ordering options when selecting data from "parameters".""" +input parameters_order_by { + key: order_by + value: order_by +} + +""" +select columns of table "parameters" +""" +enum parameters_select_column { + """column name""" + key + + """column name""" + value +} + +""" +Streaming cursor of the table "parameters" +""" +input parameters_stream_cursor_input { + """Stream column input with initial value""" + initial_value: parameters_stream_cursor_value_input! + + """cursor ordering""" + ordering: cursor_ordering +} + +"""Initial value of the column from where the streaming should start""" +input parameters_stream_cursor_value_input { + key: String + value: jsonb +} + +type query_root { + """ + fetch data from the table: "account" + """ + account( + """distinct select on columns""" + distinct_on: [account_select_column!] + + """limit the number of rows returned""" + limit: Int + + """skip the first n rows. Use only with order_by""" + offset: Int + + """sort the rows by one or more columns""" + order_by: [account_order_by!] + + """filter the rows returned""" + where: account_bool_exp + ): [account!]! + + """fetch data from the table: "account" using primary key columns""" + account_by_pk( + """Pubkey of the account.""" + id: String! + ): account + + """ + fetch data from the table: "block" + """ + block( + """distinct select on columns""" + distinct_on: [block_select_column!] + + """limit the number of rows returned""" + limit: Int + + """skip the first n rows. Use only with order_by""" + offset: Int + + """sort the rows by one or more columns""" + order_by: [block_order_by!] + + """filter the rows returned""" + where: block_bool_exp + ): [block!]! + + """fetch data from the table: "block" using primary key columns""" + block_by_pk( + """Index of the block in substrate.""" + index: Int! + ): block + + """ + fetch data from the table: "certification" + """ + certification( + """distinct select on columns""" + distinct_on: [certification_select_column!] + + """limit the number of rows returned""" + limit: Int + + """skip the first n rows. Use only with order_by""" + offset: Int + + """sort the rows by one or more columns""" + order_by: [certification_order_by!] + + """filter the rows returned""" + where: certification_bool_exp + ): [certification!]! + + """ + fetch aggregated fields from the table: "certification" + """ + certification_aggregate( + """distinct select on columns""" + distinct_on: [certification_select_column!] + + """limit the number of rows returned""" + limit: Int + + """skip the first n rows. Use only with order_by""" + offset: Int + + """sort the rows by one or more columns""" + order_by: [certification_order_by!] + + """filter the rows returned""" + where: certification_bool_exp + ): certification_aggregate! + + """fetch data from the table: "certification" using primary key columns""" + certification_by_pk(issuer_id: String!, receiver_id: String!): certification + + """ + fetch data from the table: "identity" + """ + identity( + """distinct select on columns""" + distinct_on: [identity_select_column!] + + """limit the number of rows returned""" + limit: Int + + """skip the first n rows. Use only with order_by""" + offset: Int + + """sort the rows by one or more columns""" + order_by: [identity_order_by!] + + """filter the rows returned""" + where: identity_bool_exp + ): [identity!]! + + """fetch data from the table: "identity" using primary key columns""" + identity_by_pk( + """Pubkey of the account associated to this identity.""" + id: String! + ): identity + + """ + fetch data from the table: "parameters" + """ + parameters( + """distinct select on columns""" + distinct_on: [parameters_select_column!] + + """limit the number of rows returned""" + limit: Int + + """skip the first n rows. Use only with order_by""" + offset: Int + + """sort the rows by one or more columns""" + order_by: [parameters_order_by!] + + """filter the rows returned""" + where: parameters_bool_exp + ): [parameters!]! + + """fetch data from the table: "parameters" using primary key columns""" + parameters_by_pk(key: String!): parameters + + """ + execute function "search_identity" which returns "identity" + """ + search_identity( + """ + input parameters for function "search_identity" + """ + args: search_identity_args! + + """distinct select on columns""" + distinct_on: [identity_select_column!] + + """limit the number of rows returned""" + limit: Int + + """skip the first n rows. Use only with order_by""" + offset: Int + + """sort the rows by one or more columns""" + order_by: [identity_order_by!] + + """filter the rows returned""" + where: identity_bool_exp + ): [identity!]! + + """ + fetch data from the table: "transaction" + """ + transaction( + """distinct select on columns""" + distinct_on: [transaction_select_column!] + + """limit the number of rows returned""" + limit: Int + + """skip the first n rows. Use only with order_by""" + offset: Int + + """sort the rows by one or more columns""" + order_by: [transaction_order_by!] + + """filter the rows returned""" + where: transaction_bool_exp + ): [transaction!]! + + """ + fetch aggregated fields from the table: "transaction" + """ + transaction_aggregate( + """distinct select on columns""" + distinct_on: [transaction_select_column!] + + """limit the number of rows returned""" + limit: Int + + """skip the first n rows. Use only with order_by""" + offset: Int + + """sort the rows by one or more columns""" + order_by: [transaction_order_by!] + + """filter the rows returned""" + where: transaction_bool_exp + ): transaction_aggregate! + + """fetch data from the table: "transaction" using primary key columns""" + transaction_by_pk( + """ + Primary Key `id` is used for postgreSQL and Hasura relationship, not related to any value in substrate. + """ + id: Int! + ): transaction +} + +input search_identity_args { + name: String +} + +""" +Boolean expression to compare columns of type "String". All fields are combined with logical 'AND'. +""" +input String_comparison_exp { + _eq: String + _gt: String + _gte: String + + """does the column match the given case-insensitive pattern""" + _ilike: String + _in: [String!] + + """ + does the column match the given POSIX regular expression, case insensitive + """ + _iregex: String + _is_null: Boolean + + """does the column match the given pattern""" + _like: String + _lt: String + _lte: String + _neq: String + + """does the column NOT match the given case-insensitive pattern""" + _nilike: String + _nin: [String!] + + """ + does the column NOT match the given POSIX regular expression, case insensitive + """ + _niregex: String + + """does the column NOT match the given pattern""" + _nlike: String + + """ + does the column NOT match the given POSIX regular expression, case sensitive + """ + _nregex: String + + """does the column NOT match the given SQL regular expression""" + _nsimilar: String + + """ + does the column match the given POSIX regular expression, case sensitive + """ + _regex: String + + """does the column match the given SQL regular expression""" + _similar: String +} + +type subscription_root { + """ + fetch data from the table: "account" + """ + account( + """distinct select on columns""" + distinct_on: [account_select_column!] + + """limit the number of rows returned""" + limit: Int + + """skip the first n rows. Use only with order_by""" + offset: Int + + """sort the rows by one or more columns""" + order_by: [account_order_by!] + + """filter the rows returned""" + where: account_bool_exp + ): [account!]! + + """fetch data from the table: "account" using primary key columns""" + account_by_pk( + """Pubkey of the account.""" + id: String! + ): account + + """ + fetch data from the table in a streaming manner : "account" + """ + account_stream( + """maximum number of rows returned in a single batch""" + batch_size: Int! + + """cursor to stream the results returned by the query""" + cursor: [account_stream_cursor_input]! + + """filter the rows returned""" + where: account_bool_exp + ): [account!]! + + """ + fetch data from the table: "block" + """ + block( + """distinct select on columns""" + distinct_on: [block_select_column!] + + """limit the number of rows returned""" + limit: Int + + """skip the first n rows. Use only with order_by""" + offset: Int + + """sort the rows by one or more columns""" + order_by: [block_order_by!] + + """filter the rows returned""" + where: block_bool_exp + ): [block!]! + + """fetch data from the table: "block" using primary key columns""" + block_by_pk( + """Index of the block in substrate.""" + index: Int! + ): block + + """ + fetch data from the table in a streaming manner : "block" + """ + block_stream( + """maximum number of rows returned in a single batch""" + batch_size: Int! + + """cursor to stream the results returned by the query""" + cursor: [block_stream_cursor_input]! + + """filter the rows returned""" + where: block_bool_exp + ): [block!]! + + """ + fetch data from the table: "certification" + """ + certification( + """distinct select on columns""" + distinct_on: [certification_select_column!] + + """limit the number of rows returned""" + limit: Int + + """skip the first n rows. Use only with order_by""" + offset: Int + + """sort the rows by one or more columns""" + order_by: [certification_order_by!] + + """filter the rows returned""" + where: certification_bool_exp + ): [certification!]! + + """ + fetch aggregated fields from the table: "certification" + """ + certification_aggregate( + """distinct select on columns""" + distinct_on: [certification_select_column!] + + """limit the number of rows returned""" + limit: Int + + """skip the first n rows. Use only with order_by""" + offset: Int + + """sort the rows by one or more columns""" + order_by: [certification_order_by!] + + """filter the rows returned""" + where: certification_bool_exp + ): certification_aggregate! + + """fetch data from the table: "certification" using primary key columns""" + certification_by_pk(issuer_id: String!, receiver_id: String!): certification + + """ + fetch data from the table in a streaming manner : "certification" + """ + certification_stream( + """maximum number of rows returned in a single batch""" + batch_size: Int! + + """cursor to stream the results returned by the query""" + cursor: [certification_stream_cursor_input]! + + """filter the rows returned""" + where: certification_bool_exp + ): [certification!]! + + """ + fetch data from the table: "identity" + """ + identity( + """distinct select on columns""" + distinct_on: [identity_select_column!] + + """limit the number of rows returned""" + limit: Int + + """skip the first n rows. Use only with order_by""" + offset: Int + + """sort the rows by one or more columns""" + order_by: [identity_order_by!] + + """filter the rows returned""" + where: identity_bool_exp + ): [identity!]! + + """fetch data from the table: "identity" using primary key columns""" + identity_by_pk( + """Pubkey of the account associated to this identity.""" + id: String! + ): identity + + """ + fetch data from the table in a streaming manner : "identity" + """ + identity_stream( + """maximum number of rows returned in a single batch""" + batch_size: Int! + + """cursor to stream the results returned by the query""" + cursor: [identity_stream_cursor_input]! + + """filter the rows returned""" + where: identity_bool_exp + ): [identity!]! + + """ + fetch data from the table: "parameters" + """ + parameters( + """distinct select on columns""" + distinct_on: [parameters_select_column!] + + """limit the number of rows returned""" + limit: Int + + """skip the first n rows. Use only with order_by""" + offset: Int + + """sort the rows by one or more columns""" + order_by: [parameters_order_by!] + + """filter the rows returned""" + where: parameters_bool_exp + ): [parameters!]! + + """fetch data from the table: "parameters" using primary key columns""" + parameters_by_pk(key: String!): parameters + + """ + fetch data from the table in a streaming manner : "parameters" + """ + parameters_stream( + """maximum number of rows returned in a single batch""" + batch_size: Int! + + """cursor to stream the results returned by the query""" + cursor: [parameters_stream_cursor_input]! + + """filter the rows returned""" + where: parameters_bool_exp + ): [parameters!]! + + """ + execute function "search_identity" which returns "identity" + """ + search_identity( + """ + input parameters for function "search_identity" + """ + args: search_identity_args! + + """distinct select on columns""" + distinct_on: [identity_select_column!] + + """limit the number of rows returned""" + limit: Int + + """skip the first n rows. Use only with order_by""" + offset: Int + + """sort the rows by one or more columns""" + order_by: [identity_order_by!] + + """filter the rows returned""" + where: identity_bool_exp + ): [identity!]! + + """ + fetch data from the table: "transaction" + """ + transaction( + """distinct select on columns""" + distinct_on: [transaction_select_column!] + + """limit the number of rows returned""" + limit: Int + + """skip the first n rows. Use only with order_by""" + offset: Int + + """sort the rows by one or more columns""" + order_by: [transaction_order_by!] + + """filter the rows returned""" + where: transaction_bool_exp + ): [transaction!]! + + """ + fetch aggregated fields from the table: "transaction" + """ + transaction_aggregate( + """distinct select on columns""" + distinct_on: [transaction_select_column!] + + """limit the number of rows returned""" + limit: Int + + """skip the first n rows. Use only with order_by""" + offset: Int + + """sort the rows by one or more columns""" + order_by: [transaction_order_by!] + + """filter the rows returned""" + where: transaction_bool_exp + ): transaction_aggregate! + + """fetch data from the table: "transaction" using primary key columns""" + transaction_by_pk( + """ + Primary Key `id` is used for postgreSQL and Hasura relationship, not related to any value in substrate. + """ + id: Int! + ): transaction + + """ + fetch data from the table in a streaming manner : "transaction" + """ + transaction_stream( + """maximum number of rows returned in a single batch""" + batch_size: Int! + + """cursor to stream the results returned by the query""" + cursor: [transaction_stream_cursor_input]! + + """filter the rows returned""" + where: transaction_bool_exp + ): [transaction!]! +} + +scalar timestamp + +""" +Boolean expression to compare columns of type "timestamp". All fields are combined with logical 'AND'. +""" +input timestamp_comparison_exp { + _eq: timestamp + _gt: timestamp + _gte: timestamp + _in: [timestamp!] + _is_null: Boolean + _lt: timestamp + _lte: timestamp + _neq: timestamp + _nin: [timestamp!] +} + +scalar timestamptz + +""" +Boolean expression to compare columns of type "timestamptz". All fields are combined with logical 'AND'. +""" +input timestamptz_comparison_exp { + _eq: timestamptz + _gt: timestamptz + _gte: timestamptz + _in: [timestamptz!] + _is_null: Boolean + _lt: timestamptz + _lte: timestamptz + _neq: timestamptz + _nin: [timestamptz!] +} + +"""Table of transactions.""" +type transaction { + """Amount of the transaction. 100 units = 1 Äž1.""" + amount: Float! + created_at: timestamptz! + + """Block number where transaction was created.""" + created_on: Int! + + """An object relationship""" + created_on_block: block! + + """ + Primary Key `id` is used for postgreSQL and Hasura relationship, not related to any value in substrate. + """ + id: Int! + + """An object relationship""" + issuer: account! + issuer_id: String! + + """An object relationship""" + receiver: account! + receiver_id: String! +} + +""" +aggregated selection of "transaction" +""" +type transaction_aggregate { + aggregate: transaction_aggregate_fields + nodes: [transaction!]! +} + +""" +aggregate fields of "transaction" +""" +type transaction_aggregate_fields { + avg: transaction_avg_fields + count(columns: [transaction_select_column!], distinct: Boolean): Int! + max: transaction_max_fields + min: transaction_min_fields + stddev: transaction_stddev_fields + stddev_pop: transaction_stddev_pop_fields + stddev_samp: transaction_stddev_samp_fields + sum: transaction_sum_fields + var_pop: transaction_var_pop_fields + var_samp: transaction_var_samp_fields + variance: transaction_variance_fields +} + +""" +order by aggregate values of table "transaction" +""" +input transaction_aggregate_order_by { + avg: transaction_avg_order_by + count: order_by + max: transaction_max_order_by + min: transaction_min_order_by + stddev: transaction_stddev_order_by + stddev_pop: transaction_stddev_pop_order_by + stddev_samp: transaction_stddev_samp_order_by + sum: transaction_sum_order_by + var_pop: transaction_var_pop_order_by + var_samp: transaction_var_samp_order_by + variance: transaction_variance_order_by +} + +"""aggregate avg on columns""" +type transaction_avg_fields { + """Amount of the transaction. 100 units = 1 Äž1.""" + amount: Float + + """Block number where transaction was created.""" + created_on: Float + + """ + Primary Key `id` is used for postgreSQL and Hasura relationship, not related to any value in substrate. + """ + id: Float +} + +""" +order by avg() on columns of table "transaction" +""" +input transaction_avg_order_by { + """Amount of the transaction. 100 units = 1 Äž1.""" + amount: order_by + + """Block number where transaction was created.""" + created_on: order_by + + """ + Primary Key `id` is used for postgreSQL and Hasura relationship, not related to any value in substrate. + """ + id: order_by +} + +""" +Boolean expression to filter rows from the table "transaction". All fields are combined with a logical 'AND'. +""" +input transaction_bool_exp { + _and: [transaction_bool_exp!] + _not: transaction_bool_exp + _or: [transaction_bool_exp!] + amount: Float_comparison_exp + created_at: timestamptz_comparison_exp + created_on: Int_comparison_exp + created_on_block: block_bool_exp + id: Int_comparison_exp + issuer: account_bool_exp + issuer_id: String_comparison_exp + receiver: account_bool_exp + receiver_id: String_comparison_exp +} + +"""aggregate max on columns""" +type transaction_max_fields { + """Amount of the transaction. 100 units = 1 Äž1.""" + amount: Float + created_at: timestamptz + + """Block number where transaction was created.""" + created_on: Int + + """ + Primary Key `id` is used for postgreSQL and Hasura relationship, not related to any value in substrate. + """ + id: Int + issuer_id: String + receiver_id: String +} + +""" +order by max() on columns of table "transaction" +""" +input transaction_max_order_by { + """Amount of the transaction. 100 units = 1 Äž1.""" + amount: order_by + created_at: order_by + + """Block number where transaction was created.""" + created_on: order_by + + """ + Primary Key `id` is used for postgreSQL and Hasura relationship, not related to any value in substrate. + """ + id: order_by + issuer_id: order_by + receiver_id: order_by +} + +"""aggregate min on columns""" +type transaction_min_fields { + """Amount of the transaction. 100 units = 1 Äž1.""" + amount: Float + created_at: timestamptz + + """Block number where transaction was created.""" + created_on: Int + + """ + Primary Key `id` is used for postgreSQL and Hasura relationship, not related to any value in substrate. + """ + id: Int + issuer_id: String + receiver_id: String +} + +""" +order by min() on columns of table "transaction" +""" +input transaction_min_order_by { + """Amount of the transaction. 100 units = 1 Äž1.""" + amount: order_by + created_at: order_by + + """Block number where transaction was created.""" + created_on: order_by + + """ + Primary Key `id` is used for postgreSQL and Hasura relationship, not related to any value in substrate. + """ + id: order_by + issuer_id: order_by + receiver_id: order_by +} + +"""Ordering options when selecting data from "transaction".""" +input transaction_order_by { + amount: order_by + created_at: order_by + created_on: order_by + created_on_block: block_order_by + id: order_by + issuer: account_order_by + issuer_id: order_by + receiver: account_order_by + receiver_id: order_by +} + +""" +select columns of table "transaction" +""" +enum transaction_select_column { + """column name""" + amount + + """column name""" + created_at + + """column name""" + created_on + + """column name""" + id + + """column name""" + issuer_id + + """column name""" + receiver_id +} + +"""aggregate stddev on columns""" +type transaction_stddev_fields { + """Amount of the transaction. 100 units = 1 Äž1.""" + amount: Float + + """Block number where transaction was created.""" + created_on: Float + + """ + Primary Key `id` is used for postgreSQL and Hasura relationship, not related to any value in substrate. + """ + id: Float +} + +""" +order by stddev() on columns of table "transaction" +""" +input transaction_stddev_order_by { + """Amount of the transaction. 100 units = 1 Äž1.""" + amount: order_by + + """Block number where transaction was created.""" + created_on: order_by + + """ + Primary Key `id` is used for postgreSQL and Hasura relationship, not related to any value in substrate. + """ + id: order_by +} + +"""aggregate stddev_pop on columns""" +type transaction_stddev_pop_fields { + """Amount of the transaction. 100 units = 1 Äž1.""" + amount: Float + + """Block number where transaction was created.""" + created_on: Float + + """ + Primary Key `id` is used for postgreSQL and Hasura relationship, not related to any value in substrate. + """ + id: Float +} + +""" +order by stddev_pop() on columns of table "transaction" +""" +input transaction_stddev_pop_order_by { + """Amount of the transaction. 100 units = 1 Äž1.""" + amount: order_by + + """Block number where transaction was created.""" + created_on: order_by + + """ + Primary Key `id` is used for postgreSQL and Hasura relationship, not related to any value in substrate. + """ + id: order_by +} + +"""aggregate stddev_samp on columns""" +type transaction_stddev_samp_fields { + """Amount of the transaction. 100 units = 1 Äž1.""" + amount: Float + + """Block number where transaction was created.""" + created_on: Float + + """ + Primary Key `id` is used for postgreSQL and Hasura relationship, not related to any value in substrate. + """ + id: Float +} + +""" +order by stddev_samp() on columns of table "transaction" +""" +input transaction_stddev_samp_order_by { + """Amount of the transaction. 100 units = 1 Äž1.""" + amount: order_by + + """Block number where transaction was created.""" + created_on: order_by + + """ + Primary Key `id` is used for postgreSQL and Hasura relationship, not related to any value in substrate. + """ + id: order_by +} + +""" +Streaming cursor of the table "transaction" +""" +input transaction_stream_cursor_input { + """Stream column input with initial value""" + initial_value: transaction_stream_cursor_value_input! + + """cursor ordering""" + ordering: cursor_ordering +} + +"""Initial value of the column from where the streaming should start""" +input transaction_stream_cursor_value_input { + """Amount of the transaction. 100 units = 1 Äž1.""" + amount: Float + created_at: timestamptz + + """Block number where transaction was created.""" + created_on: Int + + """ + Primary Key `id` is used for postgreSQL and Hasura relationship, not related to any value in substrate. + """ + id: Int + issuer_id: String + receiver_id: String +} + +"""aggregate sum on columns""" +type transaction_sum_fields { + """Amount of the transaction. 100 units = 1 Äž1.""" + amount: Float + + """Block number where transaction was created.""" + created_on: Int + + """ + Primary Key `id` is used for postgreSQL and Hasura relationship, not related to any value in substrate. + """ + id: Int +} + +""" +order by sum() on columns of table "transaction" +""" +input transaction_sum_order_by { + """Amount of the transaction. 100 units = 1 Äž1.""" + amount: order_by + + """Block number where transaction was created.""" + created_on: order_by + + """ + Primary Key `id` is used for postgreSQL and Hasura relationship, not related to any value in substrate. + """ + id: order_by +} + +"""aggregate var_pop on columns""" +type transaction_var_pop_fields { + """Amount of the transaction. 100 units = 1 Äž1.""" + amount: Float + + """Block number where transaction was created.""" + created_on: Float + + """ + Primary Key `id` is used for postgreSQL and Hasura relationship, not related to any value in substrate. + """ + id: Float +} + +""" +order by var_pop() on columns of table "transaction" +""" +input transaction_var_pop_order_by { + """Amount of the transaction. 100 units = 1 Äž1.""" + amount: order_by + + """Block number where transaction was created.""" + created_on: order_by + + """ + Primary Key `id` is used for postgreSQL and Hasura relationship, not related to any value in substrate. + """ + id: order_by +} + +"""aggregate var_samp on columns""" +type transaction_var_samp_fields { + """Amount of the transaction. 100 units = 1 Äž1.""" + amount: Float + + """Block number where transaction was created.""" + created_on: Float + + """ + Primary Key `id` is used for postgreSQL and Hasura relationship, not related to any value in substrate. + """ + id: Float +} + +""" +order by var_samp() on columns of table "transaction" +""" +input transaction_var_samp_order_by { + """Amount of the transaction. 100 units = 1 Äž1.""" + amount: order_by + + """Block number where transaction was created.""" + created_on: order_by + + """ + Primary Key `id` is used for postgreSQL and Hasura relationship, not related to any value in substrate. + """ + id: order_by +} + +"""aggregate variance on columns""" +type transaction_variance_fields { + """Amount of the transaction. 100 units = 1 Äž1.""" + amount: Float + + """Block number where transaction was created.""" + created_on: Float + + """ + Primary Key `id` is used for postgreSQL and Hasura relationship, not related to any value in substrate. + """ + id: Float +} + +""" +order by variance() on columns of table "transaction" +""" +input transaction_variance_order_by { + """Amount of the transaction. 100 units = 1 Äž1.""" + amount: order_by + + """Block number where transaction was created.""" + created_on: order_by + + """ + Primary Key `id` is used for postgreSQL and Hasura relationship, not related to any value in substrate. + """ + id: order_by +} + diff --git a/src/cache.rs b/src/cache.rs new file mode 100644 index 0000000000000000000000000000000000000000..0a7d531ce9604eeeadf98239825de29a305fca29 --- /dev/null +++ b/src/cache.rs @@ -0,0 +1,73 @@ +use crate::indexer::*; +use crate::Api; + +use anyhow::{anyhow, Result}; +use std::collections::{hash_map, HashMap}; + +pub struct IdentityCache<'a> { + api: &'a Api, + identities: HashMap<u32, String>, + indexer: Option<(&'a reqwest::Client, &'a str)>, +} + +impl<'a> IdentityCache<'a> { + pub fn new(api: &'a Api, indexer: Option<(&'a reqwest::Client, &'a str)>) -> Self { + Self { + api, + identities: HashMap::new(), + indexer, + } + } + + pub async fn fetch_identity( + &mut self, + identity_id: u32, + parent_hash: sp_core::H256, + ) -> Result<String> { + Ok(match self.identities.entry(identity_id) { + hash_map::Entry::Occupied(entry) => entry.get().clone(), + hash_map::Entry::Vacant(entry) => entry + .insert({ + let pubkey = self + .api + .storage() + .identity() + .identities(&identity_id, Some(parent_hash)) + .await? + .ok_or_else(|| anyhow!("Identity {} not found", identity_id))? + .owner_key + .to_string(); + if let Some((gql_client, gql_url)) = self.indexer { + if let Ok(resp) = post_graphql::<IdentityNameByPubkey, _>( + gql_client, + gql_url, + identity_name_by_pubkey::Variables { + pubkey: pubkey.clone(), + }, + ) + .await + { + if let Some(data) = resp.data { + if let Some(identity) = data.identity_by_pk { + if let Some(name) = identity.name { + format!("“ {} â€", name) + } else { + pubkey + } + } else { + pubkey + } + } else { + pubkey + } + } else { + pubkey + } + } else { + pubkey + } + }) + .clone(), + }) + } +} diff --git a/src/indexer.rs b/src/indexer.rs new file mode 100644 index 0000000000000000000000000000000000000000..9183a45d96faf561be4fbdacff809aa36214f446 --- /dev/null +++ b/src/indexer.rs @@ -0,0 +1,8 @@ +pub use graphql_client::{reqwest::post_graphql, GraphQLQuery}; + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "res/indexer-schema.graphql", + query_path = "res/indexer-queries.graphql" +)] +pub struct IdentityNameByPubkey; diff --git a/src/main.rs b/src/main.rs index f2beb9c7bcfb2958651a86466cccdb2228f7d0a2..f7ca9b0a31125eb3e05e76e0f02f8c31cc360435 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,6 @@ +mod cache; +mod indexer; + use anyhow::{anyhow, Context, Result}; use clap::Parser; use codec::Encode; @@ -5,6 +8,7 @@ use sp_core::{ crypto::{AccountId32, DeriveJunction, Pair as _}, sr25519::Pair, }; +use std::collections::BTreeMap; use subxt::sp_runtime::MultiAddress; use subxt::{ extrinsic::{BaseExtrinsicParams, BaseExtrinsicParamsBuilder}, @@ -41,6 +45,12 @@ struct Args { #[clap(subcommand)] pub subcommand: Subcommand, + /// Indexer URL + #[clap(short, long, default_value = "https://idx.gdev.cgeek.fr/v1/graphql")] + indexer: String, + /// Do not use indexer + #[clap(long)] + no_indexer: bool, /// Secret key or BIP39 mnemonic /// (eventually followed by derivation path) #[clap(short, long)] @@ -70,6 +80,15 @@ pub enum Subcommand { #[clap(long = "rem-one")] remaining_to_oneshot: bool, }, + /// List upcoming expirations that require an action + Expire { + /// Show certs that expire within less than this number of blocks + #[clap(short, long, default_value_t = 100800)] + blocks: u32, + /// Show authorities that should rotate keys within less than this number of sessions + #[clap(short, long, default_value_t = 100)] + sessions: u32, + }, /// Generate a revocation document for the provided account GenRevocDoc, OneshotBalance { @@ -109,6 +128,10 @@ async fn main() -> Result<()> { .with_context(|| "fail to connect to node")?; let api = client.clone().to_runtime_api::<Api>(); + let gql_client = reqwest::Client::builder() + .user_agent("gcli/0.1.0") + .build()?; + let account_id: sp_core::crypto::AccountId32 = pair.public().into(); let account = api.storage().system().account(&account_id, None).await?; logs::info!("Account free balance: {}", account.data.free); @@ -184,6 +207,153 @@ async fn main() -> Result<()> { ) .await?; } + Subcommand::Expire { blocks, sessions } => { + let parent_hash = api.storage().system().parent_hash(None).await?; + let current_block = api.storage().system().number(Some(parent_hash)).await?; + let current_session = api + .storage() + .session() + .current_index(Some(parent_hash)) + .await?; + let end_block = current_block + blocks; + let end_session = current_session + sessions; + + let mut identity_cache = cache::IdentityCache::new( + &api, + if args.no_indexer { + None + } else { + Some((&gql_client, &args.indexer)) + }, + ); + + // Rotate keys + let mut must_rotate_keys_before_iter = client + .storage() + .iter::<gdev_300::authority_members::storage::MustRotateKeysBefore>(Some( + parent_hash, + )) + .await?; + let mut must_rotate_keys_before = BTreeMap::new(); + while let Some((k, v)) = must_rotate_keys_before_iter.next().await? { + let session_index = u32::from_le_bytes(k.as_ref()[40..44].try_into().unwrap()); + if session_index < end_session { + must_rotate_keys_before.insert(session_index - current_session, v); + } + } + + println!("\nAuthority members:"); + for (sessions_left, identity_ids) in must_rotate_keys_before { + println!("Must rotate keys before {} sessions:", sessions_left); + for identity_id in identity_ids { + println!( + " {} ({})", + identity_cache + .fetch_identity(identity_id, parent_hash) + .await + .unwrap_or_else(|_| "?".into()), + identity_id + ); + } + } + + // Certifications + let mut basic_certs_iter = client + .storage() + .iter::<gdev_300::cert::storage::StorageCertsRemovableOn>(Some(parent_hash)) + .await?; + let mut basic_certs = BTreeMap::new(); + while let Some((k, v)) = basic_certs_iter.next().await? { + let block_number = u32::from_le_bytes(k.as_ref()[40..44].try_into().unwrap()); + if block_number < end_block { + basic_certs.insert(block_number - current_block, v); + } + } + + let mut smith_certs_iter = client + .storage() + .iter::<gdev_300::smiths_cert::storage::StorageCertsRemovableOn>(Some(parent_hash)) + .await?; + let mut smith_certs = BTreeMap::new(); + while let Some((k, v)) = smith_certs_iter.next().await? { + let block_number = u32::from_le_bytes(k.as_ref()[40..44].try_into().unwrap()); + if block_number < end_block { + smith_certs.insert(block_number - current_block, v); + } + } + + for (title, certs) in [ + ("Certifications", basic_certs), + ("Smith certifications", smith_certs), + ] { + println!("\n{}:", title); + for (blocks_left, certs) in certs { + println!("{} blocks before expiration:", blocks_left); + for (issuer_id, receiver_id) in certs { + println!( + " {} ({}) -> {} ({})", + identity_cache + .fetch_identity(issuer_id, parent_hash) + .await + .unwrap_or_else(|_| "?".into()), + issuer_id, + identity_cache + .fetch_identity(receiver_id, parent_hash) + .await + .unwrap_or_else(|_| "?".into()), + receiver_id, + ); + } + } + } + + // Memberships + let mut basic_membership_iter = client + .storage() + .iter::<gdev_300::membership::storage::MembershipsExpireOn>(Some(parent_hash)) + .await?; + let mut basic_memberships = BTreeMap::new(); + while let Some((k, v)) = basic_membership_iter.next().await? { + let block_number = u32::from_le_bytes(k.as_ref()[40..44].try_into().unwrap()); + if block_number < end_block { + basic_memberships.insert(block_number - current_block, v); + } + } + + let mut smith_membership_iter = client + .storage() + .iter::<gdev_300::smiths_membership::storage::MembershipsExpireOn>(Some( + parent_hash, + )) + .await?; + let mut smith_memberships = BTreeMap::new(); + while let Some((k, v)) = smith_membership_iter.next().await? { + let block_number = u32::from_le_bytes(k.as_ref()[40..44].try_into().unwrap()); + if block_number < end_block { + smith_memberships.insert(block_number - current_block, v); + } + } + + for (title, memberships) in [ + ("Memberships", basic_memberships), + ("Smith memberships", smith_memberships), + ] { + println!("\n{}:", title); + for (blocks_left, membership) in memberships { + println!("{} blocks before expiration:", blocks_left); + for identity_id in membership { + println!( + " {} ({})", + identity_cache + .fetch_identity(identity_id, parent_hash) + .await + .unwrap_or_else(|_| "?".into()), + identity_id, + ); + } + } + } + } Subcommand::GenRevocDoc => gen_revoc_doc(&api, &pair).await?, Subcommand::OneshotBalance { account } => { logs::info!(