diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 0df7e8f..16f889d 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -8,34 +8,24 @@ on: - main env: + CARGO_INCREMENTAL: 1 CARGO_TERM_COLOR: always GITHUB_CACHE_VERSION: 1 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} RUST_BACKTRACE: full - RUSTC_WRAPPER: sccache - - SCCACHE_LINK: https://github.com/mozilla/sccache/releases/download - SCCACHE_VERSION: v0.3.0 - SCCACHE_DIR: /home/runner/.cache/sccache jobs: - cargo_checks: + cargo-checks: name: Task cargo ${{ matrix.action }} runs-on: ubuntu-latest strategy: matrix: - action: [clippy, nextest] + action: [clippy, fmt, nextest] steps: - name: Fetch latest code uses: actions/checkout@v3 - - name: Install Sccache - run: | - export SCCACHE_FILE=sccache-${{ env.SCCACHE_VERSION }}-x86_64-unknown-linux-musl - curl -L ${{ env.SCCACHE_LINK }}/${{ env.SCCACHE_VERSION }}/$SCCACHE_FILE.tar.gz | tar xz - sudo mv $SCCACHE_FILE/sccache /usr/bin - sudo chmod u+x /usr/bin/sccache - name: Cache cargo uses: actions/cache@v3 with: @@ -45,24 +35,25 @@ jobs: target key: cargo-${{ env.GITHUB_CACHE_VERSION }}-${{ matrix.action }}-${{ hashFiles('**/Cargo.lock') }} restore-keys: cargo-${{ env.GITHUB_CACHE_VERSION }}-${{ matrix.action }}- - - name: Cache sccache - uses: actions/cache@v3 - with: - path: ${{ env.SCCACHE_DIR}} - key: sccache-${{ env.GITHUB_CACHE_VERSION }}-${{ matrix.action }}--${{ hashFiles('**/Cargo.lock') }} - restore-keys: sccache-${{ env.GITHUB_CACHE_VERSION }}-${{ matrix.action }}- - name: Cargo ${{ matrix.action }} if: matrix.action == 'clippy' uses: actions-rs/clippy-check@v1 with: token: ${{ secrets.GITHUB_TOKEN }} args: --workspace --all-features --all-targets --locked + - name: Cargo ${{ matrix.action }} + if: matrix.action == 'fmt' + run: cargo ${{ matrix.action }} --all -- --check - name: Install cargo-nextest if: matrix.action == 'nextest' uses: taiki-e/install-action@nextest - - name: Cargo nextest + - name: Cargo ${{ matrix.action }} if: matrix.action == 'nextest' - uses: actions-rs/cargo@v1 + run: cargo ${{ matrix.action }} run --release --workspace --all-features --all-targets --locked + - name: Fast fail + uses: vishnudxb/cancel-workflow@v1.2 + if: failure() with: - command: nextest - args: run --workspace --all-features --all-targets --locked + repo: hack-ink/subapeye + workflow_id: ${{ github.run_id }} + access_token: ${{ github.token }} diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml.bak similarity index 100% rename from .github/workflows/pages.yml rename to .github/workflows/pages.yml.bak diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b6eebc4..3a4ac35 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,88 +8,24 @@ env: CARGO_TERM_COLOR: always jobs: - build: - name: Build ${{ matrix.target.name }} package - runs-on: ${{ matrix.target.os }} - strategy: - matrix: - target: - [ - { name: x86_64-unknown-linux-gnu, os: ubuntu-latest }, - { name: aarch64-apple-darwin, os: macos-latest }, - { name: x86_64-apple-darwin, os: macos-latest }, - { - name: x86_64-pc-windows-msvc, - os: windows-latest, - extension: .exe, - }, - ] - steps: - - name: Fetch latest code - uses: actions/checkout@v3 - - name: Setup Rust toolchain - run: rustup target add ${{ matrix.target.name }} - - name: Build - uses: actions-rs/cargo@v1 - with: - command: build - args: --release --locked --target ${{ matrix.target.name }} - - name: Compress - run: | - mv target/${{ matrix.target.name }}/release/${{ matrix.target.extension }} . - zstd --ultra -22 -o -${{ matrix.target.name }}.zst ${{ matrix.target.extension }} - - name: Collect artifact - run: | - mkdir -p artifacts - mv -${{ matrix.target.name }}.zst artifacts - - name: Upload artifact - uses: actions/upload-artifact@v3.1.2 - with: - name: artifacts - path: artifacts - release: name: Release runs-on: ubuntu-latest needs: [build] steps: - - name: Download artifacts - uses: actions/download-artifact@v3 - with: - name: artifacts - path: artifacts - - name: Hash - run: | - cd artifacts - sha256sum * | tee ../SHA256 - md5sum * | tee ../MD5 - mv ../SHA256 . - mv ../MD5 . - name: Publish uses: softprops/action-gh-release@v1 with: discussion_category_name: Announcements generate_release_notes: true - files: artifacts/* - # publish-on-crates-io: - # name: Publish on crates.io - # runs-on: ubuntu-latest - # steps: - # - name: Fetch latest code - # uses: actions/checkout@v3 - # - name: Login - # run: cargo login ${{ secrets.CARGO_REGISTRY_TOKEN }} - # - name: Publish - # run: .maintain/release.sh - - clean-artifacts: - name: Clean artifacts - if: always() - needs: [release] - steps: - - name: Clean artifacts - uses: geekyeggo/delete-artifact@v2 - with: - name: artifacts + publish-on-crates-io: + name: Publish on crates.io runs-on: ubuntu-latest + steps: + - name: Fetch latest code + uses: actions/checkout@v3 + - name: Login + run: cargo login ${{ secrets.CARGO_REGISTRY_TOKEN }} + - name: Publish + run: cargo publish --locked diff --git a/.github/workflows/staging.yml b/.github/workflows/staging.yml deleted file mode 100644 index 8278e8f..0000000 --- a/.github/workflows/staging.yml +++ /dev/null @@ -1,87 +0,0 @@ -name: Staging -on: - workflow_dispatch: - -env: - CARGO_TERM_COLOR: always - -jobs: - build: - name: Build ${{ matrix.target.name }} package - runs-on: ${{ matrix.target.os }} - strategy: - matrix: - target: - [ - { name: x86_64-unknown-linux-gnu, os: ubuntu-latest }, - { name: aarch64-apple-darwin, os: macos-latest }, - { name: x86_64-apple-darwin, os: macos-latest }, - { - name: x86_64-pc-windows-msvc, - os: windows-latest, - extension: .exe, - }, - ] - steps: - - name: Fetch latest code - uses: actions/checkout@v3 - - name: Setup Rust toolchain - run: rustup target add ${{ matrix.target.name }} - - name: Build - uses: actions-rs/cargo@v1 - with: - command: build - args: --release --locked --target ${{ matrix.target.name }} - - name: Compress - run: | - mv target/${{ matrix.target.name }}/release/${{ matrix.target.extension }} . - zstd --ultra -22 -o -${{ matrix.target.name }}.zst ${{ matrix.target.extension }} - - name: Collect artifact - run: | - mkdir -p artifacts - mv -${{ matrix.target.name }}.zst artifacts - - name: Upload artifact - uses: actions/upload-artifact@v3.1.2 - with: - name: artifacts - path: artifacts - - staging: - name: Staging - runs-on: ubuntu-latest - needs: [build] - steps: - - name: Download artifacts - uses: actions/download-artifact@v3 - with: - name: artifacts - path: artifacts - - name: Hash - run: | - cd artifacts - sha256sum * | tee ../SHA256 - md5sum * | tee ../MD5 - mv ../SHA256 . - mv ../MD5 . - - # publish-on-crates-io: - # name: Publish on crates.io - # runs-on: ubuntu-latest - # steps: - # - name: Fetch latest code - # uses: actions/checkout@v3 - # - name: Login - # run: cargo login ${{ secrets.CARGO_REGISTRY_TOKEN }} - # - name: Publish - # run: .maintain/release.sh - - clean-artifacts: - name: Clean artifacts - if: always() - needs: [staging] - steps: - - name: Clean artifacts - uses: geekyeggo/delete-artifact@v2 - with: - name: artifacts - runs-on: ubuntu-latest diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..2307f5c --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1439 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "affix" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e7ea84d3fa2009f355f8429a0b418a96849135a4188fadf384f59127d5d4bc" +dependencies = [ + "convert_case", +] + +[[package]] +name = "array-bytes" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b1c5a481ec30a5abd8dfbd94ab5cf1bb4e9a66be7f1b3b322f2f1170c200fd" + +[[package]] +name = "arrayvec" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" +dependencies = [ + "nodrop", +] + +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + +[[package]] +name = "async-trait" +version = "0.1.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake2-rfc" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" +dependencies = [ + "arrayvec 0.4.12", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" + +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "convert_case" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb4a24b1aaf0fd0ce8b45161144d6f42cd91677fd5940fd431183eb023b3a2b8" + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "cpufeatures" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "280a9f2d8b3a38871a3c8a46fb80db65e5e5ed97da80c4d08bf27fb63e35e181" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "frame-metadata" +version = "15.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "878babb0b136e731cc77ec2fd883ff02745ff21e6fb662729953d44923df009c" +dependencies = [ + "cfg-if", + "parity-scale-codec", + "scale-info", + "serde", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-executor" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-macro" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + +[[package]] +name = "futures-task" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-util" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" + +[[package]] +name = "js-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.141" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "mio" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.45.0", +] + +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parity-scale-codec" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "637935964ff85a605d114591d4d2c13c5d1ba2806dae97cea6bf180238a749ac" +dependencies = [ + "arrayvec 0.7.2", + "bitvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b26a931f824dd4eca30b3e43bb4f31cd5f0d3a403c5f5ff27106b805bfde7b" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "rustls" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" +dependencies = [ + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" +dependencies = [ + "base64 0.21.0", +] + +[[package]] +name = "ryu" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" + +[[package]] +name = "scale-info" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cfdffd972d76b22f3d7f81c8be34b2296afd3a25e0a547bd9abe340a4dbbe97" +dependencies = [ + "bitvec", + "cfg-if", + "derive_more", + "parity-scale-codec", + "scale-info-derive", + "serde", +] + +[[package]] +name = "scale-info-derive" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61fa974aea2d63dd18a4ec3a49d59af9f34178c73a4f56d2f18205628d00681e" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "schannel" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +dependencies = [ + "windows-sys 0.42.0", +] + +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "security-framework" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.160" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.160" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "serde_json" +version = "1.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "slab" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "subapeye" +version = "0.1.0" +dependencies = [ + "array-bytes", + "async-trait", + "futures", + "fxhash", + "num_cpus", + "parity-scale-codec", + "serde", + "serde_json", + "submetadatan", + "subrpcer", + "substorager", + "thiserror", + "tokio", + "tokio-stream", + "tokio-tungstenite", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "subhasher" +version = "0.10.0" +dependencies = [ + "blake2-rfc", + "byteorder", + "sha2", + "tiny-keccak", + "twox-hash", +] + +[[package]] +name = "submetadatan" +version = "0.10.0" +dependencies = [ + "array-bytes", + "frame-metadata", + "fxhash", + "parity-scale-codec", + "scale-info", + "substorager", + "thiserror", +] + +[[package]] +name = "subrpcer" +version = "0.10.0" +dependencies = [ + "affix", + "serde", + "serde_json", +] + +[[package]] +name = "substorager" +version = "0.10.0" +dependencies = [ + "array-bytes", + "subhasher", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "thiserror" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001" +dependencies = [ + "autocfg", + "bytes", + "libc", + "mio", + "num_cpus", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.45.0", +] + +[[package]] +name = "tokio-macros" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "tokio-rustls" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +dependencies = [ + "rustls", + "tokio", + "webpki", +] + +[[package]] +name = "tokio-stream" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fb52b74f05dbf495a8fba459fdc331812b96aa086d9eb78101fa0d4569c3313" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54319c93411147bced34cb5609a80e0a8e44c5999c93903a81cd866630ec0bfd" +dependencies = [ + "futures-util", + "log", + "rustls", + "rustls-native-certs", + "tokio", + "tokio-rustls", + "tungstenite", + "webpki", +] + +[[package]] +name = "toml_datetime" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" + +[[package]] +name = "toml_edit" +version = "0.19.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "tungstenite" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ee6ab729cd4cf0fd55218530c4522ed30b7b6081752839b68fcec8d0960788" +dependencies = [ + "base64 0.13.1", + "byteorder", + "bytes", + "http", + "httparse", + "log", + "rand", + "rustls", + "sha1", + "thiserror", + "url", + "utf-8", + "webpki", +] + +[[package]] +name = "twox-hash" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" +dependencies = [ + "cfg-if", + "rand", + "static_assertions", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 1.0.109", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" + +[[package]] +name = "web-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "winnow" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28" +dependencies = [ + "memchr", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] diff --git a/Cargo.toml b/Cargo.toml index 5cf288e..55a201a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,39 @@ [package] authors = ["Xavier Lau "] -build = "build.rs" -description = "" +description = "Substrate API client." edition = "2021" -homepage = "https://.hack.ink" +homepage = "https://subapeye.hack.ink" license = "GPL-3.0" -name = "" +name = "subapeye" readme = "README.md" -repository = "https://github.com/hack-ink/" +repository = "https://github.com/hack-ink/subapeye" version = "0.1.0" + +[features] +tracing = [] + +[dependencies] +# crates.io +array-bytes = { version = "6.1" } +async-trait = { version = "0.1" } +futures = { version = "0.3" } +fxhash = { version = "0.2" } +num_cpus = { version = "1.15" } +parity-scale-codec = { version = "3.4" } +serde = { version = "1.0", features = ["derive"] } +serde_json = { version = "1.0" } +submetadatan = { version = "0.10" } +subrpcer = { version = "0.10" } +substorager = { version = "0.10" } +thiserror = { version = "1.0" } +tokio = { version = "1.27", features = ["macros", "rt-multi-thread", "sync", "time"] } +tokio-stream = { version = "0.1" } +tokio-tungstenite = { version = "0.18", features = ["rustls-tls-native-roots"] } +tracing = { version = "0.1" } + +tracing-subscriber = { version = "0.3" } + +[patch.crates-io] +submetadatan = { version = "0.10", path = "../substrate-minimal/submetadatan" } +subrpcer = { version = "0.10", path = "../substrate-minimal/subrpcer" } +substorager = { version = "0.10", path = "../substrate-minimal/substorager" } diff --git a/README.md b/README.md index 00d358c..4e76685 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@
-# \ -### \ +# Subapeye +### Substrate API client [![License](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) -[![Checks](https://github.com/hack-ink//actions/workflows/checks.yml/badge.svg?branch=main)](https://github.com/hack-ink//actions/workflows/checks.yml) -[![Release](https://github.com/hack-ink//actions/workflows/release.yml/badge.svg)](https://github.com/hack-ink//actions/workflows/release.yml) -[![GitHub tag (latest by date)](https://img.shields.io/github/v/tag/hack-ink/)](https://github.com/hack-ink//tags) -[![GitHub code lines](https://tokei.rs/b1/github/hack-ink/)](https://github.com/hack-ink/) -[![GitHub last commit](https://img.shields.io/github/last-commit/hack-ink/?color=red&style=plastic)](https://github.com/hack-ink/) +[![Checks](https://github.com/hack-ink/subapeye/actions/workflows/checks.yml/badge.svg?branch=main)](https://github.com/hack-ink/subapeye/actions/workflows/checks.yml) +[![Release](https://github.com/hack-ink/subapeye/actions/workflows/release.yml/badge.svg)](https://github.com/hack-ink/subapeye/actions/workflows/release.yml) +[![GitHub tag (latest by date)](https://img.shields.io/github/v/tag/hack-ink/subapeye)](https://github.com/hack-ink/subapeye/tags) +[![GitHub code lines](https://tokei.rs/b1/github/hack-ink/subapeye)](https://github.com/hack-ink/subapeye) +[![GitHub last commit](https://img.shields.io/github/last-commit/hack-ink/subapeye?color=red&style=plastic)](https://github.com/hack-ink/subapeye)
diff --git a/build.rs b/build.rs deleted file mode 100644 index f6072f5..0000000 --- a/build.rs +++ /dev/null @@ -1,17 +0,0 @@ -// crates.io -use vergen::{Config, ShaKind}; - -fn main() { - let mut config = Config::default(); - - *config.git_mut().sha_kind_mut() = ShaKind::Short; - - // Disable the git version if installed from . - if vergen::vergen(config.clone()).is_err() { - *config.git_mut().enabled_mut() = false; - - println!("cargo:rustc-env=VERGEN_GIT_SHA_SHORT=crates.io"); - - vergen::vergen(config).unwrap(); - } -} diff --git a/src/apeye.rs b/src/apeye.rs new file mode 100644 index 0000000..45289f1 --- /dev/null +++ b/src/apeye.rs @@ -0,0 +1,131 @@ +//! + +pub mod api; +use api::ApiState; + +pub mod runtime; +use runtime::Runtime; + +// std +use std::{marker::PhantomData, sync::Arc}; +// crates.io +use serde::de::DeserializeOwned; +use submetadatan::{Meta, Metadata, StorageEntry}; +// subapeye +use crate::{ + jsonrpc::{Connection, Initialize, IntoRawRequest}, + prelude::*, +}; + +/// +pub trait Layer: Invoker + Runtime {} +impl Layer for T where T: Invoker + Runtime {} + +/// +#[async_trait::async_trait] +pub trait Invoker: Send + Sync { + /// + async fn request<'a, D, R>(&self, raw_request: R) -> Result + where + D: DeserializeOwned, + R: IntoRawRequest<'a>; + + /// + async fn batch<'a, D, R>(&self, raw_requests: Vec) -> Result> + where + D: DeserializeOwned, + R: IntoRawRequest<'a>; +} +#[async_trait::async_trait] +impl Invoker for T +where + T: Connection, +{ + async fn request<'a, D, R>(&self, raw_request: R) -> Result + where + D: DeserializeOwned, + R: IntoRawRequest<'a>, + { + self.request(raw_request).await.map(|r| r.result) + } + + async fn batch<'a, D, R>(&self, raw_requests: Vec) -> Result> + where + D: DeserializeOwned, + R: IntoRawRequest<'a>, + { + self.batch(raw_requests).await.map(|v| v.into_iter().map(|r| r.result).collect()) + } +} + +/// The API client for Substrate-like chain. +#[derive(Clone, Debug)] +pub struct Apeye +where + I: Invoker, + R: Runtime, +{ + /// + pub invoker: Arc, + /// + pub metadata: Metadata, + /// + pub runtime: PhantomData, +} +impl Apeye +where + I: Invoker, + R: Runtime, +{ + /// Initialize the API client with the given initializer. + pub async fn initialize(initializer: Iz) -> Result + where + Iz: Initialize, + { + let invoker = Arc::new(initializer.initialize().await?); + let mut apeye = Self { invoker, metadata: Default::default(), runtime: Default::default() }; + + apeye.metadata = + submetadatan::unprefix_raw_metadata_minimal(apeye.get_metadata::(None).await?) + .map_err(error::Generic::Submetadatan)?; + + #[cfg(feature = "tracing")] + tracing::trace!("Metadata({:?})", apeye.metadata); + + Ok(apeye) + } +} +#[async_trait::async_trait] +impl Invoker for Apeye +where + I: Invoker, + R: Runtime, +{ + async fn request<'a, D, Rq>(&self, raw_request: Rq) -> Result + where + D: DeserializeOwned, + Rq: IntoRawRequest<'a>, + { + I::request(&self.invoker, raw_request).await + } + + async fn batch<'a, D, Rq>(&self, raw_requests: Vec) -> Result> + where + D: DeserializeOwned, + Rq: IntoRawRequest<'a>, + { + I::batch(&self.invoker, raw_requests).await + } +} +impl Meta for Apeye +where + I: Invoker, + R: Runtime, +{ + fn storage<'a, 'b>(&'a self, pallet: &str, item: &'b str) -> Option> + where + 'a: 'b, + { + self.metadata.storage(pallet, item) + } +} diff --git a/src/apeye/api.rs b/src/apeye/api.rs new file mode 100644 index 0000000..e0fa3f4 --- /dev/null +++ b/src/apeye/api.rs @@ -0,0 +1,41 @@ +//! + +pub mod prelude { + //! + + pub use crate::apeye::{ + api::{Argument, Parameter, Response}, + runtime::Runtime, + Layer, + }; +} + +pub mod base; +pub use base::*; + +pub mod ext; +pub use ext::*; + +pub mod frontier; +pub use frontier::*; + +// std +use std::fmt::Debug; +// crates.io +use serde::{de::DeserializeOwned, Serialize}; + +/// +pub trait Api: ApiBase + ApiExt + ApiFrontier {} +impl Api for T where T: ApiBase + ApiExt + ApiFrontier {} + +/// +pub trait Argument: Debug + Send + Sync {} +impl Argument for T where T: Debug + Send + Sync {} + +/// +pub trait Parameter: Serialize + Argument {} +impl Parameter for T where T: Serialize + Argument {} + +/// +pub trait Response: Debug + DeserializeOwned {} +impl Response for T where T: Debug + DeserializeOwned {} diff --git a/src/apeye/api/base.rs b/src/apeye/api/base.rs new file mode 100644 index 0000000..96cf5a1 --- /dev/null +++ b/src/apeye/api/base.rs @@ -0,0 +1,19 @@ +//! + +pub mod chain; +pub use chain::Api as ApiChain; + +pub mod net; +pub use net::Api as ApiNet; + +pub mod state; +pub use state::Api as ApiState; + +pub use Api as ApiBase; + +// subapeye +use crate::apeye::api::prelude::*; + +/// +pub trait Api: ApiChain + ApiNet + ApiState {} +impl Api for T where T: Layer {} diff --git a/src/apeye/api/base/chain.rs b/src/apeye/api/base/chain.rs new file mode 100644 index 0000000..5816055 --- /dev/null +++ b/src/apeye/api/base/chain.rs @@ -0,0 +1,66 @@ +//! + +// crates.io +use subrpcer::chain; +// subapeye +use crate::{apeye::api::prelude::*, prelude::*}; + +/// +#[async_trait::async_trait] +pub trait Api { + /// + async fn get_block(&self, hash: Option<&str>) -> Result + where + R: Response; + + /// + async fn get_block_hash(&self, list_or_value: Option) -> Result + where + LoV: Parameter, + R: Response; + + /// + async fn get_finalized_head(&self) -> Result + where + R: Response; + + /// + async fn get_header(&self, hash: Option<&str>) -> Result + where + R: Response; +} +#[async_trait::async_trait] +impl Api for T +where + T: Layer, +{ + async fn get_block(&self, hash: Option<&str>) -> Result + where + R: Response, + { + self.request::(chain::get_block_raw(hash)).await + } + + async fn get_block_hash(&self, list_or_value: Option) -> Result + where + LoV: Parameter, + R: Response, + { + self.request::(chain::get_block_hash_raw(list_or_value)).await + } + + async fn get_finalized_head(&self) -> Result + where + R: Response, + { + self.request::(chain::get_finalized_head_raw()).await + } + + /// + async fn get_header(&self, hash: Option<&str>) -> Result + where + R: Response, + { + self.request::(chain::get_header_raw(hash)).await + } +} diff --git a/src/apeye/api/base/net.rs b/src/apeye/api/base/net.rs new file mode 100644 index 0000000..24d96d0 --- /dev/null +++ b/src/apeye/api/base/net.rs @@ -0,0 +1,51 @@ +//! + +// crates.io +use subrpcer::net; +// subapeye +use crate::{apeye::api::prelude::*, prelude::*}; + +/// +#[async_trait::async_trait] +pub trait Api { + /// + async fn listening(&self) -> Result + where + R: Response; + + /// + async fn peer_count(&self) -> Result + where + R: Response; + + /// + async fn version(&self) -> Result + where + R: Response; +} +#[async_trait::async_trait] +impl Api for T +where + T: Layer, +{ + async fn listening(&self) -> Result + where + R: Response, + { + self.request::(net::listening_raw()).await + } + + async fn peer_count(&self) -> Result + where + R: Response, + { + self.request::(net::peer_count_raw()).await + } + + async fn version(&self) -> Result + where + R: Response, + { + self.request::(net::version_raw()).await + } +} diff --git a/src/apeye/api/base/state.rs b/src/apeye/api/base/state.rs new file mode 100644 index 0000000..f4787f0 --- /dev/null +++ b/src/apeye/api/base/state.rs @@ -0,0 +1,81 @@ +//! + +// crates.io +use subrpcer::state; +// subapeye +use crate::{apeye::api::prelude::*, prelude::*}; + +/// +#[async_trait::async_trait] +pub trait Api { + /// + async fn get_keys(&self, prefix: P, hash: Option<&str>) -> Result + where + P: Parameter, + R: Response; + + /// + async fn get_metadata(&self, hash: Option<&str>) -> Result + where + R: Response; + + /// + async fn get_pairs(&self, prefix: P, hash: Option<&str>) -> Result + where + P: Parameter, + R: Response; + + /// + async fn get_read_proof(&self, keys: K, hash: Option<&str>) -> Result + where + K: Parameter, + R: Response; + + /// + async fn get_runtime_version(&self, hash: Option<&str>) -> Result + where + R: Response; +} +#[async_trait::async_trait] +impl Api for T +where + T: Layer, +{ + async fn get_keys(&self, prefix: P, hash: Option<&str>) -> Result + where + P: Parameter, + R: Response, + { + self.request::(state::get_keys_raw(prefix, hash)).await + } + + async fn get_metadata(&self, hash: Option<&str>) -> Result + where + R: Response, + { + self.request::(state::get_metadata_raw(hash)).await + } + + async fn get_pairs(&self, prefix: P, hash: Option<&str>) -> Result + where + P: Parameter, + R: Response, + { + self.request::(state::get_pairs_raw(prefix, hash)).await + } + + async fn get_read_proof(&self, keys: K, hash: Option<&str>) -> Result + where + K: Parameter, + R: Response, + { + self.request::(state::get_read_proof_raw(keys, hash)).await + } + + async fn get_runtime_version(&self, hash: Option<&str>) -> Result + where + R: Response, + { + self.request::(state::get_runtime_version_raw(hash)).await + } +} diff --git a/src/apeye/api/ext.rs b/src/apeye/api/ext.rs new file mode 100644 index 0000000..2f812a3 --- /dev/null +++ b/src/apeye/api/ext.rs @@ -0,0 +1,199 @@ +//! + +pub use Api as ApiExt; + +// crates.io +use parity_scale_codec::Encode; +use submetadatan::{Meta, StorageEntry, StorageEntryType}; +use subrpcer::state; +// subapeye +use crate::{apeye::api::prelude::*, prelude::*}; + +/// +pub trait Api: ApiQuery {} +impl Api for T where T: Layer + Meta {} + +/// +#[async_trait::async_trait] +pub trait ApiQuery { + /// + fn query_of<'a, E>(&'a self, pallet: &'a str, item: &'a str) -> Result> + where + E: EncodableArgs; + + /// + async fn query(&self, storage_query: &StorageQuery) -> Result + where + R: Response; +} +#[async_trait::async_trait] +impl ApiQuery for T +where + T: Layer + Meta, +{ + fn query_of<'a, E>(&'a self, pallet: &'a str, item: &'a str) -> Result> + where + E: EncodableArgs, + { + let storage_entry = self + .storage(pallet, item) + .ok_or_else(|| error::Apeye::StorageNotFound(format!("{pallet}::{item}")))?; + + Ok(StorageQueryArgs::new(storage_entry)) + } + + async fn query(&self, storage_query: &StorageQuery) -> Result + where + R: Response, + { + self.request::(state::get_storage_raw(&storage_query.key, storage_query.at.as_ref())) + .await + } +} + +/// +pub trait EncodableArgs { + /// + const LENGTH: usize; + + /// + fn encode(&self) -> [Vec; Self::LENGTH]; +} +impl EncodableArgs for () { + const LENGTH: usize = 0; + + fn encode(&self) -> [Vec; Self::LENGTH] { + [] + } +} +impl EncodableArgs for (E,) +where + E: Encode, +{ + const LENGTH: usize = 1; + + fn encode(&self) -> [Vec; Self::LENGTH] { + [self.0.encode()] + } +} +impl EncodableArgs for (E, E1) +where + E: Encode, + E1: Encode, +{ + const LENGTH: usize = 2; + + fn encode(&self) -> [Vec; Self::LENGTH] { + [self.0.encode(), self.1.encode()] + } +} +impl EncodableArgs for (E, E1, E2) +where + E: Encode, + E1: Encode, + E2: Encode, +{ + const LENGTH: usize = 3; + + fn encode(&self) -> [Vec; Self::LENGTH] { + [self.0.encode(), self.1.encode(), self.2.encode()] + } +} +impl EncodableArgs for (E, E1, E2, E3) +where + E: Encode, + E1: Encode, + E2: Encode, + E3: Encode, +{ + const LENGTH: usize = 4; + + fn encode(&self) -> [Vec; Self::LENGTH] { + [self.0.encode(), self.1.encode(), self.2.encode(), self.3.encode()] + } +} + +/// +pub struct StorageQuery<'a> { + /// + pub key: String, + /// + pub at: Option<&'a str>, +} +/// +pub struct StorageQueryArgs<'a, E> +where + E: EncodableArgs, +{ + /// + pub storage_entry: StorageEntry<'a>, + /// + pub keys: Option>, + /// + pub at: Option<&'a str>, +} +impl<'a, E> StorageQueryArgs<'a, E> +where + E: EncodableArgs, +{ + /// + pub fn new(storage_entry: StorageEntry<'a>) -> Self { + Self { storage_entry, keys: None, at: None } + } + + /// + pub fn keys(mut self, keys: Keys<'a, E>) -> Self { + self.keys = Some(keys); + + self + } + + /// + pub fn at(mut self, at: &'a str) -> Self { + self.at = Some(at); + + self + } + + /// + pub fn construct(self) -> Result> + where + [(); E::LENGTH]:, + { + let key = match &self.storage_entry.r#type { + StorageEntryType::Plain => + substorager::storage_value_key(self.storage_entry.prefix, self.storage_entry.item), + StorageEntryType::Map(hashers) => match self.keys.ok_or(error::Apeye::KeysNotFound)? { + Keys::Raw(keys) => substorager::storage_n_map_key( + self.storage_entry.prefix, + self.storage_entry.item, + hashers.iter().zip(keys.encode().iter()).collect::>(), + ), + Keys::Encoded(keys) => substorager::storage_n_map_key( + self.storage_entry.prefix, + self.storage_entry.item, + hashers.iter().zip(keys.iter()).collect::>(), + ), + }, + } + .to_string(); + + #[cfg(feature = "tracing")] + tracing::trace!("StorageKey({key})"); + + Ok(StorageQuery { key, at: self.at }) + } +} + +/// +pub enum Keys<'a, E> +where + E: EncodableArgs, +{ + /// + Raw(&'a E), + /// + Encoded(&'a [&'a [u8]]), + // /// + // Hashed(&'a [&'a [u8]]), +} diff --git a/src/apeye/api/frontier.rs b/src/apeye/api/frontier.rs new file mode 100644 index 0000000..12cc323 --- /dev/null +++ b/src/apeye/api/frontier.rs @@ -0,0 +1,10 @@ +//! + +pub use Api as ApiFrontier; + +// subapeye +use crate::apeye::api::prelude::*; + +/// +pub trait Api {} +impl Api for T where T: Layer {} diff --git a/src/apeye/runtime.rs b/src/apeye/runtime.rs new file mode 100644 index 0000000..faf6c82 --- /dev/null +++ b/src/apeye/runtime.rs @@ -0,0 +1,30 @@ +//! + +// crates.io +use array_bytes::{Hex, TryFromHex}; +// subapeye +use crate::apeye::{Apeye, Invoker}; + +/// +pub trait Runtime: Send + Sync { + /// + type BlockNumber: ParameterConvertor; + /// + type Hash: ParameterConvertor; + /// + type AccountId: ParameterConvertor; +} + +/// +pub trait ParameterConvertor: Hex + TryFromHex {} +impl ParameterConvertor for T where T: Hex + TryFromHex {} + +impl Runtime for Apeye +where + I: Invoker, + R: Runtime, +{ + type AccountId = R::AccountId; + type BlockNumber = R::BlockNumber; + type Hash = R::Hash; +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..66fb717 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,90 @@ +//! Subapeye error collections. + +use thiserror::Error as ThisError; + +/// Main error. +#[allow(missing_docs)] +#[derive(Debug, ThisError)] +pub enum Error { + #[error(transparent)] + Quick(#[from] Quick), + + #[error(transparent)] + Apeye(#[from] Apeye), + #[error(transparent)] + Generic(#[from] Generic), + #[error(transparent)] + Jsonrpc(#[from] Jsonrpc), + #[error(transparent)] + Tokio(#[from] Tokio), +} + +/// An error helper/wrapper to debug/print the error quickly. +#[derive(Debug)] +pub struct Quick(String); +impl std::fmt::Display for Quick { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::Debug::fmt(self, f) + } +} +impl std::error::Error for Quick {} +/// Wrap the error with [`Quick`]. +pub fn quick_err(e: E) -> Quick +where + E: std::fmt::Debug, +{ + Quick(format!("{e:?}")) +} + +/// Api error. +#[allow(missing_docs)] +#[derive(Debug, ThisError)] +pub enum Apeye { + #[error("[apeye] can not find keys of the storage map")] + KeysNotFound, + #[error("[apeye] can not find the storage from runtime, {0:?}")] + StorageNotFound(String), +} + +/// Generic error. +#[allow(missing_docs)] +#[derive(Debug, ThisError)] +pub enum Generic { + #[error("{0:?}")] + AlmostImpossible(&'static str), + // #[error(transparent)] + // Codec(#[from] parity_scale_codec::Error), + #[error(transparent)] + Serde(#[from] serde_json::Error), + #[error(transparent)] + Tungstenite(#[from] tokio_tungstenite::tungstenite::Error), + #[error(transparent)] + Submetadatan(#[from] submetadatan::Error), +} +/// Wrap the error with [`Generic::AlmostImpossible`]. +pub fn almost_impossible(e_msg: &'static str) -> Generic { + Generic::AlmostImpossible(e_msg) +} + +/// JSONRPC error. +#[allow(missing_docs)] +#[derive(Debug, ThisError)] +pub enum Jsonrpc { + // #[error("[core::jsonrpc] empty batch")] + // EmptyBatch, + #[error("[jsonrpc] exceeded the maximum number of request queue size, {0:?}")] + ExceededRequestQueueMaxSize(crate::jsonrpc::Id), +} + +/// Tokio error. +#[allow(missing_docs)] +#[derive(Debug, ThisError)] +pub enum Tokio { + #[error(transparent)] + OneshotRecv(tokio::sync::oneshot::error::RecvError), + // https://github.com/tokio-rs/tokio/blob/master/tokio/src/sync/mpsc/error.rs#L12 + #[error("channel closed")] + MpscSend, + #[error(transparent)] + Elapsed(tokio::time::error::Elapsed), +} diff --git a/src/jsonrpc.rs b/src/jsonrpc.rs new file mode 100644 index 0000000..68a06d3 --- /dev/null +++ b/src/jsonrpc.rs @@ -0,0 +1,145 @@ +//! Subapeye core JSONRPC library. + +pub mod ws; +pub use ws::{Ws, WsInitializer}; + +// std +use std::sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, +}; +// crates.io +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use serde_json::Value; +// subapeye +use crate::prelude::*; + +/// JSONRPC Id. +pub type Id = usize; + +/// JSONRPC version. +pub const VERSION: &str = "2.0"; + +/// +#[async_trait::async_trait] +pub trait Initialize { + /// + type Connection; + + /// + async fn initialize(self) -> Result; +} +#[async_trait::async_trait] +impl<'a> Initialize for WsInitializer<'a> { + type Connection = Ws; + + async fn initialize(self) -> Result { + // #[cfg(feature = "tracing")] + // tracing::trace!("Connecting({uri})"); + + self.initialize().await + } +} + +/// +pub trait IntoRawRequest<'a>: Send + Into> {} +impl<'a, T> IntoRawRequest<'a> for T where T: Send + Into> {} + +/// +#[async_trait::async_trait] +pub trait Jsonrpc { + /// Send a single request. + async fn request<'a, D, R>(&self, raw_request: R) -> Result> + where + D: DeserializeOwned, + R: IntoRawRequest<'a>; + + /// Send a single request. + async fn batch<'a, D, R>(&self, raw_requests: Vec) -> Result>> + where + D: DeserializeOwned, + R: IntoRawRequest<'a>; +} + +/// +pub trait Connection: Send + Sync + Jsonrpc {} +impl Connection for T where T: Send + Sync + Jsonrpc {} + +/// Generic JSONRPC request. +#[allow(missing_docs)] +#[derive(Clone, Debug, Serialize)] +pub struct Request<'a, P> { + #[serde(borrow)] + pub jsonrpc: &'a str, + pub id: Id, + #[serde(borrow)] + pub method: &'a str, + pub params: P, +} +/// Raw JSONRPC request. +#[allow(missing_docs)] +#[derive(Clone, Debug)] +pub struct RequestRaw<'a, P> { + pub method: &'a str, + pub params: P, +} +impl<'a, P> From<(&'a str, P)> for RequestRaw<'a, P> { + fn from(raw: (&'a str, P)) -> Self { + Self { method: raw.0, params: raw.1 } + } +} + +/// Generic JSONRPC response. +#[allow(missing_docs)] +#[derive(Clone, Debug, Deserialize)] +pub struct Response { + pub jsonrpc: String, + pub id: Id, + pub result: R, +} + +#[derive(Debug)] +struct RequestQueue { + size: Id, + active: Arc<()>, + next: AtomicUsize, +} +impl RequestQueue { + fn consume_once(&self) -> Result> { + let active = Arc::strong_count(&self.active); + + #[cfg(feature = "tracing")] + tracing::trace!("RequestQueue({active}/{})", self.size); + + if active == self.size { + Err(error::Jsonrpc::ExceededRequestQueueMaxSize(self.size))? + } else { + Ok(RequestQueueGuard { + lock: self.next.fetch_add(1, Ordering::SeqCst), + _strong: self.active.clone(), + }) + } + } + + fn consume(&self, count: Id) -> Result>> { + let active = Arc::strong_count(&self.active); + + #[cfg(feature = "tracing")] + tracing::trace!("RequestQueue({active}/{})", self.size); + + if active == self.size { + Err(error::Jsonrpc::ExceededRequestQueueMaxSize(self.size))? + } else { + Ok(RequestQueueGuard { + lock: (0..count).map(|_| self.next.fetch_add(1, Ordering::SeqCst)).collect(), + _strong: self.active.clone(), + }) + } + } +} + +#[derive(Debug)] +struct RequestQueueGuard { + lock: L, + _strong: Arc<()>, +} diff --git a/src/jsonrpc/ws.rs b/src/jsonrpc/ws.rs new file mode 100644 index 0000000..762fada --- /dev/null +++ b/src/jsonrpc/ws.rs @@ -0,0 +1,225 @@ +//! Full functionality WS JSONRPC client implementation. +//! Follow specification. + +pub mod initializer; +pub use initializer::*; + +// std +use std::{str, time::Duration}; +// crates.io +use futures::stream::{SplitSink, SplitStream}; +use fxhash::FxHashMap; +use tokio::{ + net::TcpStream, + sync::{mpsc, oneshot}, + time, +}; +use tokio_tungstenite::{ + tungstenite::{error::Result as WsResult, Message}, + MaybeTlsStream, WebSocketStream, +}; +// subapeye +use crate::jsonrpc::*; + +type CallSender = mpsc::Sender; +type CallReceiver = mpsc::Receiver; + +type WsSender = SplitSink>, Message>; +type WsReceiver = SplitStream>>; + +type ExitSender = oneshot::Sender<()>; +type ExitReceiver = oneshot::Receiver<()>; + +type RequestResponse = Response; +type RequestNotifier = oneshot::Sender; +type RequestPool = FxHashMap; + +type BatchResponse = Vec; +type BatchNotifier = oneshot::Sender; +type BatchPool = FxHashMap; + +/// A Ws instance. +/// +/// Use this to interact with the server. +#[derive(Debug)] +pub struct Ws { + messenger: CallSender, + request_queue: RequestQueue, + request_timeout: Duration, + closer: Option, +} +impl Drop for Ws { + fn drop(&mut self) { + if let Some(c) = self.closer.take() { + let _ = c.send(()); + } else { + // + } + } +} +#[async_trait::async_trait] +impl Jsonrpc for Ws { + /// Send a single request. + async fn request<'a, D, R>(&self, raw_request: R) -> Result> + where + D: DeserializeOwned, + R: Send + Into>, + { + let RequestQueueGuard { lock: id, .. } = self.request_queue.consume_once()?; + let RequestRaw { method, params } = raw_request.into(); + let (tx, rx) = oneshot::channel(); + + // Debug. + // self.messenger.send(Call::Debug(id)).await.map_err(|_| error::Tokio::MpscSend)?; + self.messenger + .send(Call::Single(RawCall { + id, + request: serde_json::to_string(&Request { jsonrpc: VERSION, id, method, params }) + .map_err(error::Generic::Serde)?, + notifier: tx, + })) + .await + .map_err(|_| error::Tokio::MpscSend)?; + + let response = time::timeout(self.request_timeout, rx) + .await + .map_err(error::Tokio::Elapsed)? + .map_err(error::Tokio::OneshotRecv)?; + + Ok(Response { + jsonrpc: response.jsonrpc, + id: response.id, + result: serde_json::from_value(response.result).map_err(error::Generic::Serde)?, + }) + } + + /// Send a batch of requests. + async fn batch<'a, D, R>(&self, raw_requests: Vec) -> Result>> + where + D: DeserializeOwned, + R: Send + Into>, + { + if raw_requests.is_empty() { + return Ok(Vec::new()); + // Err(error::Jsonrpc::EmptyBatch)?; + } + + let RequestQueueGuard { lock: ids, .. } = self.request_queue.consume(raw_requests.len())?; + let id = ids.first().expect("[core::jsonrpc] `raw_requests` never empty; qed").to_owned(); + let requests = ids + .into_iter() + .zip(raw_requests.into_iter()) + .map(|(id, raw_request)| { + let RequestRaw { method, params } = raw_request.into(); + + Request { jsonrpc: VERSION, id, method, params } + }) + .collect::>(); + let request = serde_json::to_string(&requests).map_err(error::Generic::Serde)?; + let (tx, rx) = oneshot::channel(); + + self.messenger + .send(Call::Batch(RawCall { id, request, notifier: tx })) + .await + .map_err(|_| error::Tokio::MpscSend)?; + + let mut responses = time::timeout(self.request_timeout, rx) + .await + .map_err(error::Tokio::Elapsed)? + .map_err(error::Tokio::OneshotRecv)? + .into_iter() + .map(|r| { + Ok(Response { + jsonrpc: r.jsonrpc, + id: r.id, + result: serde_json::from_value(r.result).map_err(error::Generic::Serde)?, + }) + }) + .collect::>>()?; + + // Each id is unique. + responses.sort_unstable_by_key(|r| r.id); + + Ok(responses) + } +} + +#[derive(Debug, Default)] +struct Pool { + requests: RequestPool, + batches: BatchPool, +} +impl Pool { + fn new() -> Self { + Default::default() + } + + async fn on_ws_recv(&mut self, response: WsResult) -> Result<()> { + match response { + Ok(msg) => { + match msg { + Message::Binary(raw_response) => + self.process_raw_response(str::from_utf8(&raw_response).unwrap()).await, + Message::Text(raw_response) => self.process_raw_response(&raw_response).await, + Message::Ping(_) => tracing::trace!("ping"), + Message::Pong(_) => tracing::trace!("pong"), + Message::Close(_) => tracing::trace!("close"), + Message::Frame(_) => tracing::trace!("frame"), + } + + Ok(()) + }, + Err(e) => Err(error::Generic::Tungstenite(e))?, + } + } + + // TODO: error handling + async fn process_raw_response(&mut self, raw_response: &str) { + #[cfg(feature = "tracing")] + tracing::trace!("RawResponse({raw_response})"); + + if let Ok(response) = serde_json::from_str::(raw_response) { + if response.id == 0 { + return; + } + + let notifier = self.requests.remove(&response.id).unwrap(); + + if let Err(e) = notifier.send(response) { + tracing::error!("{e:?}"); + } + } else if let Ok(responses) = serde_json::from_str::(raw_response) { + let notifier = self.batches.remove(&responses.first().unwrap().id).unwrap(); + + if let Err(e) = notifier.send(responses) { + tracing::error!("{e:?}"); + } + } else { + // RawResponse({"jsonrpc":"2.0","error":{"code":-32601,"message":"Method not + // found"},"id":2}) + tracing::error!("unable to process raw message"); + } + } +} + +#[derive(Debug)] +enum Call { + // Debug. + // Debug(Id), + Single(RawCall), + Batch(RawCall), +} + +// A single request object. +// `id`: Request Id. +// +// Or +// +// A batch requests object to send several request objects simultaneously. +// `id`: The first request's id. +#[derive(Debug)] +struct RawCall { + id: Id, + request: String, + notifier: N, +} diff --git a/src/jsonrpc/ws/initializer.rs b/src/jsonrpc/ws/initializer.rs new file mode 100644 index 0000000..9b6af94 --- /dev/null +++ b/src/jsonrpc/ws/initializer.rs @@ -0,0 +1,303 @@ +//! + +pub use Initializer as WsInitializer; + +// std +use std::{future::Future, pin::Pin, str, time::Duration}; +// crates.io +use futures::{ + future::{self, Either, Fuse}, + stream, FutureExt, SinkExt, StreamExt, +}; +use tokio_stream::wrappers::IntervalStream; +// subapeye +use crate::{jsonrpc::ws::*, prelude::*}; + +type GenericConnect = Box< + dyn FnOnce( + Duration, + WsSender, + WsReceiver, + CallReceiver, + ExitReceiver, + ) -> Pin + Send>> + + Send, +>; + +/// [`Ws`] initializer. +#[derive(Clone, Debug)] +pub struct Initializer<'a> { + /// URI to connect to. + /// + /// Default: `ws://127.0.0.1:9944`. + pub uri: &'a str, + /// Maximum concurrent task count. + pub max_concurrency: Id, + /// Send tick with this interval to keep the WS alive. + pub interval: Duration, + /// Request timeout. + pub request_timeout: Duration, + /// Future selector. + pub future_selector: FutureSelector, +} +impl<'a> Initializer<'a> { + /// Create a initializer with default configurations. + pub fn new() -> Self { + Self::default() + } + + /// Set the [`uri`](#structfield.uri). + pub fn uri(mut self, uri: &'a str) -> Self { + self.uri = uri; + + self + } + + /// Set the [`max_concurrency`](#structfield.max_concurrency). + pub fn max_concurrency(mut self, max_concurrency: Id) -> Self { + self.max_concurrency = max_concurrency; + + self + } + + /// Set the [`interval`](#structfield.interval). + pub fn interval(mut self, interval: Duration) -> Self { + self.interval = interval; + + self + } + + /// Set the [`request_timeout`](#structfield.request_timeout). + pub fn request_timeout(mut self, request_timeout: Duration) -> Self { + self.request_timeout = request_timeout; + + self + } + + /// Set the [`future_selector`](#structfield.future_selector). + pub fn future_selector(mut self, future_selector: FutureSelector) -> Self { + self.future_selector = future_selector; + + self + } + + /// Initialize the connection. + pub async fn initialize(self) -> Result { + let (messenger, closer) = self + .connect(match self.future_selector { + FutureSelector::Futures => Box::new(connect_futures), + FutureSelector::Tokio => Box::new(connect_tokio), + }) + .await?; + + Ok(Ws { + messenger, + request_queue: RequestQueue { + size: self.max_concurrency, + active: Arc::new(()), + // Id 0 is reserved for system health check. + next: AtomicUsize::new(1), + }, + request_timeout: self.request_timeout, + closer: Some(closer), + }) + } + + async fn connect(&self, connect_inner: GenericConnect) -> Result<(CallSender, ExitSender)> { + let interval = self.interval; + let (ws_tx, ws_rx) = tokio_tungstenite::connect_async(self.uri) + .await + .map_err(error::Generic::Tungstenite)? + .0 + .split(); + let (call_tx, call_rx) = mpsc::channel(self.max_concurrency); + let (exit_tx, exit_rx) = oneshot::channel(); + + tokio::spawn(async move { connect_inner(interval, ws_tx, ws_rx, call_rx, exit_rx).await }); + + Ok((call_tx, exit_tx)) + } +} +impl<'a> Default for Initializer<'a> { + fn default() -> Self { + Self { + uri: "ws://127.0.0.1:9944", + max_concurrency: num_cpus::get(), + interval: Duration::from_secs(10), + request_timeout: Duration::from_secs(30), + future_selector: FutureSelector::default(), + } + } +} + +/// Async future selectors. +#[derive(Clone, Debug)] +pub enum FutureSelector { + /// Use [`futures::future::select`]. + Futures, + /// Use [`tokio::select!`]. + Tokio, +} +impl Default for FutureSelector { + fn default() -> Self { + Self::Tokio + } +} + +fn connect_futures( + interval: Duration, + mut ws_tx: WsSender, + mut ws_rx: WsReceiver, + call_rx: CallReceiver, + exit_rx: ExitReceiver, +) -> Pin + Send>> { + Box::pin(async move { + let call_rx = stream::unfold(call_rx, |mut r| async { r.recv().await.map(|c| (c, r)) }); + + futures::pin_mut!(call_rx); + + let mut rxs_fut = future::select(call_rx.next(), ws_rx.next()); + // TODO: clean dead items? + let mut pool = Pool::new(); + // Minimum interval is 1ms. + let interval_max = interval.max(Duration::from_millis(1)); + let mut interval_max = IntervalStream::new(time::interval(interval_max)); + // Disable the tick, if the interval is zero. + let mut exit_or_interval_fut = future::select( + exit_rx, + if interval.is_zero() { Fuse::terminated() } else { interval_max.next().fuse() }, + ); + + loop { + match future::select(rxs_fut, exit_or_interval_fut).await { + Either::Left((Either::Left((maybe_call, ws_rx_next)), exit_or_interval_fut_)) => { + if let Some(call) = maybe_call { + match call { + // Debug. + // Call::Debug(_) => { + // tracing::debug!("{call:?}"); + // }, + Call::Single(RawCall { id, request, notifier }) => { + #[cfg(feature = "tracing")] + tracing::trace!("Request({request})"); + + ws_tx.send(Message::Text(request)).await.unwrap(); + pool.requests.insert(id, notifier); + }, + Call::Batch(RawCall { id, request, notifier }) => { + #[cfg(feature = "tracing")] + tracing::trace!("BatchRequests({request})"); + + ws_tx.send(Message::Text(request)).await.unwrap(); + pool.batches.insert(id, notifier); + }, + } + } else { + // + } + + rxs_fut = future::select(call_rx.next(), ws_rx_next); + exit_or_interval_fut = exit_or_interval_fut_; + }, + Either::Left(( + Either::Right((maybe_response, call_rx_next)), + exit_or_interval_fut_, + )) => { + if let Some(response) = maybe_response { + pool.on_ws_recv(response).await.unwrap() + } else { + // TODO?: closed + } + + rxs_fut = future::select(call_rx_next, ws_rx.next()); + exit_or_interval_fut = exit_or_interval_fut_; + }, + Either::Right((Either::Left((_, _)), _)) => break, + Either::Right((Either::Right((_, exit_rx)), rxs_fut_)) => { + #[cfg(feature = "tracing")] + tracing::trace!("TickRequest(Ping)"); + + ws_tx.send(Message::Text("Ping".into())).await.unwrap(); + + rxs_fut = rxs_fut_; + exit_or_interval_fut = future::select( + exit_rx, + if interval.is_zero() { + Fuse::terminated() + } else { + interval_max.next().fuse() + }, + ); + }, + } + } + }) +} + +fn connect_tokio( + interval: Duration, + mut ws_tx: WsSender, + mut ws_rx: WsReceiver, + mut call_rx: CallReceiver, + mut exit_rx: ExitReceiver, +) -> Pin + Send>> { + Box::pin(async move { + // TODO: clean dead items? + let mut pool = Pool::new(); + // Minimum interval is 1ms. + let interval_max = interval.max(Duration::from_millis(1)); + let mut interval_max = IntervalStream::new(time::interval(interval_max)); + // Disable the tick, if the interval is zero. + let mut interval_fut = + if interval.is_zero() { Fuse::terminated() } else { interval_max.next().fuse() }; + + loop { + tokio::select! { + maybe_request = call_rx.recv() => { + if let Some(call) = maybe_request { + match call { + // Debug. + // Call::Debug(_) => { + // tracing::debug!("{call:?}"); + // } + Call::Single(RawCall { id, request, notifier }) => { + #[cfg(feature = "tracing")] + tracing::trace!("Request({request})"); + + ws_tx.send(Message::Text(request)).await.unwrap(); + pool.requests.insert(id, notifier); + } + Call::Batch(RawCall { id, request, notifier }) => { + #[cfg(feature = "tracing")] + tracing::trace!("BatchRequests({request})"); + + ws_tx.send(Message::Text(request)).await.unwrap(); + pool.batches.insert(id, notifier); + } + } + } else { + // + } + }, + maybe_response = ws_rx.next() => { + if let Some(response) = maybe_response { + pool.on_ws_recv(response).await.unwrap() + } else { + // TODO?: closed + } + } + _ = &mut interval_fut => { + #[cfg(feature = "tracing")] + tracing::trace!("TickRequest(Ping)"); + + ws_tx.send(Message::Text("Ping".into())).await.unwrap(); + + interval_fut = interval_max.next().fuse(); + }, + _ = &mut exit_rx => { + break; + }, + } + } + }) +} diff --git a/src/lib.rs b/src/lib.rs index e69de29..58bcde8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -0,0 +1,21 @@ +//! Substrate API client. + +#![deny(missing_docs)] +// https://github.com/rust-lang/rust/issues/60551 +#![allow(incomplete_features)] +#![feature(generic_const_exprs)] + +pub mod prelude { + //! Subapeye core prelude. + + pub use std::result::Result as StdResult; + + pub use crate::error::{self, Error}; + + /// Subapeye's `Result` type. + pub type Result = StdResult; +} + +pub mod apeye; +pub mod error; +pub mod jsonrpc; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..5bf7efe --- /dev/null +++ b/src/main.rs @@ -0,0 +1,59 @@ +use array_bytes::TryFromHex; +use serde_json::Value; +use subapeye::{ + apeye::{api::*, runtime::Runtime, Apeye}, + jsonrpc::WsInitializer, +}; + +#[tokio::main] +async fn main() { + tracing_subscriber::fmt::init(); + + enum R {} + impl Runtime for R { + type AccountId = [u8; 32]; + type BlockNumber = u32; + type Hash = [u8; 32]; + } + + let apeye = + >::initialize(WsInitializer::default().uri("wss://kusama-rpc.polkadot.io")) + .await + .unwrap(); + + // let hashes = apeye.get_block_hash::<_, Vec>(Some([0, 1, 2])).await.unwrap(); + + // dbg!(hashes); + + // for h in hashes { + // dbg!(apeye.get_block::(Some(&h)).await.unwrap()); + // dbg!(apeye.get_header::(Some(&h)).await.unwrap()); + // } + + // dbg!(apeye.get_finalized_head::().await.unwrap()); + // dbg!(apeye.get_metadata::(None).await.unwrap()); + + // dbg!(apeye + // .query::(&apeye.query_of::<()>("System", "Number").unwrap().construct().unwrap()) + // .await + // .unwrap()); + dbg!(apeye + .query::( + &apeye + .query_of("Staking", "ErasValidatorPrefs") + .unwrap() + .keys(Keys::Raw(&( + 5_044_u32, + ::AccountId::try_from_hex( + "0x305b1689cfee594c19a642a2fcd554074c93d62181c0d4117ebe196bd7c62b79" + ) + .unwrap() + ))) + .construct() + .unwrap() + ) + .await + .unwrap()); + + // dbg!(apeye.version::().await.unwrap()); +}