diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..4ba404d --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,7 @@ +[target.'cfg(all())'] +rustflags = [ + "-Dclippy::all", + "-Dlet_underscore_drop", + "-Aclippy::assertions_on_result_states", + "-Aclippy::let_unit_value", +] \ No newline at end of file diff --git a/.github/workflows/check_lint_build.yaml b/.github/workflows/check_lint_build.yaml new file mode 100644 index 0000000..9132001 --- /dev/null +++ b/.github/workflows/check_lint_build.yaml @@ -0,0 +1,233 @@ +on: [pull_request, push] + +name: Check, Lint, Build + +env: + CARGO_TERM_COLOR: always + +jobs: + check-lint-build-stable: + name: Check, Lint, Build (ubuntu stable) + runs-on: ubuntu-latest + permissions: + contents: write + timeout-minutes: 20 + # env: + # RUSTFLAGS: -D warnings + steps: + - uses: actions/checkout@v2 + - name: Install latest nightly toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly + components: rustfmt, clippy + + - name: Rust Cache + uses: Swatinem/rust-cache@v2.5.1 + + - name: Rustfmt + run: cargo fmt --all -- --check + + - name: Cargo check + run: cargo check + + - name: Clippy + run: cargo clippy --all-targets --all-features + + - name: Build + run: cargo build --release + + - name: 'Set environment variables: version number and output filenames' + run: | + THUNDER_APP_VERSION=$(cargo metadata --format-version 1 | \ + jq -er '.packages | map(select(.name == "thunder_app") | .version) | .[0]') + THUNDER_APP_FILENAME="thunder-${THUNDER_APP_VERSION}-x86_64-unknown-linux-gnu" + THUNDER_CLI_FILENAME="thunder-cli-${THUNDER_APP_VERSION}-x86_64-unknown-linux-gnu" + echo "THUNDER_APP_VERSION=$THUNDER_APP_VERSION" >> "$GITHUB_ENV" + echo "THUNDER_APP_FILENAME=$THUNDER_APP_FILENAME" >> "$GITHUB_ENV" + echo "THUNDER_CLI_FILENAME=$THUNDER_CLI_FILENAME" >> "$GITHUB_ENV" + + + - name: 'Set filenames for release binaries' + run: | + pushd "target/release" + ln -fs "thunder_app" "${THUNDER_APP_FILENAME}" + ln -fs "thunder_app_cli" "${THUNDER_CLI_FILENAME}" + popd + + - name: 'Upload Artifacts (thunder)' + uses: actions/upload-artifact@v4 + with: + name: ${{ env.THUNDER_APP_FILENAME }} + if-no-files-found: error + path: | + target/release/${{ env.THUNDER_APP_FILENAME }} + target/release/${{ env.THUNDER_CLI_FILENAME }} + + - name: Release + uses: softprops/action-gh-release@v2.0.2 + if: startsWith(github.ref, 'refs/tags/') + with: + files: | + target/release/${{ env.THUNDER_APP_FILENAME }} + target/release/${{ env.THUNDER_CLI_FILENAME }} + fail_on_unmatched_files: true + + build-macos: + name: Build (macos-x86_64) + runs-on: macos-latest + permissions: + contents: write + timeout-minutes: 20 + # env: + # RUSTFLAGS: -D warnings + steps: + - uses: actions/checkout@v2 + - name: Install latest nightly toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly + + - name: Rust Cache + uses: Swatinem/rust-cache@v2.5.1 + + - name: Build + run: cargo build --release + + - name: 'Set environment variables: version number and output filenames' + run: | + THUNDER_APP_VERSION=$(cargo metadata --format-version 1 | \ + jq -er '.packages | map(select(.name == "thunder_app") | .version) | .[0]') + THUNDER_APP_FILENAME="thunder-${THUNDER_APP_VERSION}-x86_64-apple-darwin" + THUNDER_CLI_FILENAME="thunder-cli-${THUNDER_APP_VERSION}-x86_64-apple-darwin" + echo "THUNDER_APP_VERSION=$THUNDER_APP_VERSION" >> "$GITHUB_ENV" + echo "THUNDER_APP_FILENAME=$THUNDER_APP_FILENAME" >> "$GITHUB_ENV" + echo "THUNDER_CLI_FILENAME=$THUNDER_CLI_FILENAME" >> "$GITHUB_ENV" + + - name: 'Set filenames for release binaries' + run: | + pushd "target/release" + ln -fs "thunder_app" "${THUNDER_APP_FILENAME}" + ln -fs "thunder_app_cli" "${THUNDER_CLI_FILENAME}" + popd + + - name: 'Upload Artifacts (thunder)' + uses: actions/upload-artifact@v4 + with: + name: ${{ env.THUNDER_APP_FILENAME }} + if-no-files-found: error + path: | + target/release/${{ env.THUNDER_APP_FILENAME }} + target/release/${{ env.THUNDER_CLI_FILENAME }} + + - name: Release + uses: softprops/action-gh-release@v2.0.2 + if: startsWith(github.ref, 'refs/tags/') + with: + files: | + target/release/${{ env.THUNDER_APP_FILENAME }} + target/release/${{ env.THUNDER_CLI_FILENAME }} + fail_on_unmatched_files: true + + build-windows: + name: Build (x86_64-pc-windows-gnu) + runs-on: ubuntu-latest + permissions: + contents: write + timeout-minutes: 20 + # env: + # RUSTFLAGS: -D warnings + steps: + - uses: actions/checkout@v2 + - name: Install latest nightly toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly + target: x86_64-pc-windows-gnu + + - name: Install mingw-w64 + run: sudo apt install mingw-w64 + + - name: Rust Cache + uses: Swatinem/rust-cache@v2.5.1 + + - name: Build + run: cargo build --release --target x86_64-pc-windows-gnu + env: + RUSTFLAGS: "-C linker=/usr/bin/x86_64-w64-mingw32-gcc" + + - name: 'Set environment variables: version number and output filenames' + run: | + THUNDER_APP_VERSION=$(cargo metadata --format-version 1 | \ + jq -er '.packages | map(select(.name == "thunder_app") | .version) | .[0]') + THUNDER_APP_FILENAME="thunder-${THUNDER_APP_VERSION}-x86_64-pc-windows-gnu.exe" + THUNDER_CLI_FILENAME="thunder-cli-${THUNDER_APP_VERSION}-x86_64-pc-windows-gnu.exe" + echo "THUNDER_APP_VERSION=$THUNDER_APP_VERSION" >> "$GITHUB_ENV" + echo "THUNDER_APP_FILENAME=$THUNDER_APP_FILENAME" >> "$GITHUB_ENV" + echo "THUNDER_CLI_FILENAME=$THUNDER_CLI_FILENAME" >> "$GITHUB_ENV" + + + - name: 'Set filenames for release binaries' + run: | + pushd "target/x86_64-pc-windows-gnu/release" + ln -fs "thunder_app.exe" "${THUNDER_APP_FILENAME}" + ln -fs "thunder_app_cli.exe" "${THUNDER_CLI_FILENAME}" + popd + + - name: 'Upload Artifacts (thunder)' + uses: actions/upload-artifact@v4 + with: + name: ${{ env.THUNDER_APP_FILENAME }} + if-no-files-found: error + path: | + target/x86_64-pc-windows-gnu/release/${{ env.THUNDER_APP_FILENAME }} + target/x86_64-pc-windows-gnu/release/${{ env.THUNDER_CLI_FILENAME }} + + - name: Release + uses: softprops/action-gh-release@v2.0.2 + if: startsWith(github.ref, 'refs/tags/') + with: + files: | + target/x86_64-pc-windows-gnu/release/${{ env.THUNDER_APP_FILENAME }} + target/x86_64-pc-windows-gnu/release/${{ env.THUNDER_CLI_FILENAME }} + fail_on_unmatched_files: true + + upload-releases-to-releases-drivechain-info: + name: Upload releases to releases.drivechain.info + runs-on: ubuntu-latest + needs: [check-lint-build-stable, build-macos, build-windows] + if: startsWith(github.ref, 'refs/tags/') + steps: + - name: Download release assets + uses: robinraju/release-downloader@v1.9 + with: + tag: ${{ github.ref_name }} + + - name: Create zip files for releases.drivechain.info + run: | + shopt -s extglob + mv thunder-+([0-9]).+([0-9]).+([0-9])-x86_64-apple-darwin thunder-latest-x86_64-apple-darwin + mv thunder-cli-+([0-9]).+([0-9]).+([0-9])-x86_64-apple-darwin thunder-cli-latest-x86_64-apple-darwin + zip L2-S9-Thunder-latest-x86_64-apple-darwin.zip \ + thunder-latest-x86_64-apple-darwin \ + thunder-cli-latest-x86_64-apple-darwin + mv thunder-+([0-9]).+([0-9]).+([0-9])-x86_64-pc-windows-gnu.exe thunder-latest-x86_64-pc-windows-gnu.exe + mv thunder-cli-+([0-9]).+([0-9]).+([0-9])-x86_64-pc-windows-gnu.exe thunder-cli-latest-x86_64-pc-windows-gnu.exe + zip L2-S9-Thunder-latest-x86_64-pc-windows-gnu.zip \ + thunder-latest-x86_64-pc-windows-gnu.exe \ + thunder-cli-latest-x86_64-pc-windows-gnu.exe + mv thunder-+([0-9]).+([0-9]).+([0-9])-x86_64-unknown-linux-gnu thunder-latest-x86_64-unknown-linux-gnu + mv thunder-cli-+([0-9]).+([0-9]).+([0-9])-x86_64-unknown-linux-gnu thunder-cli-latest-x86_64-unknown-linux-gnu + zip L2-S9-Thunder-latest-x86_64-unknown-linux-gnu.zip \ + thunder-latest-x86_64-unknown-linux-gnu \ + thunder-cli-latest-x86_64-unknown-linux-gnu + + - name: Upload release assets to releases.drivechain.info + uses: cross-the-world/ssh-scp-ssh-pipelines@latest + with: + host: 45.33.96.47 + user: root + pass: ${{ secrets.RELEASES_SERVER_PW }} + port: 22 + scp: | + 'L2-S9-Thunder-latest-*.zip' => '/var/www/html/' diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..4f8b656 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "mainchain"] + path = mainchain + url = https://github.com/LayerTwo-Labs/mainchain.git diff --git a/Cargo.lock b/Cargo.lock index 2ad35a7..5c31f22 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "ab_glyph" -version = "0.2.21" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5110f1c78cf582855d895ecd0746b653db010cec6d9f5575293f27934d980a39" +checksum = "6f90148830dac590fac7ccfe78ec4a8ea404c60f75a24e16407a71f0f40de775" dependencies = [ "ab_glyph_rasterizer", "owned_ttf_parser", @@ -20,65 +20,67 @@ checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" [[package]] name = "accesskit" -version = "0.11.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76eb1adf08c5bcaa8490b9851fd53cca27fa9880076f178ea9d29f05196728a8" +checksum = "74a4b14f3d99c1255dcba8f45621ab1a2e7540a0009652d33989005a4d0bfc6b" [[package]] name = "accesskit_consumer" -version = "0.15.2" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04bb4d9e4772fe0d47df57d0d5dbe5d85dd05e2f37ae1ddb6b105e76be58fb00" +checksum = "8c17cca53c09fbd7288667b22a201274b9becaa27f0b91bf52a526db95de45e6" dependencies = [ "accesskit", ] [[package]] name = "accesskit_macos" -version = "0.9.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134d0acf6acb667c89d3332999b1a5df4edbc8d6113910f392ebb73f2b03bb56" +checksum = "cd3b6ae1eabbfbced10e840fd3fce8a93ae84f174b3e4ba892ab7bcb42e477a7" dependencies = [ "accesskit", "accesskit_consumer", - "objc2", + "objc2 0.3.0-beta.3.patch-leaks.3", "once_cell", ] [[package]] name = "accesskit_unix" -version = "0.5.2" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e084cb5168790c0c112626175412dc5ad127083441a8248ae49ddf6725519e83" +checksum = "09f46c18d99ba61ad7123dd13eeb0c104436ab6af1df6a1cd8c11054ed394a08" dependencies = [ "accesskit", "accesskit_consumer", "async-channel", + "async-once-cell", "atspi", - "futures-lite", + "futures-lite 1.13.0", + "once_cell", "serde", "zbus", ] [[package]] name = "accesskit_windows" -version = "0.14.3" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9eac0a7f2d7cd7a93b938af401d3d8e8b7094217989a7c25c55a953023436e31" +checksum = "afcae27ec0974fc7c3b0b318783be89fd1b2e66dd702179fe600166a38ff4a0b" dependencies = [ "accesskit", "accesskit_consumer", - "arrayvec", "once_cell", "paste", - "windows", + "static_assertions", + "windows 0.48.0", ] [[package]] name = "accesskit_winit" -version = "0.14.4" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "825d23acee1bd6d25cbaa3ca6ed6e73faf24122a774ec33d52c5c86c6ab423c0" +checksum = "5284218aca17d9e150164428a0ebc7b955f70e3a9a78b4c20894513aabf98a67" dependencies = [ "accesskit", "accesskit_macos", @@ -104,40 +106,51 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" -version = "0.8.3" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", + "getrandom", "once_cell", "version_check", + "zerocopy", ] [[package]] name = "aho-corasick" -version = "1.0.5" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + [[package]] name = "android-activity" -version = "0.4.3" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64529721f27c2314ced0890ce45e469574a73e5e6fdd6e9da1860eb29285f5e0" +checksum = "ee91c0c2905bae44f84bfa4e044536541df26b7703fd0888deeb9060fcc44289" dependencies = [ "android-properties", - "bitflags 1.3.2", + "bitflags 2.5.0", "cc", + "cesu8", + "jni", "jni-sys", "libc", "log", "ndk", "ndk-context", "ndk-sys", - "num_enum 0.6.1", + "num_enum", + "thiserror", ] [[package]] @@ -146,74 +159,85 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" -version = "0.5.0" +version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.2" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" [[package]] name = "anstyle-parse" -version = "0.2.1" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "2.1.0" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" dependencies = [ "anstyle", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "anyhow" -version = "1.0.75" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3" +dependencies = [ + "backtrace", +] [[package]] name = "arboard" -version = "3.2.1" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac57f2b058a76363e357c056e4f74f1945bf734d37b8b3ef49066c4787dde0fc" +checksum = "9fb4009533e8ff8f1450a5bcbc30f4242a1d34442221f72314bea1f5dc9c7f89" dependencies = [ "clipboard-win", "log", - "objc", - "objc-foundation", - "objc_id", + "objc2 0.5.1", + "objc2-app-kit", + "objc2-foundation", "parking_lot", - "thiserror", - "winapi", "x11rb", ] @@ -229,38 +253,54 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" + +[[package]] +name = "ash" +version = "0.37.3+1.3.251" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a" +dependencies = [ + "libloading 0.7.4", +] + [[package]] name = "async-broadcast" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c48ccdbf6ca6b121e0f586cbc0e73ae440e56c67c30fa0873b4e110d9c26d2b" dependencies = [ - "event-listener", + "event-listener 2.5.3", "futures-core", ] [[package]] name = "async-channel" -version = "1.9.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +checksum = "136d4d23bcc79e27423727b36823d86233aad06dfea531837b038394d11e9928" dependencies = [ "concurrent-queue", - "event-listener", + "event-listener 5.3.0", + "event-listener-strategy 0.5.2", "futures-core", + "pin-project-lite", ] [[package]] name = "async-executor" -version = "1.5.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb" +checksum = "b10202063978b3351199d68f8b22c4e47e4b1b822f8d43fd862d5ea8c006b29a" dependencies = [ - "async-lock", "async-task", "concurrent-queue", - "fastrand 1.9.0", - "futures-lite", + "fastrand 2.1.0", + "futures-lite 2.3.0", "slab", ] @@ -270,10 +310,10 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" dependencies = [ - "async-lock", + "async-lock 2.8.0", "autocfg", "blocking", - "futures-lite", + "futures-lite 1.13.0", ] [[package]] @@ -282,125 +322,193 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" dependencies = [ - "async-lock", + "async-lock 2.8.0", "autocfg", "cfg-if", "concurrent-queue", - "futures-lite", + "futures-lite 1.13.0", "log", "parking", - "polling", - "rustix 0.37.23", + "polling 2.8.0", + "rustix 0.37.27", "slab", - "socket2 0.4.9", + "socket2 0.4.10", "waker-fn", ] +[[package]] +name = "async-io" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcccb0f599cfa2f8ace422d3555572f47424da5648a4382a9dd0310ff8210884" +dependencies = [ + "async-lock 3.3.0", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite 2.3.0", + "parking", + "polling 3.7.0", + "rustix 0.38.34", + "slab", + "tracing", + "windows-sys 0.52.0", +] + [[package]] name = "async-lock" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" dependencies = [ - "event-listener", + "event-listener 2.5.3", +] + +[[package]] +name = "async-lock" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" +dependencies = [ + "event-listener 4.0.3", + "event-listener-strategy 0.4.0", + "pin-project-lite", ] +[[package]] +name = "async-once-cell" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9338790e78aa95a416786ec8389546c4b6a1dfc3dc36071ed9518a9413a542eb" + [[package]] name = "async-process" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9d28b1d97e08915212e2e45310d47854eafa69600756fc735fb788f75199c9" +checksum = "ea6438ba0a08d81529c69b36700fa2f95837bfe3e776ab39cde9c14d9149da88" dependencies = [ - "async-io", - "async-lock", - "autocfg", + "async-io 1.13.0", + "async-lock 2.8.0", + "async-signal", "blocking", "cfg-if", - "event-listener", - "futures-lite", - "rustix 0.37.23", - "signal-hook", + "event-listener 3.1.0", + "futures-lite 1.13.0", + "rustix 0.38.34", "windows-sys 0.48.0", ] [[package]] name = "async-recursion" -version = "1.0.4" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.61", +] + +[[package]] +name = "async-signal" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afe66191c335039c7bb78f99dc7520b0cbb166b3a1cb33a03f53d8a1c6f2afda" +dependencies = [ + "async-io 2.3.2", + "async-lock 3.3.0", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 0.38.34", + "signal-hook-registry", + "slab", + "windows-sys 0.52.0", ] [[package]] name = "async-task" -version = "4.4.0" +version = "4.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.73" +version = "0.1.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.61", ] [[package]] name = "atomic-waker" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] -name = "atomic_refcell" -version = "0.1.11" +name = "atspi" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112ef6b3f6cb3cb6fc5b6b494ef7a848492cff1ab0ef4de10b0f7d572861c905" +checksum = "6059f350ab6f593ea00727b334265c4dfc7fd442ee32d264794bd9bdc68e87ca" +dependencies = [ + "atspi-common", + "atspi-connection", + "atspi-proxies", +] [[package]] -name = "atspi" -version = "0.10.1" +name = "atspi-common" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "674e7a3376837b2e7d12d34d58ac47073c491dc3bf6f71a7adaf687d4d817faa" +checksum = "92af95f966d2431f962bc632c2e68eda7777330158bf640c4af4249349b2cdf5" dependencies = [ - "async-recursion", - "async-trait", - "atspi-macros", "enumflags2", - "futures-lite", "serde", - "tracing", + "static_assertions", "zbus", "zbus_names", + "zvariant", ] [[package]] -name = "atspi-macros" -version = "0.2.0" +name = "atspi-connection" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fb4870a32c0eaa17e35bca0e6b16020635157121fb7d45593d242c295bc768" +checksum = "a0c65e7d70f86d4c0e3b2d585d9bf3f979f0b19d635a336725a88d279f76b939" dependencies = [ - "quote", - "syn 1.0.109", + "atspi-common", + "atspi-proxies", + "futures-lite 1.13.0", + "zbus", +] + +[[package]] +name = "atspi-proxies" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6495661273703e7a229356dcbe8c8f38223d697aacfaf0e13590a9ac9977bb52" +dependencies = [ + "atspi-common", + "serde", + "zbus", ] [[package]] name = "autocfg" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line", "cc", @@ -419,15 +527,27 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.3" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bech32" -version = "0.9.1" +version = "0.10.0-beta" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" +checksum = "98f7eed2b2781a6f0b5c903471d48e15f56fb4e1165df8a9a2337fd1a59d45ea" [[package]] name = "beef" @@ -449,33 +569,62 @@ dependencies = [ [[package]] name = "bip300301" -version = "0.1.0" -source = "git+https://github.com/nchashch/bip300301?rev=cf917605ab1937c57f19f72311f96ac0b4832de0#cf917605ab1937c57f19f72311f96ac0b4832de0" +version = "0.1.1" +source = "git+https://github.com/Ash-L2L/bip300301.git?rev=64568dee7b89fe8c021226f10b17a18fe3386871#64568dee7b89fe8c021226f10b17a18fe3386871" dependencies = [ - "base64 0.21.3", + "base64 0.21.7", "bitcoin", "hex", + "hex-conservative", "http", - "jsonrpsee 0.19.0", + "jsonrpsee", "serde", "serde_json", + "serde_with", "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", ] +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bitcoin" -version = "0.30.1" +version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e99ff7289b20a7385f66a0feda78af2fc119d28fb56aea8886a9cd0a4abdd75" +checksum = "6c85783c2fe40083ea54a33aa2f0ba58831d90fcd190f5bdc47e74e84d2a96ae" dependencies = [ "bech32", - "bitcoin-private", - "bitcoin_hashes", + "bitcoin-internals", + "bitcoin_hashes 0.13.0", + "hex-conservative", "hex_lit", "secp256k1", "serde", ] +[[package]] +name = "bitcoin-internals" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb" +dependencies = [ + "serde", +] + [[package]] name = "bitcoin-private" version = "0.1.0" @@ -489,6 +638,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d7066118b13d4b20b23645932dfb3a81ce7e29f95726c2036fa33cd7b092501" dependencies = [ "bitcoin-private", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b" +dependencies = [ + "bitcoin-internals", + "hex-conservative", "serde", ] @@ -500,22 +659,24 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +dependencies = [ + "serde", +] [[package]] name = "blake3" -version = "1.4.1" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "199c42ab6972d92c9f8995f086273d25c42fc0f7b2a1fcefba465c1352d25ba5" +checksum = "30cca6d3674597c30ddf2c587bf8d9d65c9a84d2326d941cc79c9842dfe0ef52" dependencies = [ "arrayref", "arrayvec", "cc", "cfg-if", "constant_time_eq", - "digest 0.10.7", ] [[package]] @@ -548,7 +709,16 @@ version = "0.1.0-beta.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa55741ee90902547802152aaf3f8e5248aab7e21468089560d4c8840561146" dependencies = [ - "objc-sys", + "objc-sys 0.2.0-beta.2", +] + +[[package]] +name = "block-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae85a0696e7ea3b835a453750bf002770776609115e6d25c6d2ff28a8200f7e7" +dependencies = [ + "objc-sys 0.3.3", ] [[package]] @@ -557,95 +727,150 @@ version = "0.2.0-alpha.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8dd9e63c1744f755c2f60332b88de39d341e5e86239014ad839bd71c106dec42" dependencies = [ - "block-sys", - "objc2-encode", + "block-sys 0.1.0-beta.1", + "objc2-encode 2.0.0-pre.2", +] + +[[package]] +name = "block2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b55663a85f33501257357e6421bb33e769d5c9ffb5ba0921c975a123e35e68" +dependencies = [ + "block-sys 0.2.1", + "objc2 0.4.1", +] + +[[package]] +name = "block2" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43ff7d91d3c1d568065b06c899777d1e48dcf76103a672a0adbc238a7f247f1e" +dependencies = [ + "objc2 0.5.1", ] [[package]] name = "blocking" -version = "1.3.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" +checksum = "495f7104e962b7356f0aeb34247aca1fe7d2e783b346582db7f2904cb5717e88" dependencies = [ "async-channel", - "async-lock", + "async-lock 3.3.0", "async-task", - "atomic-waker", - "fastrand 1.9.0", - "futures-lite", - "log", + "futures-io", + "futures-lite 2.3.0", + "piper", +] + +[[package]] +name = "borsh" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbe5b10e214954177fb1dc9fbd20a1a2608fe99e6c832033bdc7cea287a20d77" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a8646f94ab393e43e8b35a2558b1624bed28b97ee09c5d15456e3c9463f46d" +dependencies = [ + "once_cell", + "proc-macro-crate 3.1.0", + "proc-macro2", + "quote", + "syn 2.0.61", + "syn_derive", ] [[package]] name = "bs58" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" dependencies = [ - "sha2 0.10.7", + "sha2", "tinyvec", ] [[package]] name = "bumpalo" -version = "3.13.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" -version = "1.13.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" +checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.4.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdde5c9cd29ebd706ce1b35600920a33550e402fc998a2e53ad3b42c3c47a192" +checksum = "4da9a32f3fed317401fa3c862968128267c3106685286e15d5aaa3d7389c2f60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.61", ] [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.4.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "calloop" -version = "0.10.6" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e0d00eb1ea24371a97d2da6201c6747a633dc6dc1988ef503403b4c59504a8" +checksum = "fba7adb4dd5aa98e5553510223000e7148f621165ec5f9acd7113f6ca4995298" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", "log", - "nix 0.25.1", - "slotmap", + "polling 3.7.0", + "rustix 0.38.34", + "slab", "thiserror", - "vec_map", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f0ea9b9476c7fad82841a8dbb380e2eae480c21910feba80725b46931ed8f02" +dependencies = [ + "calloop", + "rustix 0.38.34", + "wayland-backend", + "wayland-client", ] [[package]] name = "cc" -version = "1.0.83" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" dependencies = [ "jobserver", "libc", + "once_cell", ] [[package]] @@ -677,61 +902,58 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.1" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c8d502cbaec4595d2e7d5f61e318f05417bd2b66fdc3809498f0d3fdf0bea27" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" dependencies = [ "clap_builder", "clap_derive", - "once_cell", ] [[package]] name = "clap_builder" -version = "4.4.1" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5891c7bc0edb3e1c2204fc5e94009affabeb1821c9e5fdc3959536c5c0bb984d" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim", + "strsim 0.11.1", ] [[package]] name = "clap_derive" -version = "4.4.0" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9fd1a5729c4548118d7d70ff234a44868d00489a4b6597b0b020918a0e91a1a" +checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.61", ] [[package]] name = "clap_lex" -version = "0.5.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "clipboard-win" -version = "4.5.0" +version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362" +checksum = "79f4473f5144e20d9aceaf2972478f06ddf687831eafeeb434fbaf0acc4144ad" dependencies = [ "error-code", - "str-buf", - "winapi", ] [[package]] name = "cocoa" -version = "0.24.1" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a" +checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" dependencies = [ "bitflags 1.3.2", "block", @@ -745,19 +967,28 @@ dependencies = [ [[package]] name = "cocoa-foundation" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "931d3837c286f56e3c58423ce4eba12d08db2374461a785c86f672b08b5650d6" +checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" dependencies = [ "bitflags 1.3.2", "block", "core-foundation", "core-graphics-types", - "foreign-types", "libc", "objc", ] +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + [[package]] name = "color_quant" version = "1.1.0" @@ -766,29 +997,66 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] -name = "combine" -version = "4.6.6" +name = "com" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" +checksum = "7e17887fd17353b65b1b2ef1c526c83e26cd72e74f598a8dc1bee13a48f3d9f6" dependencies = [ - "bytes", - "memchr", + "com_macros", ] [[package]] -name = "concurrent-queue" -version = "2.2.0" +name = "com_macros" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" +checksum = "d375883580a668c7481ea6631fc1a8863e33cc335bf56bfad8d7e6d4b04b13a5" +dependencies = [ + "com_macros_support", + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "com_macros_support" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad899a1087a9296d5644792d7cb72b8e34c1bec8e7d4fbc002230169a6e8710c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ "crossbeam-utils", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "constant_time_eq" version = "0.3.0" @@ -797,9 +1065,9 @@ checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" [[package]] name = "core-foundation" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", @@ -807,15 +1075,15 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "core-graphics" -version = "0.22.3" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -826,9 +1094,9 @@ dependencies = [ [[package]] name = "core-graphics-types" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bb142d41022986c1d8ff29103a1411c8a3dfad3552f87a4f8dc50d61d4f4e33" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -837,74 +1105,55 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.9" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] [[package]] name = "crc32fast" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.8" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" dependencies = [ "cfg-if", - "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ - "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.15" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "autocfg", - "cfg-if", "crossbeam-utils", - "memoffset 0.9.0", - "scopeguard", ] [[package]] name = "crossbeam-queue" -version = "0.3.8" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" dependencies = [ - "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.16" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" -dependencies = [ - "cfg-if", -] +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" [[package]] name = "crypto-common" @@ -916,24 +1165,103 @@ dependencies = [ "typenum", ] +[[package]] +name = "ctrlc" +version = "3.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "672465ae37dc1bc6380a6547a8883d5dd397b0f1faaad4f265726cc7042a5345" +dependencies = [ + "nix 0.28.0", + "windows-sys 0.52.0", +] + +[[package]] +name = "cursor-icon" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" + [[package]] name = "curve25519-dalek" -version = "3.2.0" +version = "4.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348" dependencies = [ - "byteorder", - "digest 0.9.0", - "rand_core 0.5.1", + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest 0.10.7", + "fiat-crypto", + "platforms", + "rustc_version", "subtle", "zeroize", ] +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.61", +] + +[[package]] +name = "darling" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn 2.0.61", +] + +[[package]] +name = "darling_macro" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.61", +] + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "zeroize", +] + [[package]] name = "deranged" -version = "0.3.8" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] [[package]] name = "derivation-path" @@ -1005,71 +1333,92 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "libloading 0.8.0", + "libloading 0.8.3", +] + +[[package]] +name = "document-features" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef5282ad69563b5fc40319526ba27e0e7363d552a896f0297d54f767717f9b95" +dependencies = [ + "litrs", ] [[package]] name = "downcast-rs" -version = "1.2.0" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "doxygen-rs" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" +checksum = "415b6ec780d34dcf624666747194393603d0373b7141eef01d12ee58881507d9" +dependencies = [ + "phf", +] [[package]] name = "ecolor" -version = "0.22.0" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e479a7fa3f23d4e794f8b2f8b3568dd4e47886ad1b12c9c095e141cb591eb63" +checksum = "20930a432bbd57a6d55e07976089708d4893f3d556cf42a0d79e9e321fa73b10" dependencies = [ "bytemuck", ] [[package]] name = "ed25519" -version = "1.5.3" +version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ + "pkcs8", "serde", "signature", ] [[package]] name = "ed25519-dalek" -version = "1.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" dependencies = [ "curve25519-dalek", "ed25519", "merlin", - "rand 0.7.3", + "rand_core", "serde", - "serde_bytes", - "sha2 0.9.9", + "sha2", + "subtle", "zeroize", ] [[package]] name = "ed25519-dalek-bip32" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d2be62a4061b872c8c0873ee4fc6f101ce7b889d039f019c5fa2af471a59908" +checksum = "6b49a684b133c4980d7ee783936af771516011c8cd15f429dbda77245e282f03" dependencies = [ "derivation-path", "ed25519-dalek", "hmac", - "sha2 0.10.7", + "sha2", ] [[package]] name = "eframe" -version = "0.22.0" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4596583a2c680c55b6feaa748f74890c4f9cb9c7cb69d6117110444cb65b2f" +checksum = "020e2ccef6bbcec71dbc542f7eed64a5846fc3076727f5746da8fd307c91bab2" dependencies = [ "bytemuck", "cocoa", + "document-features", "egui", + "egui-wgpu", "egui-winit", "egui_glow", "glow", @@ -1079,21 +1428,25 @@ dependencies = [ "js-sys", "log", "objc", + "parking_lot", "percent-encoding", - "raw-window-handle", + "raw-window-handle 0.5.2", + "raw-window-handle 0.6.1", + "static_assertions", "thiserror", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "web-time", "winapi", "winit", ] [[package]] name = "egui" -version = "0.22.0" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3aef8ec3ae1b772f340170c65bf27d5b8c28f543a0116c844d2ac08d01123e7" +checksum = "584c5d1bf9a67b25778a3323af222dbe1a1feb532190e103901187f92c7fe29a" dependencies = [ "accesskit", "ahash", @@ -1102,58 +1455,77 @@ dependencies = [ "nohash-hasher", ] +[[package]] +name = "egui-wgpu" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469ff65843f88a702b731a1532b7d03b0e8e96d283e70f3a22b0e06c46cb9b37" +dependencies = [ + "bytemuck", + "document-features", + "egui", + "epaint", + "log", + "thiserror", + "type-map", + "web-time", + "wgpu", + "winit", +] + [[package]] name = "egui-winit" -version = "0.22.0" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a49155fd4a0a4fb21224407a91de0030847972ef90fc64edb63621caea61cb2" +checksum = "2e3da0cbe020f341450c599b35b92de4af7b00abde85624fd16f09c885573609" dependencies = [ "accesskit_winit", "arboard", "egui", - "instant", "log", - "raw-window-handle", + "raw-window-handle 0.6.1", "smithay-clipboard", + "web-time", "webbrowser", "winit", ] [[package]] name = "egui_glow" -version = "0.22.0" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f8c2752cdf1b0ef5fcda59a898cacabad974d4f5880e92a420b2c917022da64" +checksum = "e0e5d975f3c86edc3d35b1db88bb27c15dde7c55d3b5af164968ab5ede3f44ca" dependencies = [ "bytemuck", "egui", "glow", "log", - "memoffset 0.6.5", + "memoffset 0.9.1", "wasm-bindgen", "web-sys", + "winit", ] [[package]] name = "either" -version = "1.9.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" [[package]] name = "emath" -version = "0.22.0" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3857d743a6e0741cdd60b622a74c7a36ea75f5f8f11b793b41d905d2c9721a4b" +checksum = "e4c3a552cfca14630702449d35f41c84a0d15963273771c6059175a803620f3f" dependencies = [ "bytemuck", ] [[package]] name = "enumflags2" -version = "0.7.7" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c041f5090df68b32bcd905365fd51769c8b9d553fe87fde0b683534f10c01bd2" +checksum = "3278c9d5fb675e0a51dabcf4c0d355f692b064171535ba72361be1528a9d8e8d" dependencies = [ "enumflags2_derive", "serde", @@ -1161,24 +1533,44 @@ dependencies = [ [[package]] name = "enumflags2_derive" -version = "0.7.7" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e9a1f9f7d83e59740248a6e14ecf93929ade55027844dfcea78beafccc15745" +checksum = "5c785274071b1b420972453b306eeca06acf4633829db4223b58a2a8c5953bc4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.61", +] + +[[package]] +name = "env_filter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +dependencies = [ + "log", +] + +[[package]] +name = "env_logger" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "log", ] [[package]] name = "epaint" -version = "0.22.0" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09333964d4d57f40a85338ba3ca5ed4716070ab184dcfed966b35491c5c64f3b" +checksum = "b381f8b149657a4acf837095351839f32cd5c4aec1817fc4df84e18d76334176" dependencies = [ "ab_glyph", "ahash", - "atomic_refcell", "bytemuck", "ecolor", "emath", @@ -1195,40 +1587,84 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.3" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ - "errno-dragonfly", "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] -name = "errno-dragonfly" -version = "0.1.2" +name = "error-code" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0474425d51df81997e2f90a21591180b38eccf27292d755f3e30750225c175b" + +[[package]] +name = "event-listener" +version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2" dependencies = [ - "cc", - "libc", + "concurrent-queue", + "parking", + "pin-project-lite", ] [[package]] -name = "error-code" -version = "2.3.1" +name = "event-listener" +version = "4.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21" +checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" dependencies = [ - "libc", - "str-buf", + "concurrent-queue", + "parking", + "pin-project-lite", ] [[package]] name = "event-listener" -version = "2.5.3" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +checksum = "6d9944b8ca13534cdfb2800775f8dd4902ff3fc75a50101466decadfdf322a24" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" +dependencies = [ + "event-listener 4.0.3", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +dependencies = [ + "event-listener 5.3.0", + "pin-project-lite", +] + +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" [[package]] name = "fastrand" @@ -1241,24 +1677,30 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "fdeflate" -version = "0.3.0" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d329bdeac514ee06249dabc27877490f17f5d371ec693360768b838e19f3ae10" +checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" dependencies = [ "simd-adler32", ] +[[package]] +name = "fiat-crypto" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38793c55593b33412e3ae40c2c9781ffaa6f438f6f8c10f24e71846fbd7ae01e" + [[package]] name = "flate2" -version = "1.0.27" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" dependencies = [ "crc32fast", "miniz_oxide", @@ -1272,36 +1714,49 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foreign-types" -version = "0.3.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" dependencies = [ + "foreign-types-macros", "foreign-types-shared", ] +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.61", +] + [[package]] name = "foreign-types-shared" -version = "0.1.1" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" [[package]] name = "form_urlencoded" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] [[package]] name = "futures" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", + "futures-executor", "futures-io", "futures-sink", "futures-task", @@ -1310,9 +1765,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", @@ -1320,15 +1775,26 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] [[package]] name = "futures-io" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-lite" @@ -1345,34 +1811,47 @@ dependencies = [ "waker-fn", ] +[[package]] +name = "futures-lite" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" +dependencies = [ + "fastrand 2.1.0", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-macro" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.61", ] [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-timer" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" dependencies = [ "gloo-timers", "send_wrapper", @@ -1380,9 +1859,9 @@ dependencies = [ [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-channel", "futures-core", @@ -1408,41 +1887,30 @@ dependencies = [ [[package]] name = "gethostname" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "getrandom" -version = "0.1.16" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" dependencies = [ - "cfg-if", "libc", - "wasi 0.9.0+wasi-snapshot-preview1", + "windows-targets 0.48.5", ] [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", ] [[package]] name = "gimli" -version = "0.28.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "gl_generator" @@ -1457,9 +1925,9 @@ dependencies = [ [[package]] name = "gloo-net" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a66b4e3c7d9ed8d315fd6b97c8b1f74a7c6ecbbc2320e65ae7ed38b7068cc620" +checksum = "8ac9e8288ae2c632fa9f8657ac70bfe38a1530f345282d7ba66a1f70b72b7dc4" dependencies = [ "futures-channel", "futures-core", @@ -1490,9 +1958,9 @@ dependencies = [ [[package]] name = "gloo-utils" -version = "0.1.7" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037fcb07216cb3a30f7292bd0176b050b7b9a052ba830ef7d5d65f6dc64ba58e" +checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa" dependencies = [ "js-sys", "serde", @@ -1503,9 +1971,9 @@ dependencies = [ [[package]] name = "glow" -version = "0.12.3" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca0fe580e4b60a8ab24a868bc08e2f03cbcb20d3d676601fa909386713333728" +checksum = "bd348e04c43b32574f2de31c8bb397d96c9fcfa1371bd4ca6d8bdc464ab121b1" dependencies = [ "js-sys", "slotmap", @@ -1515,11 +1983,11 @@ dependencies = [ [[package]] name = "glutin" -version = "0.30.10" +version = "0.31.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc93b03242719b8ad39fb26ed2b01737144ce7bd4bfc7adadcef806596760fe" +checksum = "18fcd4ae4e86d991ad1300b8f57166e5be0c95ef1f63f3f5b827f8a164548746" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", "cfg_aliases", "cgl", "core-foundation", @@ -1527,42 +1995,43 @@ dependencies = [ "glutin_egl_sys", "glutin_glx_sys", "glutin_wgl_sys", - "libloading 0.7.4", - "objc2", + "icrate", + "libloading 0.8.3", + "objc2 0.4.1", "once_cell", - "raw-window-handle", - "wayland-sys 0.30.1", - "windows-sys 0.45.0", + "raw-window-handle 0.5.2", + "wayland-sys", + "windows-sys 0.48.0", "x11-dl", ] [[package]] name = "glutin-winit" -version = "0.3.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "629a873fc04062830bfe8f97c03773bcd7b371e23bcc465d0a61448cd1588fa4" +checksum = "1ebcdfba24f73b8412c5181e56f092b5eff16671c514ce896b258a0a64bd7735" dependencies = [ "cfg_aliases", "glutin", - "raw-window-handle", + "raw-window-handle 0.5.2", "winit", ] [[package]] name = "glutin_egl_sys" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af784eb26c5a68ec85391268e074f0aa618c096eadb5d6330b0911cf34fe57c5" +checksum = "77cc5623f5309ef433c3dd4ca1223195347fe62c413da8e2fdd0eb76db2d9bcd" dependencies = [ "gl_generator", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] name = "glutin_glx_sys" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b53cb5fe568964aa066a3ba91eac5ecbac869fb0842cd0dc9e412434f1a1494" +checksum = "a165fd686c10dcc2d45380b35796e577eacfd43d4660ee741ec8ebe2201b3b4f" dependencies = [ "gl_generator", "x11-dl", @@ -1570,18 +2039,70 @@ dependencies = [ [[package]] name = "glutin_wgl_sys" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef89398e90033fc6bc65e9bd42fd29bbbfd483bda5b56dc5562f455550618165" +checksum = "6c8098adac955faa2d31079b65dc48841251f69efd3ac25477903fc424362ead" dependencies = [ "gl_generator", ] +[[package]] +name = "gpu-alloc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" +dependencies = [ + "bitflags 2.5.0", + "gpu-alloc-types", +] + +[[package]] +name = "gpu-alloc-types" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" +dependencies = [ + "bitflags 2.5.0", +] + +[[package]] +name = "gpu-allocator" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f56f6318968d03c18e1bcf4857ff88c61157e9da8e47c5f29055d60e1228884" +dependencies = [ + "log", + "presser", + "thiserror", + "winapi", + "windows 0.52.0", +] + +[[package]] +name = "gpu-descriptor" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c" +dependencies = [ + "bitflags 2.5.0", + "gpu-descriptor-types", + "hashbrown", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bf0b36e6f090b7e1d8a4b49c0cb81c1f8376f72198c65dd3ad9ff3556b8b78c" +dependencies = [ + "bitflags 2.5.0", +] + [[package]] name = "h2" -version = "0.3.21" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ "bytes", "fnv", @@ -1589,7 +2110,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 1.9.3", + "indexmap", "slab", "tokio", "tokio-util", @@ -1598,15 +2119,28 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.12.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] [[package]] -name = "hashbrown" -version = "0.14.0" +name = "hassle-rs" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "af2a7e73e1f34c48da31fb668a907f250794837e08faa144fd24f0b8b741e890" +dependencies = [ + "bitflags 2.5.0", + "com", + "libc", + "libloading 0.8.3", + "thiserror", + "widestring", + "winapi", +] [[package]] name = "heck" @@ -1614,52 +2148,70 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "heed" -version = "0.12.4" -source = "git+https://github.com/meilisearch/heed?tag=v0.12.4#7a4542bc72dd60ef0f508c89900ea292218223fb" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a300b0deeb2957162d7752b0f063b3be1c88333af5bb4e7a57d8fb3716f50b" dependencies = [ + "bitflags 2.5.0", "byteorder", "heed-traits", "heed-types", "libc", - "lmdb-rkv-sys", + "lmdb-master-sys", "once_cell", "page_size", "serde", "synchronoise", "url", - "zerocopy", ] [[package]] name = "heed-traits" -version = "0.7.0" -source = "git+https://github.com/meilisearch/heed?tag=v0.12.4#7a4542bc72dd60ef0f508c89900ea292218223fb" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3130048d404c57ce5a1ac61a903696e8fcde7e8c2991e9fcfc1f27c3ef74ff" [[package]] name = "heed-types" -version = "0.7.2" -source = "git+https://github.com/meilisearch/heed?tag=v0.12.4#7a4542bc72dd60ef0f508c89900ea292218223fb" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cb0d6ba3700c9a57e83c013693e3eddb68a6d9b6781cacafc62a0d992e8ddb3" dependencies = [ "bincode", + "byteorder", "heed-traits", "serde", "serde_json", - "zerocopy", ] [[package]] name = "hermit-abi" -version = "0.3.2" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] + +[[package]] +name = "hex-conservative" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ed443af458ccb6d81c1e7e661545f94d3176752fb1df2f543b902a1e0f51e2" [[package]] name = "hex_lit" @@ -1667,6 +2219,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + [[package]] name = "hmac" version = "0.12.1" @@ -1678,18 +2236,18 @@ dependencies = [ [[package]] name = "home" -version = "0.5.5" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "http" -version = "0.2.9" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ "bytes", "fnv", @@ -1698,9 +2256,9 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", "http", @@ -1727,9 +2285,9 @@ checksum = "9994b79e8c1a39b3166c63ae7823bb2b00831e2a96a31399c50fe69df408eaeb" [[package]] name = "hyper" -version = "0.14.27" +version = "0.14.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" dependencies = [ "bytes", "futures-channel", @@ -1742,7 +2300,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.9", + "socket2 0.5.7", "tokio", "tower-service", "tracing", @@ -1751,9 +2309,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d78e1e73ec14cf7375674f74d7dde185c8206fd9dea6fb6295e8a98098aaa97" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", "http", @@ -1765,11 +2323,28 @@ dependencies = [ "tokio-rustls", ] +[[package]] +name = "icrate" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d3aaff8a54577104bafdf686ff18565c3b6903ca5782a2026ef06e2c7aa319" +dependencies = [ + "block2 0.3.0", + "dispatch", + "objc2 0.4.1", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -1777,36 +2352,25 @@ dependencies = [ [[package]] name = "image" -version = "0.24.7" +version = "0.24.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f3dfdbdd72063086ff443e297b61695500514b1e41095b6fb9a5ab48a70a711" +checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" dependencies = [ "bytemuck", "byteorder", "color_quant", - "num-rational", "num-traits", "png", ] [[package]] name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", -] - -[[package]] -name = "indexmap" -version = "2.0.0" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown 0.14.0", + "hashbrown", ] [[package]] @@ -1816,9 +2380,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", ] [[package]] @@ -1832,11 +2393,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jni" @@ -1862,62 +2429,51 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.26" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" dependencies = [ "libc", ] [[package]] name = "js-sys" -version = "0.3.64" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] [[package]] name = "jsonrpsee" -version = "0.19.0" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5f3783308bddc49d0218307f66a09330c106fbd792c58bac5c8dc294fdd0f98" +checksum = "affdc52f7596ccb2d7645231fc6163bb314630c989b64998f3699a28b4d5d4dc" dependencies = [ "jsonrpsee-client-transport", - "jsonrpsee-core 0.19.0", + "jsonrpsee-core", "jsonrpsee-http-client", - "jsonrpsee-proc-macros 0.19.0", - "jsonrpsee-types 0.19.0", + "jsonrpsee-proc-macros", + "jsonrpsee-server", + "jsonrpsee-types", "jsonrpsee-wasm-client", "jsonrpsee-ws-client", - "tracing", -] - -[[package]] -name = "jsonrpsee" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8002beb64691edce321fc16cdba91916b10d798f9d480a05467b0ee98463c03b" -dependencies = [ - "jsonrpsee-core 0.20.0", - "jsonrpsee-proc-macros 0.20.0", - "jsonrpsee-server", - "jsonrpsee-types 0.20.0", + "tokio", "tracing", ] [[package]] name = "jsonrpsee-client-transport" -version = "0.19.0" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abc5630e4fa0096f00ec7b44d520701fda4504170cb85e22dca603ae5d7ad0d7" +checksum = "b5b005c793122d03217da09af68ba9383363caa950b90d3436106df8cabce935" dependencies = [ "futures-channel", "futures-util", "gloo-net", "http", - "jsonrpsee-core 0.19.0", + "jsonrpsee-core", "pin-project", "rustls-native-certs", "soketto", @@ -1926,47 +2482,26 @@ dependencies = [ "tokio-rustls", "tokio-util", "tracing", + "url", "webpki-roots", ] [[package]] name = "jsonrpsee-core" -version = "0.19.0" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aaa4c4d5fb801dcc316d81f76422db259809037a86b3194ae538dd026b05ed7" +checksum = "da2327ba8df2fdbd5e897e2b5ed25ce7f299d345b9736b6828814c3dbd1fd47b" dependencies = [ "anyhow", - "async-lock", + "async-lock 2.8.0", "async-trait", "beef", "futures-timer", "futures-util", "hyper", - "jsonrpsee-types 0.19.0", - "rustc-hash", - "serde", - "serde_json", - "thiserror", - "tokio", - "tokio-stream", - "tracing", - "wasm-bindgen-futures", -] - -[[package]] -name = "jsonrpsee-core" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4278372ecb78ebb522c36a242209a29162f4af0997a41158c8b60450b081baf1" -dependencies = [ - "anyhow", - "async-trait", - "beef", - "futures-util", - "hyper", - "jsonrpsee-types 0.20.0", + "jsonrpsee-types", "parking_lot", - "rand 0.8.5", + "rand", "rustc-hash", "serde", "serde_json", @@ -1974,48 +2509,37 @@ dependencies = [ "thiserror", "tokio", "tracing", + "wasm-bindgen-futures", ] [[package]] name = "jsonrpsee-http-client" -version = "0.19.0" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa7165efcbfbc951d180162ff28fe91b657ed81925e37a35e4a396ce12109f96" +checksum = "5f80c17f62c7653ce767e3d7288b793dfec920f97067ceb189ebdd3570f2bc20" dependencies = [ "async-trait", "hyper", "hyper-rustls", - "jsonrpsee-core 0.19.0", - "jsonrpsee-types 0.19.0", + "jsonrpsee-core", + "jsonrpsee-types", "serde", "serde_json", "thiserror", "tokio", "tower", "tracing", + "url", ] [[package]] name = "jsonrpsee-proc-macros" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21dc12b1d4f16a86e8c522823c4fab219c88c03eb7c924ec0501a64bf12e058b" -dependencies = [ - "heck", - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "jsonrpsee-proc-macros" -version = "0.20.0" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "985d4a3753a08aaf120429924567795b2764c5c691489316a7fd076178e708b4" +checksum = "29110019693a4fa2dbda04876499d098fa16d70eba06b1e6e2b3f1b251419515" dependencies = [ - "heck", - "proc-macro-crate", + "heck 0.4.1", + "proc-macro-crate 1.3.1", "proc-macro2", "quote", "syn 1.0.109", @@ -2023,15 +2547,15 @@ dependencies = [ [[package]] name = "jsonrpsee-server" -version = "0.20.0" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc6357836b1d7b1367fe6d9a9b8d6e5488d1f1db985dfca4cb4ceaa9f37679e" +checksum = "82c39a00449c9ef3f50b84fc00fc4acba20ef8f559f07902244abf4c15c5ab9c" dependencies = [ "futures-util", "http", "hyper", - "jsonrpsee-core 0.20.0", - "jsonrpsee-types 0.20.0", + "jsonrpsee-core", + "jsonrpsee-types", "route-recognizer", "serde", "serde_json", @@ -2046,23 +2570,9 @@ dependencies = [ [[package]] name = "jsonrpsee-types" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00aa7cc87bc42e04e26c8ac3e7186142f7fd2949c763d9b6a7e64a69672d8fb2" -dependencies = [ - "anyhow", - "beef", - "serde", - "serde_json", - "thiserror", - "tracing", -] - -[[package]] -name = "jsonrpsee-types" -version = "0.20.0" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbea61f2d95b9592491228db0c4d2b1e43ea1154ed9713bb666169cf3919ea7d" +checksum = "5be0be325642e850ed0bdff426674d2e66b2b7117c9be23a7caef68a2902b7d9" dependencies = [ "anyhow", "beef", @@ -2074,36 +2584,48 @@ dependencies = [ [[package]] name = "jsonrpsee-wasm-client" -version = "0.19.0" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fe953c2801356f214d3f4051f786b3d11134512a46763ee8c39a9e3fa2cc1c0" +checksum = "7c7cbb3447cf14fd4d2f407c3cc96e6c9634d5440aa1fbed868a31f3c02b27f0" dependencies = [ "jsonrpsee-client-transport", - "jsonrpsee-core 0.19.0", - "jsonrpsee-types 0.19.0", + "jsonrpsee-core", + "jsonrpsee-types", ] [[package]] name = "jsonrpsee-ws-client" -version = "0.19.0" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c71b2597ec1c958c6d5bc94bb61b44d74eb28e69dc421731ab0035706f13882" +checksum = "bca9cb3933ccae417eb6b08c3448eb1cb46e39834e5b503e395e5e5bd08546c0" dependencies = [ "http", "jsonrpsee-client-transport", - "jsonrpsee-core 0.19.0", - "jsonrpsee-types 0.19.0", + "jsonrpsee-core", + "jsonrpsee-types", + "url", ] [[package]] name = "keccak" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" dependencies = [ "cpufeatures", ] +[[package]] +name = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "libloading 0.8.3", + "pkg-config", +] + [[package]] name = "khronos_api" version = "3.1.0" @@ -2118,9 +2640,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.147" +version = "0.2.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" [[package]] name = "libloading" @@ -2134,12 +2656,33 @@ dependencies = [ [[package]] name = "libloading" -version = "0.8.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d580318f95776505201b28cf98eb1fa5e4be3b689633ba6a3e6cd880ff22d8cb" +checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" dependencies = [ "cfg-if", - "windows-sys 0.48.0", + "windows-targets 0.52.5", +] + +[[package]] +name = "libredox" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607" +dependencies = [ + "bitflags 2.5.0", + "libc", + "redox_syscall 0.4.1", +] + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.5.0", + "libc", ] [[package]] @@ -2150,25 +2693,32 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" -version = "0.4.5" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] -name = "lmdb-rkv-sys" -version = "0.15.1" -source = "git+https://github.com/meilisearch/lmdb-rs#501aa34a1ab7f092e3ff54a6c22ff6c55931a2d8" +name = "litrs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" + +[[package]] +name = "lmdb-master-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc9048db3a58c0732d7236abc4909058f9d2708cfb6d7d047eb895fddec6419a" dependencies = [ "cc", + "doxygen-rs", "libc", - "pkg-config", ] [[package]] name = "lock_api" -version = "0.4.10" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -2176,9 +2726,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "malloc_buf" @@ -2190,27 +2740,27 @@ dependencies = [ ] [[package]] -name = "memchr" -version = "2.6.2" +name = "matchers" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5486aed0026218e61b8a01d5fbd5a0a134649abb71a0e53b7bc088529dced86e" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] [[package]] -name = "memmap2" -version = "0.5.10" +name = "memchr" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" -dependencies = [ - "libc", -] +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] -name = "memoffset" -version = "0.6.5" +name = "memmap2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" dependencies = [ - "autocfg", + "libc", ] [[package]] @@ -2224,36 +2774,45 @@ dependencies = [ [[package]] name = "memoffset" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] [[package]] name = "merlin" -version = "2.0.1" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e261cf0f8b3c42ded9f7d2bb59dea03aa52bc8a1cbc7482f9fc3fd1229d3b42" +checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" dependencies = [ "byteorder", "keccak", - "rand_core 0.5.1", + "rand_core", "zeroize", ] [[package]] -name = "minimal-lexical" -version = "0.2.1" +name = "metal" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +checksum = "c43f73953f8cbe511f021b58f18c3ce1c3d1ae13fe953293e13345bf83217f25" +dependencies = [ + "bitflags 2.5.0", + "block", + "core-graphics-types", + "foreign-types", + "log", + "objc", + "paste", +] [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", "simd-adler32", @@ -2261,27 +2820,48 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.8" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", - "log", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "windows-sys 0.48.0", ] [[package]] -name = "ndk" -version = "0.7.0" +name = "naga" +version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "451422b7e4718271c8b5b3aadf5adedba43dc76312454b387e98fae0fc951aa0" +checksum = "50e3524642f53d9af419ab5e8dd29d3ba155708267667c2f3f06c88c9e130843" dependencies = [ - "bitflags 1.3.2", + "bit-set", + "bitflags 2.5.0", + "codespan-reporting", + "hexf-parse", + "indexmap", + "log", + "num-traits", + "rustc-hash", + "spirv", + "termcolor", + "thiserror", + "unicode-xid", +] + +[[package]] +name = "ndk" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" +dependencies = [ + "bitflags 2.5.0", "jni-sys", + "log", "ndk-sys", - "num_enum 0.5.11", - "raw-window-handle", + "num_enum", + "raw-window-handle 0.5.2", + "raw-window-handle 0.6.1", "thiserror", ] @@ -2293,48 +2873,35 @@ checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" [[package]] name = "ndk-sys" -version = "0.4.1+23.1.7779620" +version = "0.5.0+25.2.9519653" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf2aae958bd232cac5069850591667ad422d263686d75b52a065f9badeee5a3" +checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" dependencies = [ "jni-sys", ] [[package]] name = "nix" -version = "0.24.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" -dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "libc", - "memoffset 0.6.5", -] - -[[package]] -name = "nix" -version = "0.25.1" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" dependencies = [ - "autocfg", "bitflags 1.3.2", "cfg-if", "libc", - "memoffset 0.6.5", + "memoffset 0.7.1", ] [[package]] name = "nix" -version = "0.26.4" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", "cfg-if", + "cfg_aliases", "libc", - "memoffset 0.7.1", ] [[package]] @@ -2344,41 +2911,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" [[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "num-integer" -version = "0.1.45" +name = "nu-ansi-term" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" dependencies = [ - "autocfg", - "num-traits", + "overload", + "winapi", ] [[package]] -name = "num-rational" -version = "0.4.1" +name = "num-conv" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-traits" -version = "0.2.16" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] @@ -2395,81 +2947,99 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.5.11" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" dependencies = [ - "num_enum_derive 0.5.11", + "num_enum_derive", ] [[package]] -name = "num_enum" -version = "0.6.1" +name = "num_enum_derive" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a015b430d3c108a207fd776d2e2196aaf8b1cf8cf93253e3a097ff3085076a1" +checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" dependencies = [ - "num_enum_derive 0.6.1", + "proc-macro-crate 3.1.0", + "proc-macro2", + "quote", + "syn 2.0.61", ] [[package]] -name = "num_enum_derive" -version = "0.5.11" +name = "objc" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 1.0.109", + "malloc_buf", + "objc_exception", ] [[package]] -name = "num_enum_derive" -version = "0.6.1" +name = "objc-sys" +version = "0.2.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b9834c1e95694a05a828b59f55fa2afec6288359cda67146126b3f90a55d7" + +[[package]] +name = "objc-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da284c198fb9b7b0603f8635185e85fbd5b64ee154b1ed406d489077de2d6d60" + +[[package]] +name = "objc2" +version = "0.3.0-beta.3.patch-leaks.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6" +checksum = "7e01640f9f2cb1220bbe80325e179e532cb3379ebcd1bf2279d703c19fe3a468" dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.29", + "block2 0.2.0-alpha.6", + "objc-sys 0.2.0-beta.2", + "objc2-encode 2.0.0-pre.2", ] [[package]] -name = "objc" -version = "0.2.7" +name = "objc2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +checksum = "559c5a40fdd30eb5e344fbceacf7595a81e242529fb4e21cf5f43fb4f11ff98d" dependencies = [ - "malloc_buf", + "objc-sys 0.3.3", + "objc2-encode 3.0.0", ] [[package]] -name = "objc-foundation" -version = "0.1.1" +name = "objc2" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +checksum = "b4b25e1034d0e636cd84707ccdaa9f81243d399196b8a773946dcffec0401659" dependencies = [ - "block", - "objc", - "objc_id", + "objc-sys 0.3.3", + "objc2-encode 4.0.1", ] [[package]] -name = "objc-sys" -version = "0.2.0-beta.2" +name = "objc2-app-kit" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b9834c1e95694a05a828b59f55fa2afec6288359cda67146126b3f90a55d7" +checksum = "fb79768a710a9a1798848179edb186d1af7e8a8679f369e4b8d201dd2a034047" +dependencies = [ + "block2 0.5.0", + "objc2 0.5.1", + "objc2-core-data", + "objc2-foundation", +] [[package]] -name = "objc2" -version = "0.3.0-beta.3.patch-leaks.3" +name = "objc2-core-data" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e01640f9f2cb1220bbe80325e179e532cb3379ebcd1bf2279d703c19fe3a468" +checksum = "6e092bc42eaf30a08844e6a076938c60751225ec81431ab89f5d1ccd9f958d6c" dependencies = [ - "block2", - "objc-sys", - "objc2-encode", + "block2 0.5.0", + "objc2 0.5.1", + "objc2-foundation", ] [[package]] @@ -2478,38 +3048,60 @@ version = "2.0.0-pre.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abfcac41015b00a120608fdaa6938c44cb983fee294351cc4bac7638b4e50512" dependencies = [ - "objc-sys", + "objc-sys 0.2.0-beta.2", ] [[package]] -name = "objc_id" -version = "0.1.1" +name = "objc2-encode" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d079845b37af429bfe5dfa76e6d087d788031045b25cfc6fd898486fd9847666" + +[[package]] +name = "objc2-encode" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88658da63e4cc2c8adb1262902cd6af51094df0488b760d6fd27194269c0950a" + +[[package]] +name = "objc2-foundation" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +checksum = "cfaefe14254871ea16c7d88968c0ff14ba554712a20d76421eec52f0a7fb8904" dependencies = [ - "objc", + "block2 0.5.0", + "objc2 0.5.1", +] + +[[package]] +name = "objc_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +dependencies = [ + "cc", ] [[package]] name = "object" -version = "0.32.0" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ac5bbd07aea88c60a577a1ce218075ffd59208b2d7ca97adf9bfc5aeb21ebe" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "opaque-debug" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl-probe" @@ -2525,11 +3117,11 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "orbclient" -version = "0.3.46" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8378ac0dfbd4e7895f2d2c1f1345cab3836910baf3a300b000d04250f0c8428f" +checksum = "52f0d54bde9774d3a51dcf281a5def240c71996bc6ca05d2c847ec8b2b216166" dependencies = [ - "redox_syscall 0.3.5", + "libredox 0.0.2", ] [[package]] @@ -2542,20 +3134,26 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "owned_ttf_parser" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "706de7e2214113d63a8238d1910463cfce781129a6f263d13fdb09ff64355ba4" +checksum = "d4586edfe4c648c71797a74c84bacb32b52b212eff5dfe2bb9f2c599844023e7" dependencies = [ "ttf-parser", ] [[package]] name = "page_size" -version = "0.4.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eebde548fbbf1ea81a99b128872779c437752fb99f217c45245e1a61dcd9edcd" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" dependencies = [ "libc", "winapi", @@ -2563,15 +3161,15 @@ dependencies = [ [[package]] name = "parking" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" dependencies = [ "lock_api", "parking_lot_core", @@ -2579,22 +3177,22 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.8" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.3.5", + "redox_syscall 0.5.1", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.5", ] [[package]] name = "paste" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pbkdf2" @@ -2607,45 +3205,87 @@ dependencies = [ [[package]] name = "pem" -version = "2.0.1" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b13fe415cdf3c8e44518e18a7c95a13431d9bdf6d15367d82b23c377fdd441a" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" dependencies = [ - "base64 0.21.3", + "base64 0.22.1", "serde", ] [[package]] name = "percent-encoding" -version = "2.3.0" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.61", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] [[package]] name = "pin-project" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.61", ] [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -2653,17 +3293,44 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piper" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" +dependencies = [ + "atomic-waker", + "fastrand 2.1.0", + "futures-io", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" -version = "0.3.27" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "platforms" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7" [[package]] name = "png" -version = "0.17.10" +version = "0.17.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd75bf2d8dd3702b9707cdbc56a5b9ef42cec752eb8b3bafc01234558442aa64" +checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" dependencies = [ "bitflags 1.3.2", "crc32fast", @@ -2688,12 +3355,39 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "polling" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645493cf344456ef24219d02a768cf1fb92ddf8c92161679ae3d91b91a637be3" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix 0.38.34", + "tracing", + "windows-sys 0.52.0", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "presser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + [[package]] name = "proc-macro-crate" version = "1.3.1" @@ -2701,18 +3395,65 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ "once_cell", - "toml_edit", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +dependencies = [ + "toml_edit 0.21.1", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", ] [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" dependencies = [ "unicode-ident", ] +[[package]] +name = "profiling" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58" + +[[package]] +name = "quick-xml" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" +dependencies = [ + "memchr", +] + [[package]] name = "quinn" version = "0.10.2" @@ -2732,13 +3473,13 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.10.4" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13f81c9a9d574310b8351f8666f5a93ac3b0069c45c28ad52c10291389a7cf9" +checksum = "141bf7dfde2fbc246bfd3fe12f2455aa24b0fbd9af535d8c86c7bd1381ff2b1a" dependencies = [ "bytes", - "rand 0.8.5", - "ring", + "rand", + "ring 0.16.20", "rustc-hash", "rustls", "rustls-native-certs", @@ -2756,33 +3497,20 @@ checksum = "055b4e778e8feb9f93c4e439f71dc2156ef13360b432b799e179a8c4cdf0b1d7" dependencies = [ "bytes", "libc", - "socket2 0.5.3", + "socket2 0.5.7", "tracing", "windows-sys 0.48.0", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", -] - [[package]] name = "rand" version = "0.8.5" @@ -2790,18 +3518,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", + "rand_chacha", + "rand_core", ] [[package]] @@ -2811,16 +3529,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", + "rand_core", ] [[package]] @@ -2829,29 +3538,26 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.10", + "getrandom", ] [[package]] -name = "rand_hc" -version = "0.2.0" +name = "raw-window-handle" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", -] +checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" [[package]] name = "raw-window-handle" -version = "0.5.2" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" +checksum = "8cc3bcbdb1ddfc11e700e62968e6b4cc9c75bb466464ad28fb61c5b2c964418b" [[package]] name = "rayon" -version = "1.7.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", @@ -2859,85 +3565,113 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.11.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ - "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", - "num_cpus", ] [[package]] name = "rcgen" -version = "0.11.1" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4954fbc00dcd4d8282c987710e50ba513d351400dbdd00e803a05172a90d8976" +checksum = "52c4f3084aa3bc7dfbba4eff4fab2a54db4324965d8872ab933565e6fbd83bc6" dependencies = [ "pem", - "ring", + "ring 0.16.20", "time", "yasna", ] [[package]] name = "redox_syscall" -version = "0.2.16" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "redox_syscall" -version = "0.3.5" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_syscall" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +dependencies = [ + "bitflags 2.5.0", +] + [[package]] name = "redox_users" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" dependencies = [ - "getrandom 0.2.10", - "redox_syscall 0.2.16", + "getrandom", + "libredox 0.1.3", "thiserror", ] [[package]] name = "regex" -version = "1.9.4" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.6", + "regex-syntax 0.8.3", ] [[package]] name = "regex-automata" -version = "0.3.7" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.8.3", ] [[package]] name = "regex-syntax" -version = "0.7.5" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" + +[[package]] +name = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" [[package]] name = "ring" @@ -2948,12 +3682,27 @@ dependencies = [ "cc", "libc", "once_cell", - "spin", - "untrusted", + "spin 0.5.2", + "untrusted 0.7.1", "web-sys", "winapi", ] +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin 0.9.8", + "untrusted 0.9.0", + "windows-sys 0.52.0", +] + [[package]] name = "route-recognizer" version = "0.3.1" @@ -2962,9 +3711,9 @@ checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746" [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" @@ -2972,11 +3721,20 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + [[package]] name = "rustix" -version = "0.37.23" +version = "0.37.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" +checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" dependencies = [ "bitflags 1.3.2", "errno", @@ -2988,25 +3746,25 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.10" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed6248e1caa625eb708e266e06159f135e8c26f2bb7ceb72dc4b2766d0340964" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.5.0", "errno", "libc", - "linux-raw-sys 0.4.5", - "windows-sys 0.48.0", + "linux-raw-sys 0.4.13", + "windows-sys 0.52.0", ] [[package]] name = "rustls" -version = "0.21.7" +version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", - "ring", + "ring 0.17.8", "rustls-webpki", "sct", ] @@ -3025,28 +3783,43 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64 0.21.3", + "base64 0.21.7", ] [[package]] name = "rustls-webpki" -version = "0.101.4" +version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d93931baf2d282fff8d3a532bbfd7653f734643161b87e3e01e59a04439bf0d" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring 0.17.8", + "untrusted 0.9.0", +] + +[[package]] +name = "rustreexo" +version = "0.2.0" +source = "git+https://github.com/Ash-L2L/rustreexo.git?rev=a3ac7d3ebe9749ebd0bb34c709e7616f83d573b3#a3ac7d3ebe9749ebd0bb34c709e7616f83d573b3" dependencies = [ - "ring", - "untrusted", + "bitcoin_hashes 0.12.0", + "serde", ] +[[package]] +name = "rustversion" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "092474d1a01ea8278f69e6a358998405fae5b8b963ddaeb2b0b04a128bf1dfb0" + [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "same-file" @@ -3059,11 +3832,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -3080,19 +3853,19 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sct" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring", - "untrusted", + "ring 0.17.8", + "untrusted 0.9.0", ] [[package]] name = "sctk-adwaita" -version = "0.5.4" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda4e97be1fd174ccc2aae81c8b694e803fa99b34e8fd0f057a9d70698e3ed09" +checksum = "82b2eaf3a5b264a521b988b2e73042e742df700c4f962cde845d1541adb46550" dependencies = [ "ab_glyph", "log", @@ -3103,31 +3876,31 @@ dependencies = [ [[package]] name = "secp256k1" -version = "0.27.0" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25996b82292a7a57ed3508f052cfff8640d38d32018784acd714758b43da9c8f" +checksum = "d24b59d129cdadea20aea4fb2352fa053712e5d713eee47d700cd4b2bc002f10" dependencies = [ - "bitcoin_hashes", + "bitcoin_hashes 0.13.0", "secp256k1-sys", "serde", ] [[package]] name = "secp256k1-sys" -version = "0.8.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a129b9e9efbfb223753b9163c4ab3b13cff7fd9c7f010fbac25ab4099fa07e" +checksum = "e5d1746aae42c19d583c3c1a8c646bfad910498e2051c551a7f2e3c0c9fbb7eb" dependencies = [ "cc", ] [[package]] name = "security-framework" -version = "2.9.2" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", "core-foundation", "core-foundation-sys", "libc", @@ -3136,14 +3909,20 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" dependencies = [ "core-foundation-sys", "libc", ] +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + [[package]] name = "send_wrapper" version = "0.4.0" @@ -3152,53 +3931,67 @@ checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" [[package]] name = "serde" -version = "1.0.188" +version = "1.0.201" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c" dependencies = [ "serde_derive", ] [[package]] -name = "serde_bytes" -version = "0.11.12" +name = "serde_derive" +version = "1.0.201" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.61", +] + +[[package]] +name = "serde_json" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" dependencies = [ + "itoa", + "ryu", "serde", ] [[package]] -name = "serde_derive" -version = "1.0.188" +name = "serde_repr" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.61", ] [[package]] -name = "serde_json" -version = "1.0.105" +name = "serde_with" +version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" +checksum = "0ad483d2ab0149d5a5ebcd9972a3852711e0153d863bf5a5d0391d28883c4a20" dependencies = [ - "itoa", - "ryu", "serde", + "serde_derive", + "serde_with_macros", ] [[package]] -name = "serde_repr" -version = "0.1.16" +name = "serde_with_macros" +version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" +checksum = "65569b702f41443e8bc8bbb1c5779bd0450bbe723b56198980e80ec45780bce2" dependencies = [ + "darling", "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.61", ] [[package]] @@ -3216,9 +4009,9 @@ dependencies = [ [[package]] name = "sha1" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", @@ -3227,22 +4020,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" -dependencies = [ - "block-buffer 0.9.0", - "cfg-if", - "cpufeatures", - "digest 0.9.0", - "opaque-debug", -] - -[[package]] -name = "sha2" -version = "0.10.7" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", @@ -3251,41 +4031,49 @@ dependencies = [ [[package]] name = "sha256" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7895c8ae88588ccead14ff438b939b0c569cd619116f14b4d13fdff7b8333386" +checksum = "18278f6a914fa3070aa316493f7d2ddfb9ac86ebc06fa3b83bffda487e9065b0" dependencies = [ "async-trait", "bytes", "hex", - "sha2 0.10.7", + "sha2", "tokio", ] [[package]] -name = "signal-hook" -version = "0.3.17" +name = "sharded-slab" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ - "libc", - "signal-hook-registry", + "lazy_static", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] [[package]] name = "signature" -version = "1.6.4" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core", +] [[package]] name = "simd-adler32" @@ -3293,6 +4081,12 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "slab" version = "0.4.9" @@ -3304,53 +4098,69 @@ dependencies = [ [[package]] name = "slotmap" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" dependencies = [ "version_check", ] [[package]] name = "smallvec" -version = "1.11.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smithay-client-toolkit" -version = "0.16.0" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f307c47d32d2715eb2e0ece5589057820e0e5e70d07c247d1063e844e107f454" +checksum = "922fd3eeab3bd820d76537ce8f582b1cf951eceb5475c28500c7457d9d17f53a" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", "calloop", - "dlib", - "lazy_static", + "calloop-wayland-source", + "cursor-icon", + "libc", "log", "memmap2", - "nix 0.24.3", - "pkg-config", + "rustix 0.38.34", + "thiserror", + "wayland-backend", "wayland-client", + "wayland-csd-frame", "wayland-cursor", "wayland-protocols", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", ] [[package]] name = "smithay-clipboard" -version = "0.6.6" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a345c870a1fae0b1b779085e81b51e614767c239e93503588e54c5b17f4b0e8" +checksum = "c091e7354ea8059d6ad99eace06dd13ddeedbb0ac72d40a9a6e7ff790525882d" dependencies = [ + "libc", "smithay-client-toolkit", - "wayland-client", + "wayland-backend", +] + +[[package]] +name = "smol_str" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6845563ada680337a52d43bb0b29f396f2d911616f6573012645b9e3d048a49" +dependencies = [ + "serde", ] [[package]] name = "socket2" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" dependencies = [ "libc", "winapi", @@ -3358,12 +4168,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.3" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -3378,7 +4188,7 @@ dependencies = [ "http", "httparse", "log", - "rand 0.8.5", + "rand", "sha-1", ] @@ -3389,16 +4199,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] -name = "static_assertions" -version = "1.1.0" +name = "spin" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] -name = "str-buf" -version = "1.0.6" +name = "spirv" +version = "0.3.0+sdk-1.3.268.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" +dependencies = [ + "bitflags 2.5.0", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "strict-num" @@ -3412,6 +4241,34 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.61", +] + [[package]] name = "subtle" version = "2.5.0" @@ -3431,15 +4288,27 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.29" +version = "2.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" +checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "syn_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.61", +] + [[package]] name = "synchronoise" version = "1.0.1" @@ -3450,108 +4319,187 @@ dependencies = [ ] [[package]] -name = "synstructure" -version = "0.12.6" +name = "tempfile" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", - "unicode-xid", + "cfg-if", + "fastrand 2.1.0", + "rustix 0.38.34", + "windows-sys 0.52.0", ] [[package]] -name = "tempfile" -version = "3.8.0" +name = "termcolor" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ - "cfg-if", - "fastrand 2.0.0", - "redox_syscall 0.3.5", - "rustix 0.38.10", - "windows-sys 0.48.0", + "winapi-util", +] + +[[package]] +name = "test-log" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dffced63c2b5c7be278154d76b479f9f9920ed34e7574201407f0b14e2bbb93" +dependencies = [ + "env_logger", + "test-log-macros", + "tracing-subscriber", +] + +[[package]] +name = "test-log-macros" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5999e24eaa32083191ba4e425deb75cdf25efefabe5aaccb7446dd0d4122a3f5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.61", ] [[package]] name = "thiserror" -version = "1.0.47" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" +checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.47" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" +checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.61", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", ] [[package]] name = "thunder" -version = "0.2.0" +version = "0.8.6" dependencies = [ + "anyhow", "bincode", "bip300301", "blake3", + "borsh", "bs58", "byteorder", "bytes", "ed25519-dalek", "ed25519-dalek-bip32", + "fallible-iterator", + "futures", "heed", "hex", + "jsonrpsee", + "parking_lot", "quinn", "rayon", "rcgen", "rustls", + "rustreexo", "serde", + "serde_json", "sha256", "thiserror", + "tiny-bip39", "tokio", + "tokio-stream", + "tokio-util", + "tracing", ] [[package]] name = "thunder_app" -version = "0.2.0" +version = "0.8.6" dependencies = [ "anyhow", + "base64 0.21.7", "bincode", + "bip300301", "clap", + "ctrlc", "dirs", "eframe", + "futures", "human-size", - "jsonrpsee 0.20.0", + "jsonrpsee", + "parking_lot", + "rustreexo", "serde", + "shlex", + "strum", + "tempfile", + "test-log", "thiserror", "thunder", + "thunder_app_cli", + "thunder_app_rpc_api", "tiny-bip39", "tokio", + "tokio-util", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "thunder_app_cli" +version = "0.8.6" +dependencies = [ + "anyhow", + "bip300301", + "clap", + "jsonrpsee", + "thunder", + "thunder_app_rpc_api", + "tokio", +] + +[[package]] +name = "thunder_app_rpc_api" +version = "0.8.6" +dependencies = [ + "bip300301", + "jsonrpsee", + "thunder", ] [[package]] name = "time" -version = "0.3.28" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", + "num-conv", + "powerfmt", "serde", "time-core", ] [[package]] name = "time-core" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "tiny-bip39" @@ -3563,9 +4511,9 @@ dependencies = [ "hmac", "once_cell", "pbkdf2", - "rand 0.8.5", + "rand", "rustc-hash", - "sha2 0.10.7", + "sha2", "thiserror", "unicode-normalization", "wasm-bindgen", @@ -3574,23 +4522,23 @@ dependencies = [ [[package]] name = "tiny-skia" -version = "0.8.4" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8493a203431061e901613751931f047d1971337153f96d0e5e363d6dbf6a67" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" dependencies = [ "arrayref", "arrayvec", "bytemuck", "cfg-if", - "png", + "log", "tiny-skia-path", ] [[package]] name = "tiny-skia-path" -version = "0.8.4" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adbfb5d3f3dd57a0e11d12f4f13d4ebbbc1b5c15b7ab0a156d030b21da5f677c" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" dependencies = [ "arrayref", "bytemuck", @@ -3614,9 +4562,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.32.0" +version = "1.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" dependencies = [ "backtrace", "bytes", @@ -3624,20 +4572,21 @@ dependencies = [ "mio", "num_cpus", "pin-project-lite", - "socket2 0.5.3", + "signal-hook-registry", + "socket2 0.5.7", "tokio-macros", "windows-sys 0.48.0", ] [[package]] name = "tokio-macros" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.61", ] [[package]] @@ -3652,9 +4601,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" dependencies = [ "futures-core", "pin-project-lite", @@ -3663,32 +4612,44 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.8" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ "bytes", "futures-core", "futures-io", "futures-sink", + "futures-util", + "hashbrown", "pin-project-lite", "tokio", - "tracing", ] [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] [[package]] name = "toml_edit" -version = "0.19.14" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ - "indexmap 2.0.0", + "indexmap", "toml_datetime", "winnow", ] @@ -3722,11 +4683,10 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "cfg-if", "log", "pin-project-lite", "tracing-attributes", @@ -3735,73 +4695,125 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.61", ] [[package]] name = "tracing-core" -version = "0.1.31" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ + "matchers", + "nu-ansi-term", "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", ] [[package]] name = "try-lock" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "ttf-parser" -version = "0.19.1" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a464a4b34948a5f67fddd2b823c62d9d92e44be75058b99939eae6c5b6960b33" +checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4" + +[[package]] +name = "type-map" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb68604048ff8fa93347f02441e4487594adc20bb8a084f9e564d2b827a0a9f" +dependencies = [ + "rustc-hash", +] [[package]] name = "typenum" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "uds_windows" -version = "1.0.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce65604324d3cce9b966701489fbd0cf318cb1f7bd9dd07ac9a4ee6fb791930d" +checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" dependencies = [ + "memoffset 0.9.1", "tempfile", "winapi", ] [[package]] name = "unicode-bidi" -version = "0.3.13" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "unicode-width" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" + [[package]] name = "unicode-xid" version = "0.2.4" @@ -3814,11 +4826,17 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", "idna", @@ -3832,10 +4850,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] -name = "vec_map" -version = "0.8.2" +name = "valuable" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "version_check" @@ -3845,15 +4863,15 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "waker-fn" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" +checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" [[package]] name = "walkdir" -version = "2.3.3" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", @@ -3868,12 +4886,6 @@ dependencies = [ "try-lock", ] -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -3882,9 +4894,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.87" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -3892,24 +4904,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.87" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.61", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.37" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" dependencies = [ "cfg-if", "js-sys", @@ -3919,9 +4931,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.87" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3929,113 +4941,147 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.87" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.61", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.87" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] -name = "wayland-client" -version = "0.29.5" +name = "wayland-backend" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715" +checksum = "9d50fa61ce90d76474c87f5fc002828d81b32677340112b4ef08079a9d459a40" dependencies = [ - "bitflags 1.3.2", + "cc", "downcast-rs", - "libc", - "nix 0.24.3", + "rustix 0.38.34", "scoped-tls", - "wayland-commons", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82fb96ee935c2cea6668ccb470fb7771f6215d1691746c2d896b447a00ad3f1f" +dependencies = [ + "bitflags 2.5.0", + "rustix 0.38.34", + "wayland-backend", "wayland-scanner", - "wayland-sys 0.29.5", ] [[package]] -name = "wayland-commons" -version = "0.29.5" +name = "wayland-csd-frame" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902" +checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" dependencies = [ - "nix 0.24.3", - "once_cell", - "smallvec", - "wayland-sys 0.29.5", + "bitflags 2.5.0", + "cursor-icon", + "wayland-backend", ] [[package]] name = "wayland-cursor" -version = "0.29.5" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661" +checksum = "71ce5fa868dd13d11a0d04c5e2e65726d0897be8de247c0c5a65886e283231ba" dependencies = [ - "nix 0.24.3", + "rustix 0.38.34", "wayland-client", "xcursor", ] [[package]] name = "wayland-protocols" -version = "0.29.5" +version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6" +checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-plasma" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23803551115ff9ea9bce586860c5c5a971e360825a0309264102a9495a5ff479" +dependencies = [ + "bitflags 2.5.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" +dependencies = [ + "bitflags 2.5.0", + "wayland-backend", "wayland-client", - "wayland-commons", + "wayland-protocols", "wayland-scanner", ] [[package]] name = "wayland-scanner" -version = "0.29.5" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4303d8fa22ab852f789e75a967f0a2cdc430a607751c0499bada3e451cbd53" +checksum = "63b3a62929287001986fb58c789dce9b67604a397c15c611ad9f747300b6c283" dependencies = [ "proc-macro2", + "quick-xml", "quote", - "xml-rs", ] [[package]] name = "wayland-sys" -version = "0.29.5" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be12ce1a3c39ec7dba25594b97b42cb3195d54953ddb9d3d95a7c3902bc6e9d4" +checksum = "15a0c8eaff5216d07f226cb7a549159267f3467b289d9a2e52fd3ef5aae2b7af" dependencies = [ "dlib", - "lazy_static", + "log", + "once_cell", "pkg-config", ] [[package]] -name = "wayland-sys" -version = "0.30.1" +name = "web-sys" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b2a02ac608e07132978689a6f9bf4214949c85998c247abadd4f4129b1aa06" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ - "dlib", - "lazy_static", - "log", - "pkg-config", + "js-sys", + "wasm-bindgen", ] [[package]] -name = "web-sys" -version = "0.3.64" +name = "web-time" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +checksum = "aa30049b1c872b72c89866d458eae9f20380ab280ffd1b1e18df2d3e2d98cfe0" dependencies = [ "js-sys", "wasm-bindgen", @@ -4043,9 +5089,9 @@ dependencies = [ [[package]] name = "webbrowser" -version = "0.8.11" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2c79b77f525a2d670cb40619d7d9c673d09e0666f72c591ebd7861f84a87e57" +checksum = "db67ae75a9405634f5882791678772c94ff5f16a66535aae186e26aa0841fc8b" dependencies = [ "core-foundation", "home", @@ -4053,20 +5099,125 @@ dependencies = [ "log", "ndk-context", "objc", - "raw-window-handle", + "raw-window-handle 0.5.2", "url", "web-sys", ] [[package]] name = "webpki-roots" -version = "0.24.0" +version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b291546d5d9d1eab74f069c77749f2cb8504a12caa20f0f2de93ddbf6f411888" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + +[[package]] +name = "wgpu" +version = "0.19.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbd7311dbd2abcfebaabf1841a2824ed7c8be443a0f29166e5d3c6a53a762c01" dependencies = [ - "rustls-webpki", + "arrayvec", + "cfg-if", + "cfg_aliases", + "js-sys", + "log", + "parking_lot", + "profiling", + "raw-window-handle 0.6.1", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "0.19.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28b94525fc99ba9e5c9a9e24764f2bc29bad0911a7446c12f446a8277369bf3a" +dependencies = [ + "arrayvec", + "bit-vec", + "bitflags 2.5.0", + "cfg_aliases", + "codespan-reporting", + "indexmap", + "log", + "naga", + "once_cell", + "parking_lot", + "profiling", + "raw-window-handle 0.6.1", + "rustc-hash", + "smallvec", + "thiserror", + "web-sys", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-hal" +version = "0.19.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1a4924366df7ab41a5d8546d6534f1f33231aa5b3f72b9930e300f254e39c3" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bitflags 2.5.0", + "cfg_aliases", + "core-graphics-types", + "glow", + "glutin_wgl_sys", + "gpu-alloc", + "gpu-allocator", + "gpu-descriptor", + "hassle-rs", + "js-sys", + "khronos-egl", + "libc", + "libloading 0.8.3", + "log", + "metal", + "naga", + "ndk-sys", + "objc", + "once_cell", + "parking_lot", + "profiling", + "raw-window-handle 0.6.1", + "renderdoc-sys", + "rustc-hash", + "smallvec", + "thiserror", + "wasm-bindgen", + "web-sys", + "wgpu-types", + "winapi", ] +[[package]] +name = "wgpu-types" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b671ff9fb03f78b46ff176494ee1ebe7d603393f42664be55b64dc8d53969805" +dependencies = [ + "bitflags 2.5.0", + "js-sys", + "web-sys", +] + +[[package]] +name = "widestring" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" + [[package]] name = "winapi" version = "0.3.9" @@ -4085,20 +5236,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - -[[package]] -name = "winapi-wsapoll" -version = "0.1.1" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c17110f57155602a80dca10be03852116403c9ff3cd25b079d666f2aa3df6e" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" dependencies = [ - "winapi", + "windows-sys 0.52.0", ] [[package]] @@ -4118,6 +5260,25 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core", + "windows-targets 0.52.5", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.5", +] + [[package]] name = "windows-implement" version = "0.48.0" @@ -4158,6 +5319,15 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.5", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -4188,6 +5358,22 @@ dependencies = [ "windows_x86_64_msvc 0.48.5", ] +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -4200,6 +5386,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -4212,6 +5404,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -4224,6 +5422,18 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -4236,6 +5446,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -4248,6 +5464,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -4260,6 +5482,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -4272,46 +5500,66 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + [[package]] name = "winit" -version = "0.28.6" +version = "0.29.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "866db3f712fffba75d31bf0cdecf357c8aeafd158c5b7ab51dba2a2b2d47f196" +checksum = "0d59ad965a635657faf09c8f062badd885748428933dad8e8bdd64064d92e5ca" dependencies = [ + "ahash", "android-activity", - "bitflags 1.3.2", + "atomic-waker", + "bitflags 2.5.0", + "bytemuck", + "calloop", "cfg_aliases", "core-foundation", "core-graphics", - "dispatch", - "instant", + "cursor-icon", + "icrate", + "js-sys", "libc", "log", - "mio", + "memmap2", "ndk", - "objc2", + "ndk-sys", + "objc2 0.4.1", "once_cell", "orbclient", "percent-encoding", - "raw-window-handle", + "raw-window-handle 0.5.2", + "raw-window-handle 0.6.1", "redox_syscall 0.3.5", + "rustix 0.38.34", "sctk-adwaita", "smithay-client-toolkit", + "smol_str", + "unicode-segmentation", "wasm-bindgen", + "wasm-bindgen-futures", + "wayland-backend", "wayland-client", - "wayland-commons", "wayland-protocols", - "wayland-scanner", + "wayland-protocols-plasma", "web-sys", - "windows-sys 0.45.0", + "web-time", + "windows-sys 0.48.0", "x11-dl", + "x11rb", + "xkbcommon-dl", ] [[package]] name = "winnow" -version = "0.5.15" +version = "0.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" dependencies = [ "memchr", ] @@ -4329,50 +5577,65 @@ dependencies = [ [[package]] name = "x11rb" -version = "0.10.1" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "592b4883219f345e712b3209c62654ebda0bb50887f330cbd018d0f654bfd507" +checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" dependencies = [ + "as-raw-xcb-connection", "gethostname", - "nix 0.24.3", - "winapi", - "winapi-wsapoll", + "libc", + "libloading 0.8.3", + "once_cell", + "rustix 0.38.34", "x11rb-protocol", ] [[package]] name = "x11rb-protocol" -version = "0.10.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56b245751c0ac9db0e006dc812031482784e434630205a93c73cfefcaabeac67" -dependencies = [ - "nix 0.24.3", -] +checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" [[package]] name = "xcursor" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "463705a63313cd4301184381c5e8042f0a7e9b4bb63653f216311d4ae74690b7" -dependencies = [ - "nom", -] +checksum = "6a0ccd7b4a5345edfcd0c3535718a4e9ff7798ffc536bb5b5a0e26ff84732911" [[package]] name = "xdg-home" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2769203cd13a0c6015d515be729c526d041e9cf2c0cc478d57faee85f40c6dcd" +checksum = "21e5a325c3cb8398ad6cf859c1135b25dd29e186679cf2da7581d9679f63b38e" dependencies = [ - "nix 0.26.4", + "libc", "winapi", ] +[[package]] +name = "xkbcommon-dl" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" +dependencies = [ + "bitflags 2.5.0", + "dlib", + "log", + "once_cell", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "054a8e68b76250b253f671d1268cb7f1ae089ec35e195b2efb2a4e9a836d0621" + [[package]] name = "xml-rs" -version = "0.8.16" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47430998a7b5d499ccee752b41567bc3afc57e1327dc855b1a2aa44ce29b5fa1" +checksum = "791978798f0597cfc70478424c2b4fdc2b7a8024aaff78497ef00f24ef674193" [[package]] name = "yasna" @@ -4385,15 +5648,15 @@ dependencies = [ [[package]] name = "zbus" -version = "3.14.1" +version = "3.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31de390a2d872e4cd04edd71b425e29853f786dc99317ed72d73d6fcf5ebb948" +checksum = "675d170b632a6ad49804c8cf2105d7c31eddd3312555cffd4b740e08e97c25e6" dependencies = [ "async-broadcast", "async-executor", "async-fs", - "async-io", - "async-lock", + "async-io 1.13.0", + "async-lock 2.8.0", "async-process", "async-recursion", "async-task", @@ -4402,7 +5665,7 @@ dependencies = [ "byteorder", "derivative", "enumflags2", - "event-listener", + "event-listener 2.5.3", "futures-core", "futures-sink", "futures-util", @@ -4410,7 +5673,7 @@ dependencies = [ "nix 0.26.4", "once_cell", "ordered-stream", - "rand 0.8.5", + "rand", "serde", "serde_repr", "sha1", @@ -4426,11 +5689,11 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "3.14.1" +version = "3.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d1794a946878c0e807f55a397187c11fc7a038ba5d868e7db4f3bd7760bc9d" +checksum = "7131497b0f887e8061b430c530240063d33bf9455fa34438f388a245da69e0a5" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 1.3.1", "proc-macro2", "quote", "regex", @@ -4440,9 +5703,9 @@ dependencies = [ [[package]] name = "zbus_names" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb80bb776dbda6e23d705cf0123c3b95df99c4ebeaec6c2599d4a5419902b4a9" +checksum = "437d738d3750bed6ca9b8d423ccc7a8eb284f6b1d6d4e225a0e4e6258d864c8d" dependencies = [ "serde", "static_assertions", @@ -4451,30 +5714,29 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.3.0" +version = "0.7.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6580539ad917b7c026220c4b3f2c08d52ce54d6ce0dc491e66002e35388fab46" +checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" dependencies = [ - "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.2.0" +version = "0.7.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d498dbd1fd7beb83c86709ae1c33ca50942889473473d287d56ce4770a18edfb" +checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" dependencies = [ "proc-macro2", - "syn 1.0.109", - "synstructure", + "quote", + "syn 2.0.61", ] [[package]] name = "zeroize" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" dependencies = [ "zeroize_derive", ] @@ -4487,14 +5749,14 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.61", ] [[package]] name = "zvariant" -version = "3.15.0" +version = "3.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44b291bee0d960c53170780af148dca5fa260a63cdd24f1962fa82e03e53338c" +checksum = "4eef2be88ba09b358d3b58aca6e41cd853631d44787f319a1383ca83424fb2db" dependencies = [ "byteorder", "enumflags2", @@ -4506,11 +5768,11 @@ dependencies = [ [[package]] name = "zvariant_derive" -version = "3.15.0" +version = "3.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "934d7a7dfc310d6ee06c87ffe88ef4eca7d3e37bb251dece2ef93da8f17d8ecd" +checksum = "37c24dc0bed72f5f90d1f8bb5b07228cbf63b3c6e9f82d82559d4bae666e7ed9" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 1.3.1", "proc-macro2", "quote", "syn 1.0.109", diff --git a/Cargo.toml b/Cargo.toml index c699b80..1609029 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,27 @@ [workspace] - +resolver = "2" members = [ - "lib", "app", + "cli", + "lib", + "rpc-api", ] +[workspace.package] +authors = [ + "Ash Manning ", + "Nikita Chashchinskii " +] +edition = "2021" +version = "0.8.6" + +[workspace.dependencies.bip300301] +git = "https://github.com/Ash-L2L/bip300301.git" +rev = "64568dee7b89fe8c021226f10b17a18fe3386871" + +[workspace.dependencies.rustreexo] +git = "https://github.com/Ash-L2L/rustreexo.git" +rev = "a3ac7d3ebe9749ebd0bb34c709e7616f83d573b3" + [profile.release] -lto = "fat" +# lto = "fat" diff --git a/app/Cargo.toml b/app/Cargo.toml index 8d64389..cd4edfb 100644 --- a/app/Cargo.toml +++ b/app/Cargo.toml @@ -1,20 +1,43 @@ [package] name = "thunder_app" -version = "0.2.0" -edition = "2021" -authors = [ "Nikita Chashchinskii " ] +authors.workspace = true +edition.workspace = true +version.workspace = true -[dependencies] -thunder = { path = "../lib" } +[dev-dependencies] +base64 = "0.21.2" +futures = { version = "0.3.30", default-features = false, features = ["async-await"]} +jsonrpsee = { version = "0.20.0", features = ["http-client"] } +tempfile = "3.10.0" +test-log = { version = "0.2.14", features = ["trace"] } +tokio = { version = "1.29.1", features = ["process", "rt-multi-thread"] } +[dependencies] anyhow = "1.0.72" -serde = { version = "1.0.179", features = ["derive"] } -thiserror = "1.0.44" -tokio = { version = "1.29.1", features = ["rt-multi-thread", "macros"] } bincode = "1.3.3" -clap = { version = "4.3.19", features = ["derive"] } +bip300301.workspace = true +clap = { version = "4.5.4", features = ["derive"] } +ctrlc = "3.4.0" dirs = "5.0.1" -eframe = "0.22.0" -tiny-bip39 = "1.0.0" +eframe = "0.27.1" +futures = "0.3.30" human-size = "0.4.3" -jsonrpsee = { version = "0.20.0", features = ["server", "macros"] } +jsonrpsee = { version = "0.20.0", features = ["server"] } +rustreexo = { workspace = true } +parking_lot = "0.12.1" +serde = { version = "1.0.179", features = ["derive"] } +shlex = "1.3.0" +strum = { version = "0.26.2", features = ["derive"] } +thiserror = "1.0.44" +thunder = { path = "../lib" } +thunder_app_cli = { path = "../cli" } +thunder_app_rpc_api = { path = "../rpc-api" } +tiny-bip39 = "1.0.0" +tokio = { version = "1.29.1", features = ["macros", "rt-multi-thread"] } +tokio-util = { version = "0.7.10", features = ["rt"] } +tracing = "0.1.40" +tracing-subscriber = "0.3.18" + +[[bin]] +name = "thunder_app" +path = "main.rs" \ No newline at end of file diff --git a/app/app.rs b/app/app.rs new file mode 100644 index 0000000..cd2b76b --- /dev/null +++ b/app/app.rs @@ -0,0 +1,246 @@ +use std::{collections::HashMap, sync::Arc}; + +use parking_lot::RwLock; +use rustreexo::accumulator::proof::Proof; +use thunder::{ + bip300301::{bitcoin, MainClient}, + format_deposit_address, + miner::{self, Miner}, + node::{self, Node, THIS_SIDECHAIN}, + types::{self, OutPoint, Output, Transaction}, + wallet::{self, Wallet}, +}; +use tokio::sync::RwLock as TokioRwLock; +use tokio_util::task::LocalPoolHandle; + +use crate::cli::Config; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("drivechain error")] + Drivechain(#[from] bip300301::Error), + #[error("io error")] + Io(#[from] std::io::Error), + #[error("jsonrpsee error")] + Jsonrpsee(#[from] jsonrpsee::core::Error), + #[error("miner error")] + Miner(#[from] miner::Error), + #[error("node error")] + Node(#[from] node::Error), + #[error("Utreexo error: {0}")] + Utreexo(String), + #[error("wallet error")] + Wallet(#[from] wallet::Error), +} + +#[derive(Clone)] +pub struct App { + pub node: Arc, + pub wallet: Arc, + pub miner: Arc>, + pub utxos: Arc>>, + pub transaction: Arc>, + pub runtime: Arc, + pub local_pool: LocalPoolHandle, +} + +impl App { + pub fn new(config: &Config) -> Result { + // Node launches some tokio tasks for p2p networking, that is why we need a tokio runtime + // here. + let runtime = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build()?; + let wallet = Wallet::new(&config.datadir.join("wallet.mdb"))?; + if let Some(seed_phrase_path) = &config.mnemonic_seed_phrase_path { + let mnemonic = std::fs::read_to_string(seed_phrase_path)?; + let () = wallet.set_seed_from_mnemonic(mnemonic.as_str())?; + } + let miner = Miner::new( + THIS_SIDECHAIN, + config.main_addr, + &config.main_user, + &config.main_password, + )?; + let rt_guard = runtime.enter(); + let local_pool = LocalPoolHandle::new(1); + let node = Node::new( + &config.datadir, + config.net_addr, + config.main_addr, + &config.main_user, + &config.main_password, + local_pool.clone(), + )?; + drop(rt_guard); + let utxos = { + let mut utxos = wallet.get_utxos()?; + let transactions = node.get_all_transactions()?; + for transaction in &transactions { + for (outpoint, _) in &transaction.transaction.inputs { + utxos.remove(outpoint); + } + } + utxos + }; + Ok(Self { + node: Arc::new(node), + wallet: Arc::new(wallet), + miner: Arc::new(TokioRwLock::new(miner)), + utxos: Arc::new(RwLock::new(utxos)), + transaction: Arc::new(RwLock::new(Transaction { + inputs: vec![], + proof: Proof::default(), + outputs: vec![], + })), + runtime: Arc::new(runtime), + local_pool, + }) + } + + pub fn sign_and_send(&self, tx: Transaction) -> Result<(), Error> { + let authorized_transaction = self.wallet.authorize(tx)?; + self.node.submit_transaction(authorized_transaction)?; + self.update_utxos()?; + Ok(()) + } + + pub fn get_new_main_address( + &self, + ) -> Result, Error> { + let address = self.runtime.block_on({ + let miner = self.miner.clone(); + async move { + let miner_read = miner.read().await; + let drivechain_client = &miner_read.drivechain.client; + let mainchain_info = + drivechain_client.get_blockchain_info().await?; + let res = drivechain_client + .getnewaddress("", "legacy") + .await? + .require_network(mainchain_info.chain) + .unwrap(); + Result::<_, Error>::Ok(res) + } + })?; + Ok(address) + } + + const EMPTY_BLOCK_BMM_BRIBE: bip300301::bitcoin::Amount = + bip300301::bitcoin::Amount::from_sat(1000); + + pub async fn mine( + &self, + fee: Option, + ) -> Result<(), Error> { + const NUM_TRANSACTIONS: usize = 1000; + let (txs, tx_fees) = self.node.get_transactions(NUM_TRANSACTIONS)?; + let coinbase = match tx_fees { + 0 => vec![], + _ => vec![types::Output { + address: self.wallet.get_new_address()?, + content: types::OutputContent::Value(tx_fees), + }], + }; + let body = types::Body::new(txs, coinbase); + let prev_side_hash = self.node.get_best_hash()?; + let prev_main_hash = self + .miner + .read() + .await + .drivechain + .get_mainchain_tip() + .await?; + let roots = { + let mut accumulator = self.node.get_tip_accumulator()?; + body.modify_pollard(&mut accumulator.0) + .map_err(Error::Utreexo)?; + accumulator + .0 + .get_roots() + .iter() + .map(|root| root.get_data()) + .collect() + }; + let header = types::Header { + merkle_root: body.compute_merkle_root(), + roots, + prev_side_hash, + prev_main_hash, + }; + let bribe = fee.unwrap_or_else(|| { + if tx_fees > 0 { + bip300301::bitcoin::Amount::from_sat(tx_fees) + } else { + Self::EMPTY_BLOCK_BMM_BRIBE + } + }); + let mut miner_write = self.miner.write().await; + miner_write + .attempt_bmm(bribe.to_sat(), 0, header, body) + .await?; + // miner_write.generate().await?; + tracing::trace!("confirming bmm..."); + if let Some((header, body)) = miner_write.confirm_bmm().await? { + tracing::trace!("confirmed bmm, submitting block"); + self.node.submit_block(&header, &body).await?; + } + drop(miner_write); + self.update_wallet()?; + self.update_utxos()?; + self.node.regenerate_proof(&mut self.transaction.write())?; + Ok(()) + } + + fn update_wallet(&self) -> Result<(), Error> { + let addresses = self.wallet.get_addresses()?; + let utxos = self.node.get_utxos_by_addresses(&addresses)?; + let outpoints: Vec<_> = self.wallet.get_utxos()?.into_keys().collect(); + let spent: Vec<_> = self + .node + .get_spent_utxos(&outpoints)? + .into_iter() + .map(|(outpoint, spent_output)| (outpoint, spent_output.inpoint)) + .collect(); + self.wallet.put_utxos(&utxos)?; + self.wallet.spend_utxos(&spent)?; + Ok(()) + } + + fn update_utxos(&self) -> Result<(), Error> { + let mut utxos = self.wallet.get_utxos()?; + let transactions = self.node.get_all_transactions()?; + for transaction in &transactions { + for (outpoint, _) in &transaction.transaction.inputs { + utxos.remove(outpoint); + } + } + *self.utxos.write() = utxos; + Ok(()) + } + + pub fn deposit( + &mut self, + amount: bitcoin::Amount, + fee: bitcoin::Amount, + ) -> Result<(), Error> { + self.runtime.block_on(async { + let address = self.wallet.get_new_address()?; + let address = + format_deposit_address(THIS_SIDECHAIN, &format!("{address}")); + self.miner + .read() + .await + .drivechain + .client + .createsidechaindeposit( + THIS_SIDECHAIN, + &address, + amount.into(), + fee.into(), + ) + .await?; + Ok(()) + }) + } +} diff --git a/app/src/cli.rs b/app/cli.rs similarity index 70% rename from app/src/cli.rs rename to app/cli.rs index 0978d8c..aa2713d 100644 --- a/app/src/cli.rs +++ b/app/cli.rs @@ -1,18 +1,27 @@ use clap::Parser; use std::{net::SocketAddr, path::PathBuf}; -#[derive(Parser)] +#[derive(Clone, Debug, Parser)] #[command(author, version, about, long_about = None)] pub struct Cli { /// data directory for storing blockchain data and wallet, defaults to ~/.local/share #[arg(short, long)] pub datadir: Option, - /// address to use for P2P networking, defaults to 127.0.0.1:4000 + /// If specified, the gui will not launch. + #[arg(long)] + pub headless: bool, + /// Log level, defaults to [`tracing::Level::Debug`] + #[arg(default_value_t = tracing::Level::DEBUG, long)] + pub log_level: tracing::Level, + /// address to use for P2P networking, defaults to 0.0.0.0:4000 #[arg(short, long)] pub net_addr: Option, /// address to connect to mainchain node RPC server, defaults to 127.0.0.1:18443 #[arg(short, long)] pub main_addr: Option, + /// Path to a mnemonic seed phrase + #[arg(long)] + pub mnemonic_seed_phrase_path: Option, /// address for use by the RPC server exposing getblockcount and stop commands, defaults to /// 127.0.0.1:2020 #[arg(short, long)] @@ -25,54 +34,62 @@ pub struct Cli { pub password_main: Option, } +#[derive(Clone, Debug)] pub struct Config { pub datadir: PathBuf, - pub net_addr: SocketAddr, + pub headless: bool, + pub log_level: tracing::Level, pub main_addr: SocketAddr, - pub rpc_addr: SocketAddr, - pub main_user: String, pub main_password: String, + pub mnemonic_seed_phrase_path: Option, + pub main_user: String, + pub net_addr: SocketAddr, + pub rpc_addr: SocketAddr, } impl Cli { - pub fn get_config(&self) -> anyhow::Result { - const DEFAULT_NET_ADDR: &str = "127.0.0.1:4000"; - let net_addr: SocketAddr = self - .net_addr + pub fn get_config(self) -> anyhow::Result { + let datadir = self + .datadir .clone() - .unwrap_or(DEFAULT_NET_ADDR.to_string()) - .parse()?; + .unwrap_or_else(|| { + dirs::data_dir() + .expect("couldn't get default datadir, specify --datadir") + }) + .join("thunder"); const DEFAULT_MAIN_ADDR: &str = "127.0.0.1:18443"; let main_addr: SocketAddr = self .main_addr .clone() .unwrap_or(DEFAULT_MAIN_ADDR.to_string()) .parse()?; + let main_password = self + .password_main + .clone() + .unwrap_or_else(|| "password".into()); + let main_user = self.user_main.clone().unwrap_or_else(|| "user".into()); + const DEFAULT_NET_ADDR: &str = "0.0.0.0:4000"; + let net_addr: SocketAddr = self + .net_addr + .clone() + .unwrap_or(DEFAULT_NET_ADDR.to_string()) + .parse()?; const DEFAULT_RPC_ADDR: &str = "127.0.0.1:2020"; let rpc_addr: SocketAddr = self .rpc_addr .clone() .unwrap_or(DEFAULT_RPC_ADDR.to_string()) .parse()?; - let datadir = self - .datadir - .clone() - .unwrap_or_else(|| { - dirs::data_dir().expect("couldn't get default datadir, specify --datadir") - }) - .join("thunder"); - let main_user = self.user_main.clone().unwrap_or_else(|| "user".into()); - let main_password = self - .password_main - .clone() - .unwrap_or_else(|| "password".into()); Ok(Config { datadir, - net_addr, + headless: self.headless, + log_level: self.log_level, main_addr, - rpc_addr, - main_user, main_password, + main_user, + mnemonic_seed_phrase_path: self.mnemonic_seed_phrase_path, + net_addr, + rpc_addr, }) } } diff --git a/app/src/gui/block_explorer.rs b/app/gui/block_explorer.rs similarity index 61% rename from app/src/gui/block_explorer.rs rename to app/gui/block_explorer.rs index c621030..a0d7354 100644 --- a/app/src/gui/block_explorer.rs +++ b/app/gui/block_explorer.rs @@ -1,9 +1,14 @@ -use crate::app::lib; -use crate::app::App; use eframe::egui; use human_size::{Byte, Kibibyte, Mebibyte, SpecificSize}; -use lib::state::State; -use lib::{bip300301::bitcoin, types::GetValue}; +use thunder::{ + bip300301::bitcoin, + state::State, + types::{Body, GetValue, Header}, +}; + +use crate::app::App; + +use super::util::UiExt; pub struct BlockExplorer { height: u32, @@ -16,8 +21,17 @@ impl BlockExplorer { pub fn show(&mut self, app: &mut App, ui: &mut egui::Ui) { let max_height = app.node.get_height().unwrap_or(0); - let header = app.node.get_header(self.height).ok().flatten(); - let body = app.node.get_body(self.height).ok().flatten(); + let block: Option<(Header, Body)> = { + if let Ok(Some(block_hash)) = + app.node.try_get_block_hash(self.height) + && let Ok(header) = app.node.get_header(block_hash) + && let Ok(body) = app.node.get_body(block_hash) + { + Some((header, body)) + } else { + None + } + }; egui::CentralPanel::default().show_inside(ui, |ui| { ui.heading("Block"); ui.horizontal(|ui| { @@ -32,24 +46,30 @@ impl BlockExplorer { self.height = max_height; } }); - if let (Some(header), Some(body)) = (header, body) { + if let Some((header, body)) = block { let hash = &format!("{}", header.hash()); let merkle_root = &format!("{}", header.merkle_root); let prev_side_hash = &format!("{}", header.prev_side_hash); let prev_main_hash = &format!("{}", header.prev_main_hash); - let body_size = bincode::serialize(&body).unwrap_or(vec![]).len(); - let coinbase_value: u64 = body.coinbase.iter().map(GetValue::get_value).sum(); + let body_size = + bincode::serialize(&body).unwrap_or(vec![]).len(); + let coinbase_value: u64 = + body.coinbase.iter().map(GetValue::get_value).sum(); let coinbase_value = bitcoin::Amount::from_sat(coinbase_value); let num_transactions = body.transactions.len(); - let body_size = if let Ok(body_size) = SpecificSize::new(body_size as f64, Byte) { + let body_size = if let Ok(body_size) = + SpecificSize::new(body_size as f64, Byte) + { let bytes = body_size.to_bytes(); if bytes < 1024 { format!("{body_size}") } else if bytes < 1024 * 1024 { - let body_size: SpecificSize = body_size.into(); + let body_size: SpecificSize = + body_size.into(); format!("{body_size}") } else { - let body_size: SpecificSize = body_size.into(); + let body_size: SpecificSize = + body_size.into(); format!("{body_size}") } } else { @@ -66,12 +86,26 @@ impl BlockExplorer { ui.monospace(format!("Num sigops: {num_sigops}")); let body_size_limit = State::body_size_limit(self.height); - if let Ok(body_size_limit) = SpecificSize::new(body_size_limit as f64, Byte) { - let body_size_limit: SpecificSize = body_size_limit.into(); - ui.monospace(format!("Body size limit: {body_size_limit}")); + if let Ok(body_size_limit) = + SpecificSize::new(body_size_limit as f64, Byte) + { + let body_size_limit: SpecificSize = + body_size_limit.into(); + ui.monospace(format!( + "Body size limit: {body_size_limit}" + )); } let body_sigops_limit = State::body_sigops_limit(self.height); ui.monospace(format!("Body sigops limit: {body_sigops_limit}")); + + if let Ok(Some(acc)) = + app.node.try_get_accumulator(header.hash()) + { + ui.monospace_selectable_multiline(format!( + "Utreexo accumulator: \n{}", + acc.0 + )); + } } }); } diff --git a/app/gui/coins/mod.rs b/app/gui/coins/mod.rs new file mode 100644 index 0000000..e29982d --- /dev/null +++ b/app/gui/coins/mod.rs @@ -0,0 +1,57 @@ +use eframe::egui; +use strum::{EnumIter, IntoEnumIterator}; + +use crate::app::App; + +mod transfer_receive; +mod tx_builder; +mod tx_creator; +mod utxo_creator; +mod utxo_selector; + +use transfer_receive::TransferReceive; +use tx_builder::TxBuilder; + +#[derive(Default, EnumIter, Eq, PartialEq, strum::Display)] +enum Tab { + #[default] + #[strum(to_string = "Transfer & Receive")] + TransferReceive, + #[strum(to_string = "Transaction Builder")] + TransactionBuilder, +} + +pub struct Coins { + transfer_receive: TransferReceive, + tab: Tab, + tx_builder: TxBuilder, +} + +impl Coins { + pub fn new(app: &App) -> Self { + Self { + transfer_receive: TransferReceive::new(app), + tab: Tab::default(), + tx_builder: TxBuilder::default(), + } + } + + pub fn show(&mut self, app: &mut App, ui: &mut egui::Ui) { + egui::TopBottomPanel::top("coins_tabs").show(ui.ctx(), |ui| { + ui.horizontal(|ui| { + Tab::iter().for_each(|tab_variant| { + let tab_name = tab_variant.to_string(); + ui.selectable_value(&mut self.tab, tab_variant, tab_name); + }) + }); + }); + egui::CentralPanel::default().show(ui.ctx(), |ui| match self.tab { + Tab::TransferReceive => { + let () = self.transfer_receive.show(app, ui); + } + Tab::TransactionBuilder => { + let () = self.tx_builder.show(app, ui).unwrap(); + } + }); + } +} diff --git a/app/gui/coins/transfer_receive.rs b/app/gui/coins/transfer_receive.rs new file mode 100644 index 0000000..b83924c --- /dev/null +++ b/app/gui/coins/transfer_receive.rs @@ -0,0 +1,153 @@ +use bip300301::bitcoin; +use eframe::egui; +use thunder::types::Address; + +use crate::{app::App, gui::util::UiExt}; + +#[derive(Debug, Default)] +struct Transfer { + dest: String, + amount: String, + fee: String, +} + +fn create_transfer( + app: &App, + dest: Address, + amount: bitcoin::Amount, + fee: bitcoin::Amount, +) -> anyhow::Result<()> { + let accumulator = app.node.get_tip_accumulator()?; + let tx = app.wallet.create_transaction( + &accumulator, + dest, + amount.to_sat(), + fee.to_sat(), + )?; + app.sign_and_send(tx)?; + Ok(()) +} + +impl Transfer { + fn show(&mut self, app: &App, ui: &mut egui::Ui) { + ui.add_sized((250., 10.), |ui: &mut egui::Ui| { + ui.horizontal(|ui| { + let dest_edit = egui::TextEdit::singleline(&mut self.dest) + .hint_text("destination address") + .desired_width(150.); + ui.add(dest_edit); + }) + .response + }); + ui.add_sized((110., 10.), |ui: &mut egui::Ui| { + ui.horizontal(|ui| { + let amount_edit = egui::TextEdit::singleline(&mut self.amount) + .hint_text("amount") + .desired_width(80.); + ui.add(amount_edit); + ui.label("BTC"); + }) + .response + }); + ui.add_sized((110., 10.), |ui: &mut egui::Ui| { + ui.horizontal(|ui| { + let fee_edit = egui::TextEdit::singleline(&mut self.fee) + .hint_text("fee") + .desired_width(80.); + ui.add(fee_edit); + ui.label("BTC"); + }) + .response + }); + let dest: Option
= self.dest.parse().ok(); + let amount = bitcoin::Amount::from_str_in( + &self.amount, + bitcoin::Denomination::Bitcoin, + ); + let fee = bitcoin::Amount::from_str_in( + &self.fee, + bitcoin::Denomination::Bitcoin, + ); + if ui + .add_enabled( + dest.is_some() && amount.is_ok() && fee.is_ok(), + egui::Button::new("transfer"), + ) + .clicked() + { + if let Err(err) = create_transfer( + app, + dest.expect("should not happen"), + amount.expect("should not happen"), + fee.expect("should not happen"), + ) { + tracing::error!("{err:#}"); + } else { + *self = Self::default(); + } + } + } +} + +#[derive(Debug)] +struct Receive { + address: anyhow::Result
, +} + +impl Receive { + fn new(app: &App) -> Self { + let address = app + .wallet + .get_new_address() + .map_err(anyhow::Error::from) + .inspect_err(|err| tracing::error!("{err:#}")); + Self { address } + } + + fn show(&mut self, app: &App, ui: &mut egui::Ui) { + match &self.address { + Ok(address) => { + ui.monospace_selectable_singleline(false, address.to_string()); + } + Err(err) => { + ui.monospace_selectable_multiline(format!("{err:#}")); + } + } + if ui.button("generate").clicked() { + *self = Self::new(app) + } + } +} + +#[derive(Debug)] +pub(super) struct TransferReceive { + transfer: Transfer, + receive: Receive, +} + +impl TransferReceive { + pub fn new(app: &App) -> Self { + Self { + transfer: Transfer::default(), + receive: Receive::new(app), + } + } + + pub fn show(&mut self, app: &mut App, ui: &mut egui::Ui) { + egui::SidePanel::left("transfer") + .exact_width(ui.available_width() / 2.) + .resizable(false) + .show_inside(ui, |ui| { + ui.vertical_centered(|ui| { + ui.heading("Transfer"); + self.transfer.show(app, ui); + }) + }); + egui::CentralPanel::default().show_inside(ui, |ui| { + ui.vertical_centered(|ui| { + ui.heading("Receive"); + self.receive.show(app, ui); + }) + }); + } +} diff --git a/app/gui/coins/tx_builder.rs b/app/gui/coins/tx_builder.rs new file mode 100644 index 0000000..2f2faea --- /dev/null +++ b/app/gui/coins/tx_builder.rs @@ -0,0 +1,143 @@ +use std::collections::HashSet; + +use eframe::egui; + +use thunder::{ + bip300301::bitcoin, + types::{GetValue, Transaction}, +}; + +use super::{ + tx_creator::TxCreator, + utxo_creator::UtxoCreator, + utxo_selector::{show_utxo, UtxoSelector}, +}; +use crate::app::App; + +#[derive(Debug, Default)] +pub struct TxBuilder { + // regular tx without extra data or special inputs/outputs + base_tx: Transaction, + tx_creator: TxCreator, + utxo_creator: UtxoCreator, + utxo_selector: UtxoSelector, +} + +impl TxBuilder { + pub fn show_value_in(&mut self, app: &mut App, ui: &mut egui::Ui) { + ui.heading("Value In"); + let selected: HashSet<_> = self + .base_tx + .inputs + .iter() + .map(|(outpoint, _)| *outpoint) + .collect(); + let utxos_read = app.utxos.read(); + let mut spent_utxos: Vec<_> = utxos_read + .iter() + .filter(|(outpoint, _)| selected.contains(outpoint)) + .collect(); + let value_in: u64 = spent_utxos + .iter() + .map(|(_, output)| output.get_value()) + .sum(); + self.tx_creator.value_in = value_in; + spent_utxos.sort_by_key(|(outpoint, _)| format!("{outpoint}")); + ui.separator(); + ui.monospace(format!("Total: {}", bitcoin::Amount::from_sat(value_in))); + ui.separator(); + egui::Grid::new("utxos").striped(true).show(ui, |ui| { + ui.monospace("kind"); + ui.monospace("outpoint"); + ui.monospace("value"); + ui.end_row(); + let mut remove = None; + for (vout, (outpoint, _)) in self.base_tx.inputs.iter().enumerate() + { + let output = &utxos_read[outpoint]; + show_utxo(ui, outpoint, output); + if ui.button("remove").clicked() { + remove = Some(vout); + } + ui.end_row(); + } + if let Some(vout) = remove { + self.base_tx.inputs.remove(vout); + } + }); + } + + pub fn show_value_out(&mut self, ui: &mut egui::Ui) { + ui.heading("Value Out"); + ui.separator(); + let value_out: u64 = + self.base_tx.outputs.iter().map(GetValue::get_value).sum(); + self.tx_creator.value_out = value_out; + ui.monospace(format!( + "Total: {}", + bitcoin::Amount::from_sat(value_out) + )); + ui.separator(); + egui::Grid::new("outputs").striped(true).show(ui, |ui| { + let mut remove = None; + ui.monospace("vout"); + ui.monospace("address"); + ui.monospace("value"); + ui.end_row(); + for (vout, output) in self.base_tx.outputs.iter().enumerate() { + let address = &format!("{}", output.address)[0..8]; + let value = bitcoin::Amount::from_sat(output.get_value()); + ui.monospace(format!("{vout}")); + ui.monospace(address.to_string()); + ui.with_layout( + egui::Layout::right_to_left(egui::Align::Max), + |ui| { + ui.monospace(format!("{value}")); + }, + ); + if ui.button("remove").clicked() { + remove = Some(vout); + } + ui.end_row(); + } + if let Some(vout) = remove { + self.base_tx.outputs.remove(vout); + } + }); + } + + pub fn show( + &mut self, + app: &mut App, + ui: &mut egui::Ui, + ) -> anyhow::Result<()> { + egui::SidePanel::left("spend_utxo") + .exact_width(250.) + .resizable(false) + .show_inside(ui, |ui| { + self.utxo_selector.show(app, ui, &mut self.base_tx); + }); + egui::SidePanel::left("value_in") + .exact_width(250.) + .resizable(false) + .show_inside(ui, |ui| { + let () = self.show_value_in(app, ui); + }); + egui::SidePanel::left("value_out") + .exact_width(250.) + .resizable(false) + .show_inside(ui, |ui| { + let () = self.show_value_out(ui); + }); + egui::SidePanel::left("create_utxo") + .exact_width(450.) + .resizable(false) + .show_separator_line(false) + .show_inside(ui, |ui| { + self.utxo_creator.show(app, ui, &mut self.base_tx); + ui.separator(); + self.tx_creator.show(app, ui, &mut self.base_tx).unwrap(); + }); + Ok(()) + } +} diff --git a/app/gui/coins/tx_creator.rs b/app/gui/coins/tx_creator.rs new file mode 100644 index 0000000..d93ba77 --- /dev/null +++ b/app/gui/coins/tx_creator.rs @@ -0,0 +1,68 @@ +use eframe::egui; + +use thunder::{ + bip300301::bitcoin, + types::{Transaction, Txid}, +}; + +use crate::app::App; + +#[derive(Debug, Default)] +pub struct TxCreator { + pub value_in: u64, + pub value_out: u64, + // if the base tx has changed, need to recompute final tx + base_txid: Txid, + final_tx: Option, +} + +fn send_tx(app: &App, tx: &mut Transaction) -> anyhow::Result<()> { + app.node.regenerate_proof(tx)?; + let () = app.sign_and_send(tx.clone())?; + Ok(()) +} + +impl TxCreator { + pub fn show( + &mut self, + app: &mut App, + ui: &mut egui::Ui, + base_tx: &mut Transaction, + ) -> anyhow::Result<()> { + // if base txid has changed, store the new txid + let base_txid = base_tx.txid(); + let base_txid_changed = base_txid != self.base_txid; + if base_txid_changed { + self.base_txid = base_txid; + } + // (re)compute final tx if: + // * the tx type, tx data, or base txid has changed + // * final tx not yet set + let refresh_final_tx = base_txid_changed || self.final_tx.is_none(); + if refresh_final_tx { + self.final_tx = Some(base_tx.clone()); + } + let final_tx = match &mut self.final_tx { + None => panic!("impossible! final tx should have been set"), + Some(final_tx) => final_tx, + }; + let txid = &format!("{}", final_tx.txid())[0..8]; + ui.monospace(format!("txid: {txid}")); + if self.value_in >= self.value_out { + let fee = self.value_in - self.value_out; + let fee = bitcoin::Amount::from_sat(fee); + ui.monospace(format!("fee: {fee}")); + if ui.button("sign and send").clicked() { + if let Err(err) = send_tx(app, final_tx) { + tracing::error!("{err:#}"); + } else { + *base_tx = Transaction::default(); + self.final_tx = None; + } + } + } else { + ui.label("Not Enough Value In"); + } + Ok(()) + } +} diff --git a/app/src/gui/utxo_creator.rs b/app/gui/coins/utxo_creator.rs similarity index 58% rename from app/src/gui/utxo_creator.rs rename to app/gui/coins/utxo_creator.rs index d8e386b..da728b7 100644 --- a/app/src/gui/utxo_creator.rs +++ b/app/gui/coins/utxo_creator.rs @@ -1,20 +1,12 @@ -use crate::app::lib; -use crate::app::App; use eframe::egui; -use lib::{ +use thunder::{ bip300301::bitcoin, - types::{self, Content, Output}, + types::{self, Output, OutputContent, Transaction}, }; -pub struct UtxoCreator { - utxo_type: UtxoType, - value: String, - address: String, - main_address: String, - main_fee: String, -} +use crate::app::App; -#[derive(Eq, PartialEq)] +#[derive(Debug, Eq, PartialEq)] enum UtxoType { Regular, Withdrawal, @@ -29,6 +21,15 @@ impl std::fmt::Display for UtxoType { } } +#[derive(Debug)] +pub struct UtxoCreator { + utxo_type: UtxoType, + value: String, + address: String, + main_address: String, + main_fee: String, +} + impl Default for UtxoCreator { fn default() -> Self { Self { @@ -42,14 +43,27 @@ impl Default for UtxoCreator { } impl UtxoCreator { - pub fn show(&mut self, app: &mut App, ui: &mut egui::Ui) { + pub fn show( + &mut self, + app: &mut App, + ui: &mut egui::Ui, + tx: &mut Transaction, + ) { ui.horizontal(|ui| { ui.heading("Create"); egui::ComboBox::from_id_source("utxo_type") .selected_text(format!("{}", self.utxo_type)) .show_ui(ui, |ui| { - ui.selectable_value(&mut self.utxo_type, UtxoType::Regular, "regular"); - ui.selectable_value(&mut self.utxo_type, UtxoType::Withdrawal, "withdrawal"); + ui.selectable_value( + &mut self.utxo_type, + UtxoType::Regular, + "regular", + ); + ui.selectable_value( + &mut self.utxo_type, + UtxoType::Withdrawal, + "withdrawal", + ); }); ui.heading("UTXO"); }); @@ -75,8 +89,15 @@ impl UtxoCreator { ui.monospace("Main Address:"); ui.add(egui::TextEdit::singleline(&mut self.main_address)); if ui.button("generate").clicked() { - let main_address = app.get_new_main_address().unwrap(); - self.main_address = format!("{main_address}"); + match app.get_new_main_address() { + Ok(main_address) => { + self.main_address = format!("{main_address}"); + } + Err(err) => { + let err = anyhow::Error::new(err); + tracing::error!("{err:#}") + } + }; } }); ui.horizontal(|ui| { @@ -88,10 +109,14 @@ impl UtxoCreator { ui.horizontal(|ui| { match self.utxo_type { UtxoType::Regular => { - let address: Option = self.address.parse().ok(); + let address: Option = + self.address.parse().ok(); let value: Option = - bitcoin::Amount::from_str_in(&self.value, bitcoin::Denomination::Bitcoin) - .ok(); + bitcoin::Amount::from_str_in( + &self.value, + bitcoin::Denomination::Bitcoin, + ) + .ok(); if ui .add_enabled( address.is_some() && value.is_some(), @@ -101,23 +126,31 @@ impl UtxoCreator { { let utxo = Output { address: address.expect("should not happen"), - content: Content::Value(value.expect("should not happen").to_sat()), + content: OutputContent::Value( + value.expect("should not happen").to_sat(), + ), }; - app.transaction.outputs.push(utxo); + tx.outputs.push(utxo); } } UtxoType::Withdrawal => { let value: Option = - bitcoin::Amount::from_str_in(&self.value, bitcoin::Denomination::Bitcoin) - .ok(); - let address: Option = self.address.parse().ok(); - let main_address: Option> = - self.main_address.parse().ok(); - let main_fee: Option = bitcoin::Amount::from_str_in( - &self.main_fee, - bitcoin::Denomination::Bitcoin, - ) - .ok(); + bitcoin::Amount::from_str_in( + &self.value, + bitcoin::Denomination::Bitcoin, + ) + .ok(); + let address: Option = + self.address.parse().ok(); + let main_address: Option< + bitcoin::Address, + > = self.main_address.parse().ok(); + let main_fee: Option = + bitcoin::Amount::from_str_in( + &self.main_fee, + bitcoin::Denomination::Bitcoin, + ) + .ok(); if ui .add_enabled( value.is_some() @@ -130,13 +163,16 @@ impl UtxoCreator { { let utxo = Output { address: address.expect("invalid address"), - content: Content::Withdrawal { + content: OutputContent::Withdrawal { value: value.expect("invalid value").to_sat(), - main_address: main_address.expect("invalid main_address"), - main_fee: main_fee.expect("invalid main_fee").to_sat(), + main_address: main_address + .expect("invalid main_address"), + main_fee: main_fee + .expect("invalid main_fee") + .to_sat(), }, }; - app.transaction.outputs.push(utxo); + tx.outputs.push(utxo); } } } diff --git a/app/src/gui/utxo_selector.rs b/app/gui/coins/utxo_selector.rs similarity index 50% rename from app/src/gui/utxo_selector.rs rename to app/gui/coins/utxo_selector.rs index 2bfd198..c568234 100644 --- a/app/src/gui/utxo_selector.rs +++ b/app/gui/coins/utxo_selector.rs @@ -1,26 +1,34 @@ -use crate::app::lib; -use crate::app::App; +use std::collections::HashSet; + use eframe::egui; -use lib::{ +use thunder::{ bip300301::bitcoin, - types::{GetValue, OutPoint, Output}, + types::{hash, GetValue, OutPoint, Output, PointedOutput, Transaction}, }; -use std::collections::HashSet; -#[derive(Default)] +use crate::app::App; + +#[derive(Debug, Default)] pub struct UtxoSelector; impl UtxoSelector { - pub fn show(&mut self, app: &mut App, ui: &mut egui::Ui) { + pub fn show( + &mut self, + app: &mut App, + ui: &mut egui::Ui, + tx: &mut Transaction, + ) { ui.heading("Spend UTXO"); - let selected: HashSet<_> = app.transaction.inputs.iter().cloned().collect(); - let utxos = &app.utxos; - let total: u64 = utxos + let selected: HashSet<_> = + tx.inputs.iter().map(|(outpoint, _)| *outpoint).collect(); + let utxos_read = app.utxos.read(); + let total: u64 = utxos_read .iter() .filter(|(outpoint, _)| !selected.contains(outpoint)) .map(|(_, output)| output.get_value()) .sum(); - let mut utxos: Vec<_> = utxos.into_iter().collect(); + let mut utxos: Vec<_> = (*utxos_read).clone().into_iter().collect(); + drop(utxos_read); utxos.sort_by_key(|(outpoint, _)| format!("{outpoint}")); ui.separator(); ui.monospace(format!("Total: {}", bitcoin::Amount::from_sat(total))); @@ -31,17 +39,24 @@ impl UtxoSelector { ui.monospace("value"); ui.end_row(); for (outpoint, output) in utxos { - if selected.contains(outpoint) { + if selected.contains(&outpoint) { continue; } //ui.horizontal(|ui| {}); - show_utxo(ui, outpoint, output); + show_utxo(ui, &outpoint, &output); if ui - .add_enabled(!selected.contains(outpoint), egui::Button::new("spend")) + .add_enabled( + !selected.contains(&outpoint), + egui::Button::new("spend"), + ) .clicked() { - app.transaction.inputs.push(*outpoint); + let utxo_hash = hash(&PointedOutput { + outpoint, + output: output.clone(), + }); + tx.inputs.push((outpoint, utxo_hash)); } ui.end_row(); } @@ -51,13 +66,19 @@ impl UtxoSelector { pub fn show_utxo(ui: &mut egui::Ui, outpoint: &OutPoint, output: &Output) { let (kind, hash, vout) = match outpoint { - OutPoint::Regular { txid, vout } => ("regular", format!("{txid}"), *vout), - OutPoint::Deposit(outpoint) => ("deposit", format!("{}", outpoint.txid), outpoint.vout), - OutPoint::Coinbase { merkle_root, vout } => ("coinbase", format!("{merkle_root}"), *vout), + OutPoint::Regular { txid, vout } => { + ("regular", format!("{txid}"), *vout) + } + OutPoint::Deposit(outpoint) => { + ("deposit", format!("{}", outpoint.txid), outpoint.vout) + } + OutPoint::Coinbase { merkle_root, vout } => { + ("coinbase", format!("{merkle_root}"), *vout) + } }; let hash = &hash[0..8]; let value = bitcoin::Amount::from_sat(output.get_value()); - ui.monospace(format!("{kind}",)); + ui.monospace(kind.to_string()); ui.monospace(format!("{hash}:{vout}",)); ui.with_layout(egui::Layout::right_to_left(egui::Align::Max), |ui| { ui.monospace(format!("{value}")); diff --git a/app/gui/console_logs.rs b/app/gui/console_logs.rs new file mode 100644 index 0000000..14aadec --- /dev/null +++ b/app/gui/console_logs.rs @@ -0,0 +1,121 @@ +use std::{ + net::SocketAddr, + sync::{ + atomic::{self, AtomicBool}, + Arc, + }, +}; + +use clap::Parser; +use eframe::egui::{ + self, Key, KeyboardShortcut, Modifiers, ScrollArea, TextEdit, TextStyle, + TopBottomPanel, Widget as _, +}; + +use crate::{ + app::App, + line_buffer::{LineBuffer, LineBufferWriter}, +}; + +const SHIFT_ENTER: KeyboardShortcut = KeyboardShortcut { + modifiers: Modifiers::SHIFT, + logical_key: Key::Enter, +}; + +#[derive(Parser)] +#[command(name(""), no_binary_name(true))] +pub struct ConsoleCommand { + #[command(subcommand)] + command: thunder_app_cli_lib::Command, +} + +pub struct ConsoleLogs { + line_buffer: LineBuffer, + command_input: String, + rpc_addr: SocketAddr, + running_command: Arc, +} + +impl ConsoleLogs { + pub fn new(line_buffer: LineBuffer, rpc_addr: SocketAddr) -> Self { + Self { + line_buffer, + command_input: String::new(), + rpc_addr, + running_command: Arc::new(AtomicBool::new(false)), + } + } + + fn console_command(&mut self, app: &App) { + use std::io::Write; + let Some(args) = shlex::split(&self.command_input) else { + return; + }; + let mut line_buffer_writer = LineBufferWriter::from(&self.line_buffer); + if let Err(err) = + writeln!(line_buffer_writer, "> {}", self.command_input) + { + tracing::error!("{err}"); + self.command_input.clear(); + return; + } else { + self.command_input.clear(); + } + let command = match ConsoleCommand::try_parse_from(args) { + Ok(ConsoleCommand { command }) => command, + Err(err) => { + if let Err(err) = writeln!(line_buffer_writer, "{err}") { + tracing::error!("{err}") + } + return; + } + }; + let cli = thunder_app_cli_lib::Cli { + rpc_addr: self.rpc_addr, + command, + }; + app.runtime.spawn({ + let running_command = self.running_command.clone(); + running_command.store(true, atomic::Ordering::SeqCst); + async move { + if let Err(err) = match cli.run().await { + Ok(res) => writeln!(line_buffer_writer, "{res}"), + Err(err) => writeln!(line_buffer_writer, "{err:#}"), + } { + tracing::error!("{err}") + } + running_command.store(false, atomic::Ordering::SeqCst); + } + }); + } + + pub fn show(&mut self, app: &App, ui: &mut egui::Ui) { + ScrollArea::vertical().stick_to_bottom(true).show(ui, |ui| { + let line_buffer_read = self.line_buffer.as_str(); + let mut logs: &str = &line_buffer_read; + TextEdit::multiline(&mut logs) + .font(TextStyle::Monospace) + .desired_width(f32::INFINITY) + .ui(ui); + }); + TopBottomPanel::bottom("command_input").show_inside(ui, |ui| { + let command_input = TextEdit::multiline(&mut self.command_input) + .font(TextStyle::Monospace) + .desired_width(f32::INFINITY) + .desired_rows(1) + .hint_text("help") + .return_key(SHIFT_ENTER); + let command_input_resp = ui.add_enabled( + !self.running_command.load(atomic::Ordering::SeqCst), + command_input, + ); + if command_input_resp.ctx.input_mut(|input| { + !input.consume_shortcut(&SHIFT_ENTER) + && input.consume_key(Modifiers::NONE, Key::Enter) + && !self.running_command.load(atomic::Ordering::SeqCst) + }) { + self.console_command(app); + } + }); + } +} diff --git a/app/src/gui/mempool_explorer.rs b/app/gui/mempool_explorer.rs similarity index 65% rename from app/src/gui/mempool_explorer.rs rename to app/gui/mempool_explorer.rs index 5aa5c2f..6859ded 100644 --- a/app/src/gui/mempool_explorer.rs +++ b/app/gui/mempool_explorer.rs @@ -1,25 +1,20 @@ -use crate::app::lib; -use crate::app::App; use eframe::egui; use human_size::{Byte, Kibibyte, Mebibyte, SpecificSize}; -use lib::{ +use thunder::{ bip300301::bitcoin, types::{GetValue, OutPoint}, }; +use crate::app::App; + +#[derive(Default)] pub struct MemPoolExplorer { current: usize, } -impl Default for MemPoolExplorer { - fn default() -> Self { - Self { current: 0 } - } -} - impl MemPoolExplorer { pub fn show(&mut self, app: &mut App, ui: &mut egui::Ui) { - let transactions = app.node.get_all_transactions().unwrap_or(vec![]); + let transactions = app.node.get_all_transactions().unwrap_or_default(); let utxos = app.wallet.get_utxos().unwrap_or_default(); egui::SidePanel::left("transaction_picker") .resizable(false) @@ -33,7 +28,9 @@ impl MemPoolExplorer { ui.monospace("value out"); ui.monospace("fee"); ui.end_row(); - for (index, transaction) in transactions.iter().enumerate() { + for (index, transaction) in + transactions.iter().enumerate() + { let value_out: u64 = transaction .transaction .outputs @@ -44,30 +41,50 @@ impl MemPoolExplorer { .transaction .inputs .iter() - .map(|input| utxos.get(input).map(GetValue::get_value)) + .map(|(outpoint, _)| { + utxos.get(outpoint).map(GetValue::get_value) + }) .sum::>() .unwrap_or(0); - let txid = &format!("{}", transaction.transaction.txid())[0..8]; + let txid = + &format!("{}", transaction.transaction.txid()) + [0..8]; if value_in >= value_out { let fee = value_in - value_out; - ui.selectable_value(&mut self.current, index, format!("{txid}")); + ui.selectable_value( + &mut self.current, + index, + txid.to_string(), + ); ui.with_layout( - egui::Layout::right_to_left(egui::Align::Max), + egui::Layout::right_to_left( + egui::Align::Max, + ), |ui| { - let value_out = bitcoin::Amount::from_sat(value_out); + let value_out = + bitcoin::Amount::from_sat( + value_out, + ); ui.monospace(format!("{value_out}")); }, ); ui.with_layout( - egui::Layout::right_to_left(egui::Align::Max), + egui::Layout::right_to_left( + egui::Align::Max, + ), |ui| { - let fee = bitcoin::Amount::from_sat(fee); + let fee = + bitcoin::Amount::from_sat(fee); ui.monospace(format!("{fee}")); }, ); ui.end_row(); } else { - ui.selectable_value(&mut self.current, index, format!("{txid}")); + ui.selectable_value( + &mut self.current, + index, + txid.to_string(), + ); ui.monospace("invalid"); ui.end_row(); } @@ -85,22 +102,27 @@ impl MemPoolExplorer { ui.monospace("outpoint"); ui.monospace("value"); ui.end_row(); - for input in &transaction.transaction.inputs { - let (kind, hash, vout) = match input { + for (outpoint, _) in &transaction.transaction.inputs { + let (kind, hash, vout) = match outpoint { OutPoint::Regular { txid, vout } => { ("regular", format!("{txid}"), *vout) } - OutPoint::Deposit(outpoint) => { - ("deposit", format!("{}", outpoint.txid), outpoint.vout) - } - OutPoint::Coinbase { merkle_root, vout } => { - ("coinbase", format!("{merkle_root}"), *vout) - } + OutPoint::Deposit(outpoint) => ( + "deposit", + format!("{}", outpoint.txid), + outpoint.vout, + ), + OutPoint::Coinbase { merkle_root, vout } => ( + "coinbase", + format!("{merkle_root}"), + *vout, + ), }; - let output = &utxos[input]; + let output = &utxos[outpoint]; let hash = &hash[0..8]; - let value = bitcoin::Amount::from_sat(output.get_value()); - ui.monospace(format!("{kind}",)); + let value = + bitcoin::Amount::from_sat(output.get_value()); + ui.monospace(kind.to_string()); ui.monospace(format!("{hash}:{vout}",)); ui.monospace(format!("{value}",)); ui.end_row(); @@ -117,11 +139,14 @@ impl MemPoolExplorer { ui.monospace("address"); ui.monospace("value"); ui.end_row(); - for (vout, output) in transaction.transaction.outputs.iter().enumerate() { + for (vout, output) in + transaction.transaction.outputs.iter().enumerate() + { let address = &format!("{}", output.address)[0..8]; - let value = bitcoin::Amount::from_sat(output.get_value()); + let value = + bitcoin::Amount::from_sat(output.get_value()); ui.monospace(format!("{vout}")); - ui.monospace(format!("{address}")); + ui.monospace(address.to_string()); ui.monospace(format!("{value}")); ui.end_row(); } @@ -132,7 +157,8 @@ impl MemPoolExplorer { ui.separator(); let txid = transaction.transaction.txid(); ui.monospace(format!("Txid: {txid}")); - let transaction_size = bincode::serialize(&transaction).unwrap_or(vec![]).len(); + let transaction_size = + bincode::serialize(&transaction).unwrap_or(vec![]).len(); let transaction_size = if let Ok(transaction_size) = SpecificSize::new(transaction_size as f64, Byte) { @@ -140,10 +166,12 @@ impl MemPoolExplorer { if bytes < 1024 { format!("{transaction_size}") } else if bytes < 1024 * 1024 { - let transaction_size: SpecificSize = transaction_size.into(); + let transaction_size: SpecificSize = + transaction_size.into(); format!("{transaction_size}") } else { - let transaction_size: SpecificSize = transaction_size.into(); + let transaction_size: SpecificSize = + transaction_size.into(); format!("{transaction_size}") } } else { diff --git a/app/gui/miner.rs b/app/gui/miner.rs new file mode 100644 index 0000000..08def35 --- /dev/null +++ b/app/gui/miner.rs @@ -0,0 +1,52 @@ +use std::sync::{ + atomic::{self, AtomicBool}, + Arc, +}; + +use eframe::egui::{self, Button}; + +use crate::app::App; + +#[derive(Debug)] +pub struct Miner { + running: Arc, +} + +impl Default for Miner { + fn default() -> Self { + Self { + running: Arc::new(AtomicBool::new(false)), + } + } +} + +impl Miner { + pub fn show(&mut self, app: &App, ui: &mut egui::Ui) { + let block_height = app.node.get_height().unwrap_or(0); + let best_hash = app.node.get_best_hash().unwrap_or([0; 32].into()); + ui.label("Block height: "); + ui.monospace(format!("{block_height}")); + ui.label("Best hash: "); + let best_hash = &format!("{best_hash}")[0..8]; + ui.monospace(format!("{best_hash}...")); + let running = self.running.load(atomic::Ordering::SeqCst); + if ui + .add_enabled(!running, Button::new("Mine / Refresh Block")) + .clicked() + { + self.running.store(true, atomic::Ordering::SeqCst); + app.local_pool.spawn_pinned({ + let app = app.clone(); + let running = self.running.clone(); + || async move { + tracing::debug!("Mining..."); + let mining_result = app.mine(None).await; + running.store(false, atomic::Ordering::SeqCst); + if let Err(err) = mining_result { + tracing::error!("{:#}", anyhow::Error::new(err)) + } + } + }); + } + } +} diff --git a/app/gui/mod.rs b/app/gui/mod.rs new file mode 100644 index 0000000..97c40fe --- /dev/null +++ b/app/gui/mod.rs @@ -0,0 +1,161 @@ +use std::net::SocketAddr; + +use eframe::egui; +use strum::{EnumIter, IntoEnumIterator}; + +use crate::{app::App, line_buffer::LineBuffer}; + +mod block_explorer; +mod coins; +mod console_logs; +mod mempool_explorer; +mod miner; +mod parent_chain; +mod seed; +mod util; +mod withdrawals; + +use block_explorer::BlockExplorer; +use coins::Coins; +use console_logs::ConsoleLogs; +use mempool_explorer::MemPoolExplorer; +use miner::Miner; +use parent_chain::ParentChain; +use seed::SetSeed; +use withdrawals::Withdrawals; + +pub struct EguiApp { + app: App, + block_explorer: BlockExplorer, + coins: Coins, + console_logs: ConsoleLogs, + mempool_explorer: MemPoolExplorer, + miner: Miner, + parent_chain: ParentChain, + set_seed: SetSeed, + tab: Tab, + withdrawals: Withdrawals, +} + +#[derive(Default, EnumIter, Eq, PartialEq, strum::Display)] +enum Tab { + #[default] + #[strum(to_string = "Parent Chain")] + ParentChain, + #[strum(to_string = "Coins")] + Coins, + #[strum(to_string = "Mempool Explorer")] + MemPoolExplorer, + #[strum(to_string = "Block Explorer")] + BlockExplorer, + #[strum(to_string = "Withdrawals")] + Withdrawals, + #[strum(to_string = "Console / Logs")] + ConsoleLogs, +} + +impl EguiApp { + pub fn new( + app: App, + _cc: &eframe::CreationContext<'_>, + logs_capture: LineBuffer, + rpc_addr: SocketAddr, + ) -> Self { + // Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_visuals. + // Restore app state using cc.storage (requires the "persistence" feature). + // Use the cc.gl (a glow::Context) to create graphics shaders and buffers that you can use + // for e.g. egui::PaintCallback. + let coins = Coins::new(&app); + let console_logs = ConsoleLogs::new(logs_capture, rpc_addr); + let height = app.node.get_height().unwrap_or(0); + let parent_chain = ParentChain::new(&app); + Self { + app, + block_explorer: BlockExplorer::new(height), + coins, + console_logs, + mempool_explorer: MemPoolExplorer::default(), + miner: Miner::default(), + parent_chain, + set_seed: SetSeed::default(), + tab: Tab::default(), + withdrawals: Withdrawals::default(), + } + } + + fn bottom_panel_content(&mut self, ui: &mut egui::Ui) { + ui.horizontal(|ui| { + // Fill center space, + // see https://github.com/emilk/egui/discussions/3908#discussioncomment-8270353 + + // this frame target width + // == this frame initial max rect width - last frame others width + let id_cal_target_size = egui::Id::new("cal_target_size"); + let this_init_max_width = ui.max_rect().width(); + let last_others_width = ui.data(|data| { + data.get_temp(id_cal_target_size) + .unwrap_or(this_init_max_width) + }); + // this is the total available space for expandable widgets, you can divide + // it up if you have multiple widgets to expand, even with different ratios. + let this_target_width = this_init_max_width - last_others_width; + + ui.add_space(this_target_width); + ui.separator(); + self.miner.show(&self.app, ui); + // this frame others width + // == this frame final min rect width - this frame target width + ui.data_mut(|data| { + data.insert_temp( + id_cal_target_size, + ui.min_rect().width() - this_target_width, + ) + }); + }); + } +} + +impl eframe::App for EguiApp { + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { + if self.app.wallet.has_seed().unwrap_or(false) { + egui::TopBottomPanel::top("tabs").show(ctx, |ui| { + ui.horizontal(|ui| { + Tab::iter().for_each(|tab_variant| { + let tab_name = tab_variant.to_string(); + ui.selectable_value( + &mut self.tab, + tab_variant, + tab_name, + ); + }) + }); + }); + egui::TopBottomPanel::bottom("util") + .show(ctx, |ui| self.bottom_panel_content(ui)); + egui::CentralPanel::default().show(ctx, |ui| match self.tab { + Tab::ParentChain => self.parent_chain.show(&mut self.app, ui), + Tab::Coins => { + self.coins.show(&mut self.app, ui); + } + Tab::MemPoolExplorer => { + self.mempool_explorer.show(&mut self.app, ui); + } + Tab::BlockExplorer => { + self.block_explorer.show(&mut self.app, ui); + } + Tab::Withdrawals => { + self.withdrawals.show(&mut self.app, ui); + } + Tab::ConsoleLogs => { + self.console_logs.show(&self.app, ui); + } + }); + } else { + egui::CentralPanel::default().show(ctx, |_ui| { + egui::Window::new("Set Seed").show(ctx, |ui| { + self.set_seed.show(&self.app, ui); + }); + }); + } + } +} diff --git a/app/gui/parent_chain/info.rs b/app/gui/parent_chain/info.rs new file mode 100644 index 0000000..1485648 --- /dev/null +++ b/app/gui/parent_chain/info.rs @@ -0,0 +1,76 @@ +use bip300301::{bitcoin, MainClient}; +use eframe::egui; +use futures::TryFutureExt; + +use crate::{app::App, gui::util::UiExt}; + +#[derive(Clone, Debug)] +struct Inner { + mainchain_tip: bip300301::client::Block, + sidechain_wealth: bitcoin::Amount, +} + +pub(super) struct Info(anyhow::Result); + +impl Info { + fn get_parent_chain_info(app: &App) -> anyhow::Result { + let dc = app.node.drivechain(); + let mainchain_tip = app.runtime.block_on(async { + let mainchain_tip_blockhash = dc.get_mainchain_tip().await?; + dc.client + .getblock(&mainchain_tip_blockhash, None) + .map_err(bip300301::Error::Jsonrpsee) + .await + })?; + let sidechain_wealth = app.node.get_sidechain_wealth()?; + Ok(Inner { + mainchain_tip, + sidechain_wealth, + }) + } + + pub fn new(app: &App) -> Self { + let inner = Self::get_parent_chain_info(app) + .inspect_err(|err| tracing::error!("{err:#}")); + Self(inner) + } + + fn refresh_parent_chain_info(&mut self, app: &App) { + self.0 = Self::get_parent_chain_info(app) + .inspect_err(|err| tracing::error!("{err:#}")); + } + + pub fn show(&mut self, app: &mut App, ui: &mut egui::Ui) { + if ui.button("Refresh").clicked() { + let () = self.refresh_parent_chain_info(app); + } + let parent_chain_info = match self.0.as_ref() { + Ok(parent_chain_info) => parent_chain_info, + Err(err) => { + ui.monospace_selectable_multiline(format!("{err:#}")); + return; + } + }; + ui.horizontal(|ui| { + ui.monospace("Mainchain tip hash: "); + ui.monospace_selectable_singleline( + true, + parent_chain_info.mainchain_tip.hash.to_string(), + ) + }); + ui.horizontal(|ui| { + ui.monospace("Mainchain tip height: "); + ui.monospace_selectable_singleline( + true, + parent_chain_info.mainchain_tip.height.to_string(), + ) + }); + ui.horizontal(|ui| { + ui.monospace("Sidechain wealth: "); + ui.monospace_selectable_singleline( + false, + parent_chain_info.sidechain_wealth.to_string(), + ) + }); + } +} diff --git a/app/gui/parent_chain/mod.rs b/app/gui/parent_chain/mod.rs new file mode 100644 index 0000000..2adc3b3 --- /dev/null +++ b/app/gui/parent_chain/mod.rs @@ -0,0 +1,55 @@ +use eframe::egui; +use strum::{EnumIter, IntoEnumIterator}; + +use crate::app::App; + +mod info; +mod transfer; + +use info::Info; +use transfer::Transfer; + +#[derive(Default, EnumIter, Eq, PartialEq, strum::Display)] +enum Tab { + #[default] + #[strum(to_string = "Transfer")] + Transfer, + #[strum(to_string = "Info")] + Info, +} + +pub struct ParentChain { + info: Info, + tab: Tab, + transfer: Transfer, +} + +impl ParentChain { + pub fn new(app: &App) -> Self { + let info = Info::new(app); + Self { + info, + tab: Tab::default(), + transfer: Transfer::default(), + } + } + + pub fn show(&mut self, app: &mut App, ui: &mut egui::Ui) { + egui::TopBottomPanel::top("parent_chain_tabs").show(ui.ctx(), |ui| { + ui.horizontal(|ui| { + Tab::iter().for_each(|tab_variant| { + let tab_name = tab_variant.to_string(); + ui.selectable_value(&mut self.tab, tab_variant, tab_name); + }) + }); + }); + egui::CentralPanel::default().show(ui.ctx(), |ui| match self.tab { + Tab::Transfer => { + self.transfer.show(app, ui); + } + Tab::Info => { + self.info.show(app, ui); + } + }); + } +} diff --git a/app/gui/parent_chain/transfer.rs b/app/gui/parent_chain/transfer.rs new file mode 100644 index 0000000..34d8aa1 --- /dev/null +++ b/app/gui/parent_chain/transfer.rs @@ -0,0 +1,208 @@ +use bip300301::bitcoin; +use eframe::egui; + +use crate::app::App; + +#[derive(Debug, Default)] +pub struct Deposit { + amount: String, + fee: String, +} + +impl Deposit { + pub fn show(&mut self, app: &mut App, ui: &mut egui::Ui) { + ui.add_sized((110., 10.), |ui: &mut egui::Ui| { + ui.horizontal(|ui| { + let amount_edit = egui::TextEdit::singleline(&mut self.amount) + .hint_text("amount") + .desired_width(80.); + ui.add(amount_edit); + ui.label("BTC"); + }) + .response + }); + ui.add_sized((110., 10.), |ui: &mut egui::Ui| { + ui.horizontal(|ui| { + let fee_edit = egui::TextEdit::singleline(&mut self.fee) + .hint_text("fee") + .desired_width(80.); + ui.add(fee_edit); + ui.label("BTC"); + }) + .response + }); + let amount = bitcoin::Amount::from_str_in( + &self.amount, + bitcoin::Denomination::Bitcoin, + ); + let fee = bitcoin::Amount::from_str_in( + &self.fee, + bitcoin::Denomination::Bitcoin, + ); + + if ui + .add_enabled( + amount.is_ok() && fee.is_ok(), + egui::Button::new("deposit"), + ) + .clicked() + { + if let Err(err) = app.deposit( + amount.expect("should not happen"), + fee.expect("should not happen"), + ) { + tracing::error!("{err}"); + } else { + *self = Self::default(); + } + } + } +} + +#[derive(Debug, Default)] +pub struct Withdrawal { + mainchain_address: String, + amount: String, + fee: String, + mainchain_fee: String, +} + +fn create_withdrawal( + app: &App, + mainchain_address: bitcoin::Address, + amount: bitcoin::Amount, + fee: bitcoin::Amount, + mainchain_fee: bitcoin::Amount, +) -> anyhow::Result<()> { + let accumulator = app.node.get_tip_accumulator()?; + let tx = app.wallet.create_withdrawal( + &accumulator, + mainchain_address, + amount.to_sat(), + mainchain_fee.to_sat(), + fee.to_sat(), + )?; + app.sign_and_send(tx)?; + Ok(()) +} + +impl Withdrawal { + pub fn show(&mut self, app: &mut App, ui: &mut egui::Ui) { + ui.add_sized((250., 10.), |ui: &mut egui::Ui| { + ui.horizontal(|ui| { + let mainchain_address_edit = + egui::TextEdit::singleline(&mut self.mainchain_address) + .hint_text("mainchain address") + .desired_width(150.); + ui.add(mainchain_address_edit); + if ui.button("generate").clicked() { + match app.get_new_main_address() { + Ok(main_address) => { + self.mainchain_address = main_address.to_string(); + } + Err(err) => { + let err = anyhow::Error::new(err); + tracing::error!("{err:#}") + } + }; + } + }) + .response + }); + ui.add_sized((110., 10.), |ui: &mut egui::Ui| { + ui.horizontal(|ui| { + let amount_edit = egui::TextEdit::singleline(&mut self.amount) + .hint_text("amount") + .desired_width(80.); + ui.add(amount_edit); + ui.label("BTC"); + }) + .response + }); + ui.add_sized((110., 10.), |ui: &mut egui::Ui| { + ui.horizontal(|ui| { + let fee_edit = egui::TextEdit::singleline(&mut self.fee) + .hint_text("fee") + .desired_width(80.); + ui.add(fee_edit); + ui.label("BTC"); + }) + .response + }); + ui.add_sized((110., 10.), |ui: &mut egui::Ui| { + ui.horizontal(|ui| { + let fee_edit = + egui::TextEdit::singleline(&mut self.mainchain_fee) + .hint_text("mainchain fee") + .desired_width(80.); + ui.add(fee_edit); + ui.label("BTC"); + }) + .response + }); + let mainchain_address: Option< + bitcoin::Address, + > = self.mainchain_address.parse().ok(); + let amount = bitcoin::Amount::from_str_in( + &self.amount, + bitcoin::Denomination::Bitcoin, + ); + let fee = bitcoin::Amount::from_str_in( + &self.fee, + bitcoin::Denomination::Bitcoin, + ); + let mainchain_fee = bitcoin::Amount::from_str_in( + &self.mainchain_fee, + bitcoin::Denomination::Bitcoin, + ); + + if ui + .add_enabled( + mainchain_address.is_some() + && amount.is_ok() + && fee.is_ok() + && mainchain_fee.is_ok(), + egui::Button::new("withdraw"), + ) + .clicked() + { + if let Err(err) = create_withdrawal( + app, + mainchain_address.expect("should not happen"), + amount.expect("should not happen"), + fee.expect("should not happen"), + mainchain_fee.expect("should not happen"), + ) { + tracing::error!("{err:#}"); + } else { + *self = Self::default(); + } + } + } +} + +#[derive(Default)] +pub(super) struct Transfer { + deposit: Deposit, + withdrawal: Withdrawal, +} + +impl Transfer { + pub fn show(&mut self, app: &mut App, ui: &mut egui::Ui) { + egui::SidePanel::left("deposit") + .exact_width(ui.available_width() / 2.) + .resizable(false) + .show_inside(ui, |ui| { + ui.vertical_centered(|ui| { + ui.heading("Deposit"); + self.deposit.show(app, ui); + }) + }); + egui::CentralPanel::default().show_inside(ui, |ui| { + ui.vertical_centered(|ui| { + ui.heading("Withdrawal"); + self.withdrawal.show(app, ui); + }) + }); + } +} diff --git a/app/src/gui/seed.rs b/app/gui/seed.rs similarity index 71% rename from app/src/gui/seed.rs rename to app/gui/seed.rs index 5cce604..496f20c 100644 --- a/app/src/gui/seed.rs +++ b/app/gui/seed.rs @@ -23,8 +23,10 @@ impl SetSeed { .clip_text(false); ui.add(seed_edit); if ui.button("generate").clicked() { - let mnemonic = - bip39::Mnemonic::new(bip39::MnemonicType::Words12, bip39::Language::English); + let mnemonic = bip39::Mnemonic::new( + bip39::MnemonicType::Words12, + bip39::Language::English, + ); self.seed = mnemonic.phrase().into(); } }); @@ -33,15 +35,14 @@ impl SetSeed { .password(true) .clip_text(false); ui.add(passphrase_edit); - let mnemonic = bip39::Mnemonic::from_phrase(&self.seed, bip39::Language::English); + let mnemonic = + bip39::Mnemonic::from_phrase(&self.seed, bip39::Language::English); if ui .add_enabled(mnemonic.is_ok(), egui::Button::new("set")) .clicked() { - let mnemonic = mnemonic.expect("should never happen"); - let seed = bip39::Seed::new(&mnemonic, &self.passphrase); app.wallet - .set_seed(seed.as_bytes().try_into().expect("seed it not 64 bytes")) + .set_seed_from_mnemonic(self.seed.as_str()) .expect("failed to set HD wallet seed"); } } diff --git a/app/gui/util.rs b/app/gui/util.rs new file mode 100644 index 0000000..ccc0db8 --- /dev/null +++ b/app/gui/util.rs @@ -0,0 +1,68 @@ +use std::borrow::Borrow; + +use eframe::egui::{self, InnerResponse, Response, Ui}; + +// extension for InnerResponse and InnerResponse> +pub trait InnerResponseExt { + #[allow(dead_code)] + fn join(self) -> Response; +} + +impl InnerResponseExt for InnerResponse { + fn join(self) -> Response { + self.response | self.inner + } +} + +impl InnerResponseExt for InnerResponse> { + fn join(self) -> Response { + match self.inner { + Some(inner) => self.response | inner, + None => self.response, + } + } +} + +/// extension trait for egui::Ui +pub trait UiExt { + fn monospace_selectable_singleline( + &mut self, + clip_text: bool, + text: Text, + ) -> Response + where + Text: Borrow; + + fn monospace_selectable_multiline(&mut self, text: Text) -> Response + where + Text: Borrow; +} + +impl UiExt for Ui { + fn monospace_selectable_singleline( + &mut self, + clip_text: bool, + text: Text, + ) -> Response + where + Text: Borrow, + { + use egui::{TextEdit, TextStyle, Widget}; + let mut text: &str = text.borrow(); + TextEdit::singleline(&mut text) + .font(TextStyle::Monospace) + .clip_text(clip_text) + .ui(self) + } + + fn monospace_selectable_multiline(&mut self, text: Text) -> Response + where + Text: Borrow, + { + use egui::{TextEdit, TextStyle, Widget}; + let mut text: &str = text.borrow(); + TextEdit::multiline(&mut text) + .font(TextStyle::Monospace) + .ui(self) + } +} diff --git a/app/src/gui/withdrawals.rs b/app/gui/withdrawals.rs similarity index 71% rename from app/src/gui/withdrawals.rs rename to app/gui/withdrawals.rs index f510232..7683b6c 100644 --- a/app/src/gui/withdrawals.rs +++ b/app/gui/withdrawals.rs @@ -1,30 +1,27 @@ -use crate::app::lib; -use crate::app::App; use eframe::egui; -use lib::bip300301::bitcoin; -use lib::types::GetValue; +use thunder::{bip300301::bitcoin, types::GetValue}; -pub struct Withdrawals {} +use crate::app::App; -impl Default for Withdrawals { - fn default() -> Self { - Self {} - } -} +#[derive(Default)] +pub struct Withdrawals {} impl Withdrawals { pub fn show(&mut self, app: &mut App, ui: &mut egui::Ui) { ui.heading("Pending withdrawals"); let bundle = app.node.get_pending_withdrawal_bundle().ok().flatten(); if let Some(bundle) = bundle { - let mut spent_utxos: Vec<_> = bundle.spent_utxos.iter().collect(); + let mut spent_utxos: Vec<_> = bundle.spend_utxos.iter().collect(); spent_utxos.sort_by_key(|(outpoint, _)| format!("{outpoint}")); egui::Grid::new("bundle_utxos") .striped(true) .show(ui, |ui| { for (outpoint, output) in &spent_utxos { ui.monospace(format!("{outpoint}")); - ui.monospace(format!("{}", bitcoin::Amount::from_sat(output.get_value()))); + ui.monospace(format!( + "{}", + bitcoin::Amount::from_sat(output.get_value()) + )); ui.end_row(); } }); diff --git a/app/line_buffer.rs b/app/line_buffer.rs new file mode 100644 index 0000000..d1fc115 --- /dev/null +++ b/app/line_buffer.rs @@ -0,0 +1,118 @@ +use std::{ + collections::VecDeque, + sync::{Arc, Weak}, +}; + +use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard}; +use tracing_subscriber::fmt::MakeWriter; + +const DEFAULT_MAX_CAPACITY: usize = 0x1_000; + +struct LineBufferInner { + // Max number of lines to store + max_capacity: Option, + // Lengths of each line within the buffer, not incluing newlines + line_lengths: VecDeque, + line_buffer: VecDeque, +} + +impl LineBufferInner { + fn as_str(&self) -> &str { + // This is safe because the buffer is always checked to be valid + // UTF-8, and the buffer is always contiguous. + unsafe { std::str::from_utf8_unchecked(self.line_buffer.as_slices().0) } + } +} + +#[derive(Clone)] +#[repr(transparent)] +pub struct LineBuffer(Arc>); + +impl LineBuffer { + // Optionally, set the max number of lines to store + pub fn new(max_capacity: Option) -> Self { + let inner = LineBufferInner { + max_capacity, + line_lengths: VecDeque::new(), + line_buffer: VecDeque::new(), + }; + Self(Arc::new(RwLock::new(inner))) + } + + pub fn as_str(&self) -> MappedRwLockReadGuard<'_, str> { + RwLockReadGuard::map(self.0.read(), |inner| inner.as_str()) + } +} + +impl Default for LineBuffer { + fn default() -> Self { + Self::new(Some(DEFAULT_MAX_CAPACITY)) + } +} + +#[derive(Clone)] +#[repr(transparent)] +pub struct LineBufferWriter(Weak>); + +impl From<&LineBuffer> for LineBufferWriter { + fn from(line_buffer: &LineBuffer) -> Self { + Self(Arc::downgrade(&line_buffer.0)) + } +} + +impl std::io::Write for LineBufferWriter { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + use std::io::{Error, ErrorKind}; + if let Some(line_buffer) = self.0.upgrade() { + // check that the buffer is valid UTF-8 + let msg = std::str::from_utf8(buf) + .map_err(|err| Error::new(ErrorKind::InvalidData, err))?; + let mut lines = msg.split_inclusive('\n'); + let mut line_buffer_write = line_buffer.write(); + if line_buffer_write + .line_buffer + .back() + .is_some_and(|chr| *chr != b'\n') + { + // If the last line in the buffer is unterminated, + // the recorded length must be incremented by length of the + // rest of the line. + let last_buffer_line_length = + line_buffer_write.line_lengths.back_mut().unwrap(); + if let Some(rest_of_line) = lines.next() { + *last_buffer_line_length += rest_of_line.len(); + line_buffer_write + .line_buffer + .extend(rest_of_line.as_bytes()) + } + } + lines.for_each(|line| { + line_buffer_write.line_lengths.push_back(line.len()); + line_buffer_write.line_buffer.extend(line.as_bytes()); + }); + // Remove the oldest log lines if max_capacity is set + while line_buffer_write.max_capacity.is_some_and(|max_capacity| { + line_buffer_write.line_lengths.len() >= max_capacity + }) { + let oldest_line_length = + line_buffer_write.line_lengths.pop_front().unwrap(); + line_buffer_write.line_buffer.drain(..oldest_line_length); + } + line_buffer_write.line_buffer.make_contiguous(); + drop(line_buffer_write); + drop(line_buffer) + } + Ok(buf.len()) + } + + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} + +impl<'a> MakeWriter<'a> for LineBufferWriter { + type Writer = Self; + fn make_writer(&self) -> Self::Writer { + self.clone() + } +} diff --git a/app/main.rs b/app/main.rs new file mode 100644 index 0000000..c46e92c --- /dev/null +++ b/app/main.rs @@ -0,0 +1,79 @@ +#![feature(let_chains)] + +use std::sync::mpsc; + +use clap::Parser as _; + +use tracing_subscriber::{filter as tracing_filter, layer::SubscriberExt}; + +mod app; +mod cli; +mod gui; +mod line_buffer; +mod rpc_server; + +use line_buffer::{LineBuffer, LineBufferWriter}; + +// Configure logger +fn set_tracing_subscriber(log_level: tracing::Level) -> LineBuffer { + let targets_filter = tracing_filter::Targets::new().with_targets([ + ("bip300301", log_level), + ("jsonrpsee_core::tracing", log_level), + ("thunder", log_level), + ("thunder_app", log_level), + ]); + let line_buffer = LineBuffer::default(); + let stdout_layer = tracing_subscriber::fmt::layer() + .compact() + .with_line_number(true); + let capture_layer = tracing_subscriber::fmt::layer() + .compact() + .with_line_number(true) + .with_ansi(false) + .with_writer(LineBufferWriter::from(&line_buffer)); + let tracing_subscriber = tracing_subscriber::registry() + .with(targets_filter) + .with(stdout_layer) + .with(capture_layer); + tracing::subscriber::set_global_default(tracing_subscriber) + .expect("setting default subscriber failed"); + line_buffer +} + +fn main() -> anyhow::Result<()> { + let cli = cli::Cli::parse(); + let config = cli.get_config()?; + let line_buffer = set_tracing_subscriber(config.log_level); + let app = app::App::new(&config)?; + // spawn rpc server + app.runtime.spawn({ + let app = app.clone(); + async move { rpc_server::run_server(app, config.rpc_addr).await.unwrap() } + }); + if config.headless { + // wait for ctrlc signal + let (tx, rx) = mpsc::channel(); + ctrlc::set_handler(move || { + tx.send(()).unwrap(); + }) + .expect("Error setting Ctrl-C handler"); + rx.recv().unwrap(); + println!("Received Ctrl-C signal, exiting..."); + } else { + let native_options = eframe::NativeOptions::default(); + eframe::run_native( + "Thunder", + native_options, + Box::new(move |cc| { + Box::new(gui::EguiApp::new( + app, + cc, + line_buffer, + config.rpc_addr, + )) + }), + ) + .map_err(|err| anyhow::anyhow!("failed to launch egui app: {err}"))? + } + Ok(()) +} diff --git a/app/rpc_server.rs b/app/rpc_server.rs new file mode 100644 index 0000000..45822ea --- /dev/null +++ b/app/rpc_server.rs @@ -0,0 +1,192 @@ +use std::net::SocketAddr; + +use bip300301::bitcoin; +use jsonrpsee::{ + core::{async_trait, RpcResult}, + server::Server, + types::ErrorObject, +}; +use thunder::{ + node, + types::{Address, Txid}, + wallet, +}; +use thunder_app_rpc_api::RpcServer; + +use crate::app::{self, App}; + +pub struct RpcServerImpl { + app: App, +} + +fn custom_err_msg(err_msg: impl Into) -> ErrorObject<'static> { + ErrorObject::owned(-1, err_msg.into(), Option::<()>::None) +} + +fn custom_err(error: Error) -> ErrorObject<'static> +where + anyhow::Error: From, +{ + let error = anyhow::Error::from(error); + custom_err_msg(format!("{error:#}")) +} + +fn convert_app_err(err: app::Error) -> ErrorObject<'static> { + let err = anyhow::anyhow!(err); + tracing::error!("{err:#}"); + custom_err(err) +} + +fn convert_node_err(err: node::Error) -> ErrorObject<'static> { + custom_err(err) +} + +fn convert_wallet_err(err: wallet::Error) -> ErrorObject<'static> { + custom_err(err) +} + +#[async_trait] +impl RpcServer for RpcServerImpl { + async fn balance(&self) -> RpcResult { + self.app.wallet.get_balance().map_err(convert_wallet_err) + } + + async fn connect_peer(&self, addr: SocketAddr) -> RpcResult<()> { + self.app.node.connect_peer(addr).map_err(convert_node_err) + } + + async fn format_deposit_address( + &self, + address: Address, + ) -> RpcResult { + let deposit_address = thunder::format_deposit_address( + node::THIS_SIDECHAIN, + &address.to_string(), + ); + Ok(deposit_address) + } + + async fn generate_mnemonic(&self) -> RpcResult { + let mnemonic = bip39::Mnemonic::new( + bip39::MnemonicType::Words12, + bip39::Language::English, + ); + Ok(mnemonic.to_string()) + } + + async fn get_new_address(&self) -> RpcResult
{ + self.app + .wallet + .get_new_address() + .map_err(convert_wallet_err) + } + + async fn getblockcount(&self) -> RpcResult { + self.app.node.get_height().map_err(convert_node_err) + } + + async fn mine(&self, fee: Option) -> RpcResult<()> { + let fee = fee.map(bip300301::bitcoin::Amount::from_sat); + self.app.local_pool.spawn_pinned({ + let app = self.app.clone(); + move || async move { app.mine(fee).await.map_err(convert_app_err) } + }).await.unwrap() + } + + async fn remove_from_mempool(&self, txid: Txid) -> RpcResult<()> { + self.app + .node + .remove_from_mempool(txid) + .map_err(convert_node_err) + } + + async fn set_seed_from_mnemonic(&self, mnemonic: String) -> RpcResult<()> { + let mnemonic = + bip39::Mnemonic::from_phrase(&mnemonic, bip39::Language::English) + .map_err(custom_err)?; + let seed = bip39::Seed::new(&mnemonic, ""); + let seed_bytes: [u8; 64] = seed.as_bytes().try_into().map_err( + |err: <[u8; 64] as TryFrom<&[u8]>>::Error| custom_err(err), + )?; + self.app + .wallet + .set_seed(&seed_bytes) + .map_err(convert_wallet_err) + } + + async fn sidechain_wealth(&self) -> RpcResult { + self.app + .node + .get_sidechain_wealth() + .map_err(convert_node_err) + } + + async fn stop(&self) { + std::process::exit(0); + } + + async fn transfer( + &self, + dest: Address, + value_sats: u64, + fee_sats: u64, + ) -> RpcResult { + let accumulator = self + .app + .node + .get_tip_accumulator() + .map_err(convert_node_err)?; + let tx = self + .app + .wallet + .create_transaction(&accumulator, dest, value_sats, fee_sats) + .map_err(convert_wallet_err)?; + let txid = tx.txid(); + self.app.sign_and_send(tx).map_err(convert_app_err)?; + Ok(txid) + } + + async fn withdraw( + &self, + mainchain_address: bitcoin::Address, + amount_sats: u64, + fee_sats: u64, + mainchain_fee_sats: u64, + ) -> RpcResult { + let accumulator = self + .app + .node + .get_tip_accumulator() + .map_err(convert_node_err)?; + let tx = self + .app + .wallet + .create_withdrawal( + &accumulator, + mainchain_address, + amount_sats, + mainchain_fee_sats, + fee_sats, + ) + .map_err(convert_wallet_err)?; + let txid = tx.txid(); + self.app.sign_and_send(tx).map_err(convert_app_err)?; + Ok(txid) + } +} + +pub async fn run_server( + app: App, + rpc_addr: SocketAddr, +) -> anyhow::Result { + let server = Server::builder().build(rpc_addr).await?; + + let addr = server.local_addr()?; + let handle = server.start(RpcServerImpl { app }.into_rpc()); + + // In this example we don't care about doing shutdown so let's it run forever. + // You may use the `ServerHandle` to shut it down or manage it yourself. + tokio::spawn(handle.stopped()); + + Ok(addr) +} diff --git a/app/src/app.rs b/app/src/app.rs deleted file mode 100644 index c66a91b..0000000 --- a/app/src/app.rs +++ /dev/null @@ -1,191 +0,0 @@ -use crate::cli::Config; -use lib::{ - bip300301::{self, bitcoin, jsonrpsee, MainClient}, - format_deposit_address, - miner::{self, Miner}, - node::{self, Node, THIS_SIDECHAIN}, - types::{self, OutPoint, Output, Transaction}, - wallet::{self, Wallet}, -}; -use std::collections::HashMap; -pub use thunder as lib; - -pub struct App { - pub node: Node, - pub wallet: Wallet, - pub miner: Miner, - pub utxos: HashMap, - pub transaction: Transaction, - runtime: tokio::runtime::Runtime, -} - -impl App { - pub fn new(config: &Config) -> Result { - // Node launches some tokio tasks for p2p networking, that is why we need a tokio runtime - // here. - let runtime = tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build()?; - let wallet = Wallet::new(&config.datadir.join("wallet.mdb"))?; - let miner = Miner::new( - THIS_SIDECHAIN, - config.main_addr, - &config.main_user, - &config.main_password, - )?; - let node = runtime.block_on(async { - let node = match Node::new( - &config.datadir, - config.net_addr, - config.main_addr, - &config.main_user, - &config.main_password, - ) { - Ok(node) => node, - Err(err) => return Err(err), - }; - Ok(node) - })?; - runtime.block_on(async { - crate::rpc::run_server(node.clone(), config.rpc_addr) - .await - .unwrap(); - }); - let utxos = { - let mut utxos = wallet.get_utxos()?; - let transactions = node.get_all_transactions()?; - for transaction in &transactions { - for input in &transaction.transaction.inputs { - utxos.remove(input); - } - } - utxos - }; - Ok(Self { - node, - wallet, - miner, - utxos, - transaction: Transaction { - inputs: vec![], - outputs: vec![], - }, - runtime, - }) - } - - pub fn sign_and_send(&mut self) -> Result<(), Error> { - let authorized_transaction = self.wallet.authorize(self.transaction.clone())?; - self.runtime - .block_on(self.node.submit_transaction(&authorized_transaction))?; - self.transaction = Transaction { - inputs: vec![], - outputs: vec![], - }; - self.update_utxos()?; - Ok(()) - } - - pub fn get_new_main_address( - &self, - ) -> Result, Error> { - let address = self - .runtime - .block_on(self.miner.drivechain.client.getnewaddress("", "legacy"))?; - let address: bitcoin::Address = - address.require_network(bitcoin::Network::Regtest).unwrap(); - Ok(address) - } - - const EMPTY_BLOCK_BMM_BRIBE: u64 = 1000; - pub fn mine(&mut self) -> Result<(), Error> { - self.runtime.block_on(async { - const NUM_TRANSACTIONS: usize = 1000; - let (transactions, fee) = self.node.get_transactions(NUM_TRANSACTIONS)?; - let coinbase = match fee { - 0 => vec![], - _ => vec![types::Output { - address: self.wallet.get_new_address()?, - content: types::Content::Value(fee), - }], - }; - let body = types::Body::new(transactions, coinbase); - let prev_side_hash = self.node.get_best_hash()?; - let prev_main_hash = self.miner.drivechain.get_mainchain_tip().await?; - let header = types::Header { - merkle_root: body.compute_merkle_root(), - prev_side_hash, - prev_main_hash, - }; - let bribe = if fee > 0 { - fee - } else { - Self::EMPTY_BLOCK_BMM_BRIBE - }; - let bribe = bitcoin::Amount::from_sat(bribe); - self.miner - .attempt_bmm(bribe.to_sat(), 0, header, body) - .await?; - self.miner.generate().await?; - if let Ok(Some((header, body))) = self.miner.confirm_bmm().await { - self.node.submit_block(&header, &body).await?; - } - - Ok::<(), Error>(()) - })?; - self.update_wallet()?; - self.update_utxos()?; - Ok(()) - } - - fn update_wallet(&mut self) -> Result<(), Error> { - let addresses = self.wallet.get_addresses()?; - let utxos = self.node.get_utxos_by_addresses(&addresses)?; - let outpoints: Vec<_> = self.wallet.get_utxos()?.into_keys().collect(); - let spent = self.node.get_spent_utxos(&outpoints)?; - self.wallet.put_utxos(&utxos)?; - self.wallet.delete_utxos(&spent)?; - Ok(()) - } - - fn update_utxos(&mut self) -> Result<(), Error> { - let mut utxos = self.wallet.get_utxos()?; - let transactions = self.node.get_all_transactions()?; - for transaction in &transactions { - for input in &transaction.transaction.inputs { - utxos.remove(input); - } - } - self.utxos = utxos; - Ok(()) - } - - pub fn deposit(&mut self, amount: bitcoin::Amount, fee: bitcoin::Amount) -> Result<(), Error> { - self.runtime.block_on(async { - let address = self.wallet.get_new_address()?; - let address = format_deposit_address(THIS_SIDECHAIN, &format!("{address}")); - self.miner - .drivechain - .client - .createsidechaindeposit(THIS_SIDECHAIN, &address, amount.into(), fee.into()) - .await?; - Ok(()) - }) - } -} - -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error("node error")] - Node(#[from] node::Error), - #[error("wallet error")] - Wallet(#[from] wallet::Error), - #[error("miner error")] - Miner(#[from] miner::Error), - #[error("drivechain error")] - Drivechain(#[from] bip300301::Error), - #[error("io error")] - Io(#[from] std::io::Error), - #[error("jsonrpsee error")] - Jsonrpsee(#[from] jsonrpsee::core::Error), -} diff --git a/app/src/gui/deposit.rs b/app/src/gui/deposit.rs deleted file mode 100644 index c913de8..0000000 --- a/app/src/gui/deposit.rs +++ /dev/null @@ -1,50 +0,0 @@ -use crate::app::lib; -use crate::app::App; -use eframe::egui; -use lib::bip300301::bitcoin; - -pub struct Deposit { - amount: String, - fee: String, -} - -impl Default for Deposit { - fn default() -> Self { - Self { - amount: "".into(), - fee: "".into(), - } - } -} - -impl Deposit { - pub fn show(&mut self, app: &mut App, ui: &mut egui::Ui) { - ui.horizontal(|ui| { - let amount_edit = egui::TextEdit::singleline(&mut self.amount) - .hint_text("amount") - .desired_width(80.); - ui.add(amount_edit); - ui.label("BTC"); - }); - ui.horizontal(|ui| { - let fee_edit = egui::TextEdit::singleline(&mut self.fee) - .hint_text("fee") - .desired_width(80.); - ui.add(fee_edit); - ui.label("BTC"); - }); - - let amount = bitcoin::Amount::from_str_in(&self.amount, bitcoin::Denomination::Bitcoin); - let fee = bitcoin::Amount::from_str_in(&self.fee, bitcoin::Denomination::Bitcoin); - - if ui - .add_enabled(amount.is_ok() && fee.is_ok(), egui::Button::new("deposit")) - .clicked() - { - app.deposit( - amount.expect("should not happen"), - fee.expect("should not happen"), - ); - } - } -} diff --git a/app/src/gui/miner.rs b/app/src/gui/miner.rs deleted file mode 100644 index 3193ee2..0000000 --- a/app/src/gui/miner.rs +++ /dev/null @@ -1,25 +0,0 @@ -use crate::app::App; -use eframe::egui; - -pub struct Miner; - -impl Default for Miner { - fn default() -> Self { - Self - } -} - -impl Miner { - pub fn show(&mut self, app: &mut App, ui: &mut egui::Ui) { - let block_height = app.node.get_height().unwrap_or(0); - let best_hash = app.node.get_best_hash().unwrap_or([0; 32].into()); - ui.label("Block height: "); - ui.monospace(format!("{block_height}")); - ui.label("Best hash: "); - let best_hash = &format!("{best_hash}")[0..8]; - ui.monospace(format!("{best_hash}...")); - if ui.button("mine").clicked() { - app.mine(); - } - } -} diff --git a/app/src/gui/mod.rs b/app/src/gui/mod.rs deleted file mode 100644 index d3a4a78..0000000 --- a/app/src/gui/mod.rs +++ /dev/null @@ -1,230 +0,0 @@ -use std::collections::HashSet; - -use crate::app::{lib, App}; -use eframe::egui; -use lib::{bip300301::bitcoin, types::GetValue}; - -mod block_explorer; -mod deposit; -mod mempool_explorer; -mod miner; -mod seed; -mod utxo_creator; -mod utxo_selector; -mod withdrawals; - -use block_explorer::BlockExplorer; -use deposit::Deposit; -use mempool_explorer::MemPoolExplorer; -use miner::Miner; -use seed::SetSeed; -use utxo_selector::{show_utxo, UtxoSelector}; - -use self::{utxo_creator::UtxoCreator, withdrawals::Withdrawals}; - -pub struct EguiApp { - app: App, - set_seed: SetSeed, - miner: Miner, - deposit: Deposit, - tab: Tab, - utxo_selector: UtxoSelector, - utxo_creator: UtxoCreator, - mempool_explorer: MemPoolExplorer, - block_explorer: BlockExplorer, - withdrawals: Withdrawals, -} - -#[derive(Eq, PartialEq)] -enum Tab { - TransactionBuilder, - MemPoolExplorer, - BlockExplorer, - Withdrawals, -} - -impl EguiApp { - pub fn new(app: App, _cc: &eframe::CreationContext<'_>) -> Self { - // Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_visuals. - // Restore app state using cc.storage (requires the "persistence" feature). - // Use the cc.gl (a glow::Context) to create graphics shaders and buffers that you can use - // for e.g. egui::PaintCallback. - let height = app.node.get_height().unwrap_or(0); - Self { - app, - set_seed: SetSeed::default(), - miner: Miner::default(), - deposit: Deposit::default(), - utxo_selector: UtxoSelector::default(), - utxo_creator: UtxoCreator::default(), - mempool_explorer: MemPoolExplorer::default(), - block_explorer: BlockExplorer::new(height), - tab: Tab::TransactionBuilder, - withdrawals: Withdrawals::default(), - } - } -} - -impl eframe::App for EguiApp { - fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { - if self.app.wallet.has_seed().unwrap_or(false) { - egui::TopBottomPanel::top("tabs").show(ctx, |ui| { - ui.horizontal(|ui| { - ui.selectable_value( - &mut self.tab, - Tab::TransactionBuilder, - "transaction builder", - ); - ui.selectable_value(&mut self.tab, Tab::MemPoolExplorer, "mempool explorer"); - ui.selectable_value(&mut self.tab, Tab::BlockExplorer, "block explorer"); - ui.selectable_value(&mut self.tab, Tab::Withdrawals, "withdrawals"); - }); - }); - egui::TopBottomPanel::bottom("util").show(ctx, |ui| { - ui.horizontal(|ui| { - self.miner.show(&mut self.app, ui); - ui.separator(); - self.deposit.show(&mut self.app, ui); - }); - }); - egui::CentralPanel::default().show(ctx, |ui| match self.tab { - Tab::TransactionBuilder => { - let selected: HashSet<_> = - self.app.transaction.inputs.iter().cloned().collect(); - let value_in: u64 = self - .app - .utxos - .iter() - .filter(|(outpoint, _)| selected.contains(outpoint)) - .map(|(_, output)| output.get_value()) - .sum(); - let value_out: u64 = self - .app - .transaction - .outputs - .iter() - .map(GetValue::get_value) - .sum(); - egui::SidePanel::left("spend_utxo") - .exact_width(250.) - .resizable(false) - .show_inside(ui, |ui| { - self.utxo_selector.show(&mut self.app, ui); - }); - egui::SidePanel::left("value_in") - .exact_width(250.) - .resizable(false) - .show_inside(ui, |ui| { - ui.heading("Value In"); - let mut utxos: Vec<_> = self - .app - .utxos - .iter() - .filter(|(outpoint, _)| selected.contains(outpoint)) - .collect(); - utxos.sort_by_key(|(outpoint, _)| format!("{outpoint}")); - ui.separator(); - ui.monospace(format!("Total: {}", bitcoin::Amount::from_sat(value_in))); - ui.separator(); - egui::Grid::new("utxos").striped(true).show(ui, |ui| { - ui.monospace("kind"); - ui.monospace("outpoint"); - ui.monospace("value"); - ui.end_row(); - let mut remove = None; - for (vout, outpoint) in - self.app.transaction.inputs.iter().enumerate() - { - let output = &self.app.utxos[&outpoint]; - show_utxo(ui, &outpoint, output); - if ui.button("remove").clicked() { - remove = Some(vout); - } - ui.end_row(); - } - if let Some(vout) = remove { - self.app.transaction.inputs.remove(vout); - } - }); - }); - egui::SidePanel::left("value_out") - .exact_width(250.) - .resizable(false) - .show_inside(ui, |ui| { - ui.heading("Value Out"); - ui.separator(); - ui.monospace(format!( - "Total: {}", - bitcoin::Amount::from_sat(value_out) - )); - ui.separator(); - egui::Grid::new("outputs").striped(true).show(ui, |ui| { - let mut remove = None; - ui.monospace("vout"); - ui.monospace("address"); - ui.monospace("value"); - ui.end_row(); - for (vout, output) in - self.app.transaction.outputs.iter().enumerate() - { - let address = &format!("{}", output.address)[0..8]; - let value = bitcoin::Amount::from_sat(output.get_value()); - ui.monospace(format!("{vout}")); - ui.monospace(format!("{address}")); - ui.with_layout( - egui::Layout::right_to_left(egui::Align::Max), - |ui| { - ui.monospace(format!("{value}")); - }, - ); - if ui.button("remove").clicked() { - remove = Some(vout); - } - ui.end_row(); - } - if let Some(vout) = remove { - self.app.transaction.outputs.remove(vout); - } - }); - }); - egui::SidePanel::left("create_utxo") - .exact_width(450.) - .resizable(false) - .show_separator_line(false) - .show_inside(ui, |ui| { - self.utxo_creator.show(&mut self.app, ui); - ui.separator(); - ui.heading("Transaction"); - let txid = &format!("{}", self.app.transaction.txid())[0..8]; - ui.monospace(format!("txid: {txid}")); - if value_in >= value_out { - let fee = value_in - value_out; - let fee = bitcoin::Amount::from_sat(fee); - ui.monospace(format!("fee: {fee}")); - if ui.button("sign and send").clicked() { - self.app.sign_and_send().unwrap_or(()); - } - } else { - ui.label("Not Enough Value In"); - } - }); - } - Tab::MemPoolExplorer => { - self.mempool_explorer.show(&mut self.app, ui); - } - Tab::BlockExplorer => { - self.block_explorer.show(&mut self.app, ui); - } - Tab::Withdrawals => { - self.withdrawals.show(&mut self.app, ui); - } - }); - } else { - egui::CentralPanel::default().show(ctx, |_ui| { - egui::Window::new("Set Seed").show(ctx, |ui| { - self.set_seed.show(&mut self.app, ui); - }); - }); - } - } -} diff --git a/app/src/main.rs b/app/src/main.rs deleted file mode 100644 index 8fa2091..0000000 --- a/app/src/main.rs +++ /dev/null @@ -1,21 +0,0 @@ -use clap::Parser as _; - -mod app; -mod cli; -mod gui; -mod rpc; - -fn main() -> anyhow::Result<()> { - let cli = cli::Cli::parse(); - let config = cli.get_config()?; - let app = app::App::new(&config)?; - - let native_options = eframe::NativeOptions::default(); - eframe::run_native( - "Thunder", - native_options, - Box::new(|cc| Box::new(gui::EguiApp::new(app, cc))), - ) - .expect("failed to launch egui app"); - Ok(()) -} diff --git a/app/src/rpc.rs b/app/src/rpc.rs deleted file mode 100644 index c55b6ef..0000000 --- a/app/src/rpc.rs +++ /dev/null @@ -1,41 +0,0 @@ -use std::net::SocketAddr; - -use jsonrpsee::core::async_trait; -use jsonrpsee::proc_macros::rpc; -use jsonrpsee::server::Server; -use thunder::node::Node; - -#[rpc(server)] -pub trait Rpc { - #[method(name = "stop")] - async fn stop(&self); - #[method(name = "getblockcount")] - async fn getblockcount(&self) -> u32; -} - -pub struct RpcServerImpl { - node: Node, -} - -#[async_trait] -impl RpcServer for RpcServerImpl { - async fn stop(&self) { - std::process::exit(0); - } - async fn getblockcount(&self) -> u32 { - self.node.get_height().unwrap_or(0) - } -} - -pub async fn run_server(node: Node, rpc_addr: SocketAddr) -> anyhow::Result { - let server = Server::builder().build(rpc_addr).await?; - - let addr = server.local_addr()?; - let handle = server.start(RpcServerImpl { node }.into_rpc()); - - // In this example we don't care about doing shutdown so let's it run forever. - // You may use the `ServerHandle` to shut it down or manage it yourself. - tokio::spawn(handle.stopped()); - - Ok(addr) -} diff --git a/app/tests/regtest.rs b/app/tests/regtest.rs new file mode 100644 index 0000000..04c8121 --- /dev/null +++ b/app/tests/regtest.rs @@ -0,0 +1,382 @@ +#![feature(impl_trait_in_assoc_type)] + +use std::{ + ffi::OsString, + net::{SocketAddr, TcpListener}, + ops::Deref, + path::Path, +}; + +use bip300301::{ + bitcoin::{Address as BitcoinAddress, Amount as BitcoinAmount}, + MainClient, +}; +use futures::TryFutureExt; +use jsonrpsee::http_client::{HttpClient, HttpClientBuilder}; +use tempfile::tempdir; +// Shadows #[test] +use test_log::test; + +use thunder_app_rpc_api::RpcClient as ThunderClient; + +const RPC_PASS: &str = "integrationtest"; +const RPC_USER: &str = "integrationtest"; + +#[repr(transparent)] +struct ThunderdClient(HttpClient); + +impl ThunderdClient { + fn new(socket_addr: SocketAddr) -> anyhow::Result { + let client = HttpClientBuilder::default() + .build(format!("http://{socket_addr}"))?; + Ok(Self(client)) + } +} + +impl Deref for ThunderdClient { + type Target = impl ThunderClient; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[repr(transparent)] +struct MainchaindClient(HttpClient); + +impl MainchaindClient { + fn new(socket_addr: SocketAddr) -> anyhow::Result { + use base64::Engine; + let mut headers = jsonrpsee::http_client::HeaderMap::new(); + let auth = format!("{RPC_USER}:{RPC_PASS}"); + let header_value = format!( + "Basic {}", + base64::engine::general_purpose::STANDARD_NO_PAD.encode(auth) + ) + .parse()?; + headers.insert("authorization", header_value); + let client = HttpClientBuilder::default() + .set_headers(headers.clone()) + .build(format!("http://{socket_addr}"))?; + Ok(Self(client)) + } +} + +impl Deref for MainchaindClient { + type Target = impl MainClient; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +fn thunderd( + data_dir: &Path, + mainchaind_addr: SocketAddr, + rpc_addr: SocketAddr, +) -> tokio::process::Command { + const THUNDERD: &str = "../target/debug/thunder_app"; + let mut cmd = tokio::process::Command::new(THUNDERD); + cmd.arg("--datadir") + .arg(data_dir) + .args( + [ + &["--headless"][..], + &["--log-level", "DEBUG"], + &["--main-addr", &format!("{mainchaind_addr}")], + &["--password-main", RPC_PASS], + &["--user-main", RPC_USER], + &["--rpc-addr", &format!("{rpc_addr}")], + ] + .concat(), + ) + .kill_on_drop(true); + cmd +} + +fn mainchaind(data_dir: &Path, rpc_port: u16) -> tokio::process::Command { + const MAINCHAIND: &str = "../mainchain/src/drivechaind"; + let mut cmd = tokio::process::Command::new(MAINCHAIND); + cmd.arg({ + let mut arg = OsString::from("-datadir="); + arg.push(data_dir); + arg + }) + .args([ + "-server", + &format!("-rpcpassword={RPC_PASS}"), + &format!("-rpcport={rpc_port}"), + &format!("-rpcuser={RPC_USER}"), + "-regtest", + "-connect-0", + "-debug", + //format!("-printtoconsole"), + ]) + .kill_on_drop(true); + cmd +} + +// Mine `n` blocks, and verify that the block count has increased as expected. +async fn mine_mainchain_blocks( + mainchaind_client: &MainchaindClient, + mainchain_addr: &BitcoinAddress, + n_blocks: u32, +) -> anyhow::Result<()> { + let block_count_before = + MainClient::getblockcount(&**mainchaind_client).await?; + let _resp = mainchaind_client + .generate_to_address(n_blocks, mainchain_addr.as_unchecked()) + .await?; + let block_count_after = + MainClient::getblockcount(&**mainchaind_client).await?; + let blocks_mined = block_count_after - block_count_before; + anyhow::ensure!( + n_blocks as usize == blocks_mined, + "Expected to mine {n_blocks} blocks, but only {blocks_mined} were mined" + ); + Ok(()) +} + +// Mine a block, and verify that the block count has increased as expected. +async fn mine_thunder_block( + thunderd_client: &ThunderdClient, + mainchaind_client: &MainchaindClient, + mainchain_addr: &BitcoinAddress, + fee: Option, +) -> anyhow::Result<()> { + let block_count_before = + ThunderClient::getblockcount(&**thunderd_client).await?; + let ((), ()) = + futures::try_join!(thunderd_client.mine(fee).err_into(), async { + tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; + mine_mainchain_blocks(mainchaind_client, mainchain_addr, 1).await + })?; + let block_count_after = + ThunderClient::getblockcount(&**thunderd_client).await?; + let blocks_mined = block_count_after - block_count_before; + anyhow::ensure!( + blocks_mined == 1, + "Expected to mine 1 block, but {blocks_mined} blocks were mined" + ); + Ok(()) +} + +const SIDECHAIN_NAME: &str = "Thunder"; +// 0.1 BTC +const DEFAULT_TX_FEE: BitcoinAmount = BitcoinAmount::from_sat(1_000_000); + +#[test(tokio::test)] +async fn regtest_test() -> anyhow::Result<()> { + /* Initialize mainchaind and thunderd */ + + // Setting a ctrlc handler ensures that tempdirs are dropped on ctrlc + ctrlc::set_handler(|| ())?; + let thunder_datadir = tempdir()?; + let mainchain_datadir = tempdir()?; + // Requesting port 0 assigns an arbitrary free socket + let thunder_socketaddr = TcpListener::bind("127.0.0.1:0")?.local_addr()?; + let mainchain_socketaddr = + TcpListener::bind("127.0.0.1:0")?.local_addr()?; + tracing::debug!( + thunder_datadir = %thunder_datadir.path().display(), + mainchain_datadir = %mainchain_datadir.path().display(), + %thunder_socketaddr, + %mainchain_socketaddr + ); + let mut thunderd_handle = thunderd( + thunder_datadir.path(), + mainchain_socketaddr, + thunder_socketaddr, + ) + .spawn()?; + let mut mainchaind_handle = + mainchaind(mainchain_datadir.path(), mainchain_socketaddr.port()) + .spawn()?; + let thunderd_client = ThunderdClient::new(thunder_socketaddr)?; + let mainchaind_client = MainchaindClient::new(mainchain_socketaddr)?; + // Wait 10s to accomodate startup + tokio::time::sleep(tokio::time::Duration::from_secs(10)).await; + + /* Initialize Mainchain */ + + let mainchain_addr = mainchaind_client + .getnewaddress("", "legacy") + .await? + .assume_checked(); + tracing::debug!(%mainchain_addr); + + // Check that there are no blocks + { + let block_count = + MainClient::getblockcount(&*mainchaind_client).await?; + assert_eq!(block_count, 0, "Initial mainchain block count should be 0"); + } + // Generate 101 blocks + let () = + mine_mainchain_blocks(&mainchaind_client, &mainchain_addr, 101).await?; + { + // Create sidechain proposal + let sidechain_proposal = mainchaind_client + .create_sidechain_proposal( + thunder::node::THIS_SIDECHAIN, + SIDECHAIN_NAME, + "Thunder integration test", + ) + .await?; + // Check that sidechain proposal is cached + let sidechain_proposals = + mainchaind_client.list_sidechain_proposals().await?; + assert!( + sidechain_proposals.contains(&sidechain_proposal.info), + "Sidechain proposal should be cached" + ); + } + // Mine a block + let () = + mine_mainchain_blocks(&mainchaind_client, &mainchain_addr, 1).await?; + // Check that sidechain proposal was accepted, now ready for voting + { + let sidechain_activation_statuses = + mainchaind_client.list_sidechain_activation_status().await?; + assert!( + sidechain_activation_statuses + .iter() + .any(|activation_status| { + activation_status.name == SIDECHAIN_NAME + && activation_status.age == 1 + && activation_status.fail == 0 + }), + "Sidechain proposal should be accepted and ready for voting" + ); + } + // Check that there are no active sidechains + { + let active_sidechains = + mainchaind_client.list_active_sidechains().await?; + assert!( + active_sidechains.is_empty(), + "Expected no active sidechains, but received {active_sidechains:?}" + ); + } + // Mine until only one more block is needed to activate the sidechain + let () = + mine_mainchain_blocks(&mainchaind_client, &mainchain_addr, 2016 - 2) + .await?; + // Check that there are still no active sidechains + { + let active_sidechains = + mainchaind_client.list_active_sidechains().await?; + assert!( + active_sidechains.is_empty(), + "Expected no active sidechains, but received {active_sidechains:?}" + ); + } + // Mine one more block to activate the sidechain + let () = + mine_mainchain_blocks(&mainchaind_client, &mainchain_addr, 1).await?; + { + let active_sidechains = + mainchaind_client.list_active_sidechains().await?; + let sidechain_activation_statuses = + mainchaind_client.list_sidechain_activation_status().await?; + tracing::debug!(?sidechain_activation_statuses); + assert!( + !active_sidechains.is_empty(), + "Expected sidechain to activate" + ); + } + + /* Initialize Thunder */ + // Set a mnemonic seed + { + let mnemonic_seed = thunderd_client.generate_mnemonic().await?; + let () = thunderd_client + .set_seed_from_mnemonic(mnemonic_seed) + .await?; + } + // Generate addresses + let thunder_addr = thunderd_client.get_new_address().await?; + let thunder_deposit_addr = + thunderd_client.format_deposit_address(thunder_addr).await?; + // Check that there are no blocks + { + let block_count = + ThunderClient::getblockcount(&*thunderd_client).await?; + assert_eq!(block_count, 0, "Initial Thunder block count should be 0"); + } + // Mine a block + let () = mine_thunder_block( + &thunderd_client, + &mainchaind_client, + &mainchain_addr, + Some(DEFAULT_TX_FEE.to_sat()), + ) + .await?; + + /* Sidechain Deposit */ + + let _sidechain_deposit = mainchaind_client + .createsidechaindeposit( + thunder::node::THIS_SIDECHAIN, + &thunder_deposit_addr, + BitcoinAmount::from_int_btc(10).into(), + DEFAULT_TX_FEE.into(), + ) + .await?; + // Check that there are no deposits in the db + { + let sidechain_deposits = mainchaind_client + .count_sidechain_deposits(thunder::node::THIS_SIDECHAIN) + .await?; + anyhow::ensure!( + sidechain_deposits == 0, + "Expected 0 sidechain deposits, but got {sidechain_deposits}" + ); + } + // Mine a mainchain block + let () = + mine_mainchain_blocks(&mainchaind_client, &mainchain_addr, 1).await?; + // Verify that the deposit was added + { + let sidechain_deposits = mainchaind_client + .count_sidechain_deposits(thunder::node::THIS_SIDECHAIN) + .await?; + anyhow::ensure!( + sidechain_deposits == 1, + "Expected 1 sidechain deposits, but got {sidechain_deposits}" + ); + } + // Verify that there are no deposits on Thunder + { + let balance = thunderd_client.balance().await?; + anyhow::ensure!(balance == 0, "Expected 0 balance, but got {balance}"); + } + // Mine a BMM block to process the deposit + let () = mine_thunder_block( + &thunderd_client, + &mainchaind_client, + &mainchain_addr, + Some(DEFAULT_TX_FEE.to_sat()), + ) + .await?; + // Verify that the deposit was successful + { + let balance = thunderd_client.balance().await?; + anyhow::ensure!(balance > 0, "Expected positive balance"); + } + + /* Clean up */ + { + let () = thunderd_handle + .start_kill() + .or(mainchaind_handle.start_kill())?; + let (thunderd_output, mainchaind_output) = futures::join!( + thunderd_handle.wait_with_output(), + mainchaind_handle.wait_with_output(), + ); + let _thunderd_output = thunderd_output?; + let _mainchaind_output = mainchaind_output?; + }; + + Ok(()) +} diff --git a/cli/Cargo.toml b/cli/Cargo.toml new file mode 100644 index 0000000..c67254c --- /dev/null +++ b/cli/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "thunder_app_cli" +authors.workspace = true +edition.workspace = true +version.workspace = true + +[dependencies] +anyhow = "1.0.72" +bip300301.workspace = true +clap = { version = "4.5.4", features = ["derive"] } +jsonrpsee = { version = "0.20.0", features = ["client-core"] } +thunder = { path = "../lib" } +thunder_app_rpc_api = { path = "../rpc-api" } +tokio = "1.29.1" + +[lib] +name = "thunder_app_cli_lib" +path = "lib.rs" + +[[bin]] +name = "thunder_app_cli" +path = "main.rs" \ No newline at end of file diff --git a/cli/lib.rs b/cli/lib.rs new file mode 100644 index 0000000..324e8d6 --- /dev/null +++ b/cli/lib.rs @@ -0,0 +1,144 @@ +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; + +use bip300301::bitcoin; +use clap::{Parser, Subcommand}; +use jsonrpsee::http_client::{HttpClient, HttpClientBuilder}; +use thunder::types::{Address, Txid}; +use thunder_app_rpc_api::RpcClient; + +#[derive(Clone, Debug, Subcommand)] +#[command(arg_required_else_help(true))] +pub enum Command { + /// Get balance in sats + Balance, + /// Connect to a peer + ConnectPeer { addr: SocketAddr }, + /// Format a deposit address + FormatDepositAddress { address: Address }, + /// Generate a mnemonic seed phrase + GenerateMnemonic, + /// Get a new address + GetNewAddress, + /// Get the current block count + GetBlockcount, + /// Attempt to mine a sidechain block + Mine { + #[arg(long)] + fee_sats: Option, + }, + /// Remove a tx from the mempool + RemoveFromMempool { txid: Txid }, + /// Set the wallet seed from a mnemonic seed phrase + SetSeedFromMnemonic { mnemonic: String }, + /// Get total sidechain wealth + SidechainWealth, + /// Stop the node + Stop, + /// Transfer funds to the specified address + Transfer { + dest: Address, + #[arg(long)] + value_sats: u64, + #[arg(long)] + fee_sats: u64, + }, + /// Initiate a withdrawal to the specified mainchain address + Withdraw { + mainchain_address: bitcoin::Address, + #[arg(long)] + amount_sats: u64, + #[arg(long)] + fee_sats: u64, + #[arg(long)] + mainchain_fee_sats: u64, + }, +} + +const DEFAULT_RPC_ADDR: SocketAddr = + SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 2020); + +#[derive(Clone, Debug, Parser)] +#[command(author, version, about, long_about = None)] +pub struct Cli { + /// address for use by the RPC server + #[arg(default_value_t = DEFAULT_RPC_ADDR, long)] + pub rpc_addr: SocketAddr, + + #[command(subcommand)] + pub command: Command, +} + +impl Cli { + pub async fn run(self) -> anyhow::Result { + let rpc_client: HttpClient = HttpClientBuilder::default() + .build(format!("http://{}", self.rpc_addr))?; + let res = match self.command { + Command::Balance => { + let balance = rpc_client.balance().await?; + format!("{balance}") + } + Command::ConnectPeer { addr } => { + let () = rpc_client.connect_peer(addr).await?; + String::default() + } + Command::FormatDepositAddress { address } => { + rpc_client.format_deposit_address(address).await? + } + Command::GenerateMnemonic => rpc_client.generate_mnemonic().await?, + Command::GetNewAddress => { + let address = rpc_client.get_new_address().await?; + format!("{address}") + } + Command::GetBlockcount => { + let blockcount = rpc_client.getblockcount().await?; + format!("{blockcount}") + } + Command::Mine { fee_sats } => { + let () = rpc_client.mine(fee_sats).await?; + String::default() + } + Command::RemoveFromMempool { txid } => { + let () = rpc_client.remove_from_mempool(txid).await?; + String::default() + } + Command::SetSeedFromMnemonic { mnemonic } => { + let () = rpc_client.set_seed_from_mnemonic(mnemonic).await?; + String::default() + } + Command::SidechainWealth => { + let sidechain_wealth = rpc_client.sidechain_wealth().await?; + format!("{sidechain_wealth}") + } + Command::Stop => { + let () = rpc_client.stop().await?; + String::default() + } + Command::Transfer { + dest, + value_sats, + fee_sats, + } => { + let txid = + rpc_client.transfer(dest, value_sats, fee_sats).await?; + format!("{txid}") + } + Command::Withdraw { + mainchain_address, + amount_sats, + fee_sats, + mainchain_fee_sats, + } => { + let txid = rpc_client + .withdraw( + mainchain_address, + amount_sats, + fee_sats, + mainchain_fee_sats, + ) + .await?; + format!("{txid}") + } + }; + Ok(res) + } +} diff --git a/cli/main.rs b/cli/main.rs new file mode 100644 index 0000000..9606078 --- /dev/null +++ b/cli/main.rs @@ -0,0 +1,10 @@ +use clap::Parser; +use thunder_app_cli_lib::Cli; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let cli = Cli::parse(); + let res = cli.run().await?; + println!("{res}"); + Ok(()) +} diff --git a/lib/Cargo.toml b/lib/Cargo.toml index c26bddb..0fd1315 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -1,26 +1,41 @@ [package] name = "thunder" -version = "0.2.0" -edition = "2021" -authors = [ "Nikita Chashchinskii " ] +authors.workspace = true +edition.workspace = true +version.workspace = true [dependencies] -bip300301 = { git = "https://github.com/nchashch/bip300301", rev = "cf917605ab1937c57f19f72311f96ac0b4832de0" } - +anyhow = { version = "1.0.72", features = ["backtrace"] } bincode = "1.3.3" +bip300301 ={ workspace = true, features = ["tracing"] } blake3 = "1.4.1" +borsh = { version = "1.3.1", features = ["derive"] } bs58 = { version = "0.5.0", features = ["check"] } byteorder = "1.4.3" bytes = "1.4.0" -ed25519-dalek = { version = "1.0.1", features = ["batch", "serde"] } -ed25519-dalek-bip32 = "0.2.0" -heed = { git = "https://github.com/meilisearch/heed", tag = "v0.12.4", version = "0.12.4" } -hex = "0.4.3" +ed25519-dalek = { version = "2.1.1", features = ["batch", "serde"] } +ed25519-dalek-bip32 = "0.3.0" +fallible-iterator = "0.3.0" +futures = "0.3.30" +heed = "0.20.0" +hex = { version = "0.4.3", features = ["serde"] } +jsonrpsee = { version = "0.20.0" } +parking_lot = "0.12.1" quinn = "0.10.1" rayon = "1.7.0" rcgen = "0.11.1" rustls = { version = "0.21.5", features = ["dangerous_configuration"] } +rustreexo = { workspace = true, features = ["with-serde"] } serde = { version = "1.0.179", features = ["derive"] } +serde_json = "1.0.113" sha256 = "1.2.2" thiserror = "1.0.44" +tiny-bip39 = "1.0.0" tokio = { version = "1.29.1", features = ["sync"] } +tokio-stream = "0.1.15" +tokio-util = { version = "0.7.10", features = ["rt"] } +tracing = "0.1.40" + +[lib] +name = "thunder" +path = "lib.rs" \ No newline at end of file diff --git a/lib/archive.rs b/lib/archive.rs new file mode 100644 index 0000000..cb0b35d --- /dev/null +++ b/lib/archive.rs @@ -0,0 +1,973 @@ +use std::{cmp::Ordering, collections::HashSet}; + +use bip300301::{ + bitcoin::{self, hashes::Hash}, + DepositInfo, Header as BitcoinHeader, +}; +use fallible_iterator::FallibleIterator; +use heed::{types::SerdeBincode, Database, RoTxn, RwTxn}; + +use crate::types::{Accumulator, BlockHash, Body, Header}; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("invalid mainchain block hash for deposit")] + DepositInvalidMainBlockHash, + #[error("heed error")] + Heed(#[from] heed::Error), + #[error("invalid merkle root")] + InvalidMerkleRoot, + #[error("invalid previous side hash")] + InvalidPrevSideHash, + #[error("no accumulator for block {0}")] + NoAccumulator(BlockHash), + #[error("no ancestor with depth {depth} for block {block_hash}")] + NoAncestor { block_hash: BlockHash, depth: u32 }, + #[error("no mainchain ancestor with depth {depth} for block {block_hash}")] + NoMainAncestor { + block_hash: bitcoin::BlockHash, + depth: u32, + }, + #[error("unknown block hash: {0}")] + NoBlockHash(BlockHash), + #[error("no block body with hash {0}")] + NoBody(BlockHash), + #[error("no BMM verification result with for block {0}")] + NoBmmVerification(BlockHash), + #[error("no deposits info for block {0}")] + NoDepositsInfo(bitcoin::BlockHash), + #[error("no header with hash {0}")] + NoHeader(BlockHash), + #[error("no height info for block hash {0}")] + NoHeight(BlockHash), + #[error("no mainchain header with hash {0}")] + NoMainHeader(bitcoin::BlockHash), + #[error("no height info for mainchain block hash {0}")] + NoMainHeight(bitcoin::BlockHash), +} + +#[derive(Clone)] +pub struct Archive { + accumulators: Database, SerdeBincode>, + block_hash_to_height: Database, SerdeBincode>, + /// BMM verification status for each header. + /// A status of false indicates that verification failed. + /// All ancestors of any block should always be present. + bmm_verifications: Database, SerdeBincode>, + bodies: Database, SerdeBincode>, + /// Deposits by mainchain block, sorted first-to-last in each block + deposits: Database< + SerdeBincode, + SerdeBincode>, + >, + /// Ancestors, indexed exponentially such that the nth element in a vector + /// corresponds to the ancestor 2^(i+1) blocks before. + /// eg. + /// * the 0th element is the grandparent (2nd ancestor) + /// * the 1st element is the grandparent's grandparent (4th ancestor) + /// * the 3rd element is the 16th ancestor + exponential_ancestors: + Database, SerdeBincode>>, + /// Mainchain ancestors, indexed exponentially such that the nth element in a vector + /// corresponds to the ancestor 2^(i+1) blocks before. + /// eg. + /// * the 0th element is the grandparent (2nd ancestor) + /// * the 1st element is the grandparent's grandparent (4th ancestor) + /// * the 3rd element is the 16th ancestor + exponential_main_ancestors: Database< + SerdeBincode, + SerdeBincode>, + >, + /// Sidechain headers. All ancestors of any header should always be present. + headers: Database, SerdeBincode
>, + main_block_hash_to_height: + Database, SerdeBincode>, + /// Mainchain headers. All ancestors of any header should always be present + main_headers: + Database, SerdeBincode>, + /// Successor blocks. ALL known block hashes, INCLUDING the zero hash, + /// MUST be present. + successors: + Database, SerdeBincode>>, + /// Total work for mainchain headers. + /// All ancestors of any block should always be present + total_work: + Database, SerdeBincode>, +} + +impl Archive { + pub const NUM_DBS: u32 = 12; + + pub fn new(env: &heed::Env) -> Result { + let mut rwtxn = env.write_txn()?; + let accumulators = + env.create_database(&mut rwtxn, Some("accumulators"))?; + let block_hash_to_height = + env.create_database(&mut rwtxn, Some("hash_to_height"))?; + let bmm_verifications = + env.create_database(&mut rwtxn, Some("bmm_verifications"))?; + let bodies = env.create_database(&mut rwtxn, Some("bodies"))?; + let deposits = env.create_database(&mut rwtxn, Some("deposits"))?; + let exponential_ancestors = + env.create_database(&mut rwtxn, Some("exponential_ancestors"))?; + let exponential_main_ancestors = env + .create_database(&mut rwtxn, Some("exponential_main_ancestors"))?; + let headers = env.create_database(&mut rwtxn, Some("headers"))?; + let main_block_hash_to_height = + env.create_database(&mut rwtxn, Some("main_hash_to_height"))?; + let main_headers = + env.create_database(&mut rwtxn, Some("main_headers"))?; + let successors = env.create_database(&mut rwtxn, Some("successors"))?; + if successors.get(&rwtxn, &BlockHash::default())?.is_none() { + successors.put( + &mut rwtxn, + &BlockHash::default(), + &HashSet::new(), + )?; + } + let total_work = env.create_database(&mut rwtxn, Some("total_work"))?; + rwtxn.commit()?; + Ok(Self { + accumulators, + block_hash_to_height, + bmm_verifications, + bodies, + deposits, + exponential_ancestors, + exponential_main_ancestors, + headers, + main_block_hash_to_height, + main_headers, + successors, + total_work, + }) + } + + pub fn try_get_accumulator( + &self, + rotxn: &RoTxn, + block_hash: BlockHash, + ) -> Result, Error> { + if block_hash == BlockHash::default() { + Ok(Some(Accumulator::default())) + } else { + let accumulator = self.accumulators.get(rotxn, &block_hash)?; + Ok(accumulator) + } + } + + pub fn get_accumulator( + &self, + rotxn: &RoTxn, + block_hash: BlockHash, + ) -> Result { + self.try_get_accumulator(rotxn, block_hash)? + .ok_or(Error::NoAccumulator(block_hash)) + } + + pub fn try_get_height( + &self, + rotxn: &RoTxn, + block_hash: BlockHash, + ) -> Result, Error> { + if block_hash == BlockHash::default() { + Ok(Some(0)) + } else { + self.block_hash_to_height + .get(rotxn, &block_hash) + .map_err(Error::from) + } + } + + pub fn get_height( + &self, + rotxn: &RoTxn, + block_hash: BlockHash, + ) -> Result { + self.try_get_height(rotxn, block_hash)? + .ok_or(Error::NoHeight(block_hash)) + } + + pub fn try_get_bmm_verification( + &self, + rotxn: &RoTxn, + block_hash: BlockHash, + ) -> Result, Error> { + if block_hash == BlockHash::default() { + Ok(Some(true)) + } else { + self.bmm_verifications + .get(rotxn, &block_hash) + .map_err(Error::from) + } + } + + pub fn get_bmm_verification( + &self, + rotxn: &RoTxn, + block_hash: BlockHash, + ) -> Result { + self.try_get_bmm_verification(rotxn, block_hash)? + .ok_or(Error::NoBmmVerification(block_hash)) + } + + pub fn try_get_body( + &self, + rotxn: &RoTxn, + block_hash: BlockHash, + ) -> Result, Error> { + let body = self.bodies.get(rotxn, &block_hash)?; + Ok(body) + } + + pub fn get_body( + &self, + rotxn: &RoTxn, + block_hash: BlockHash, + ) -> Result { + self.try_get_body(rotxn, block_hash)? + .ok_or(Error::NoBody(block_hash)) + } + + pub fn try_get_deposits( + &self, + rotxn: &RoTxn, + block_hash: bitcoin::BlockHash, + ) -> Result>, Error> { + let deposits = self.deposits.get(rotxn, &block_hash)?; + Ok(deposits) + } + + pub fn get_deposits( + &self, + rotxn: &RoTxn, + block_hash: bitcoin::BlockHash, + ) -> Result, Error> { + self.try_get_deposits(rotxn, block_hash)? + .ok_or(Error::NoDepositsInfo(block_hash)) + } + + pub fn try_get_header( + &self, + rotxn: &RoTxn, + block_hash: BlockHash, + ) -> Result, Error> { + let header = self.headers.get(rotxn, &block_hash)?; + Ok(header) + } + + pub fn get_header( + &self, + rotxn: &RoTxn, + block_hash: BlockHash, + ) -> Result { + self.try_get_header(rotxn, block_hash)? + .ok_or(Error::NoHeader(block_hash)) + } + + pub fn try_get_main_height( + &self, + rotxn: &RoTxn, + block_hash: bitcoin::BlockHash, + ) -> Result, Error> { + if block_hash == bitcoin::BlockHash::all_zeros() { + Ok(Some(0)) + } else { + self.main_block_hash_to_height + .get(rotxn, &block_hash) + .map_err(Error::from) + } + } + + pub fn get_main_height( + &self, + rotxn: &RoTxn, + block_hash: bitcoin::BlockHash, + ) -> Result { + self.try_get_main_height(rotxn, block_hash)? + .ok_or(Error::NoMainHeight(block_hash)) + } + + pub fn try_get_main_header( + &self, + rotxn: &RoTxn, + block_hash: bitcoin::BlockHash, + ) -> Result, Error> { + let header = self.main_headers.get(rotxn, &block_hash)?; + Ok(header) + } + + fn get_main_header( + &self, + rotxn: &RoTxn, + block_hash: bitcoin::BlockHash, + ) -> Result { + self.try_get_main_header(rotxn, block_hash)? + .ok_or(Error::NoMainHeader(block_hash)) + } + + pub fn try_get_successors( + &self, + rotxn: &RoTxn, + block_hash: BlockHash, + ) -> Result>, Error> { + let successors = self.successors.get(rotxn, &block_hash)?; + Ok(successors) + } + + pub fn get_successors( + &self, + rotxn: &RoTxn, + block_hash: BlockHash, + ) -> Result, Error> { + self.try_get_successors(rotxn, block_hash)? + .ok_or(Error::NoBlockHash(block_hash)) + } + + pub fn try_get_total_work( + &self, + rotxn: &RoTxn, + block_hash: bitcoin::BlockHash, + ) -> Result, Error> { + let total_work = self.total_work.get(rotxn, &block_hash)?; + Ok(total_work) + } + + pub fn get_total_work( + &self, + rotxn: &RoTxn, + block_hash: bitcoin::BlockHash, + ) -> Result { + self.try_get_total_work(rotxn, block_hash)? + .ok_or(Error::NoMainHeader(block_hash)) + } + + pub fn get_nth_ancestor( + &self, + rotxn: &RoTxn, + mut block_hash: BlockHash, + mut n: u32, + ) -> Result { + let orig_block_hash = block_hash; + let orig_n = n; + while n > 0 { + let height = self.get_height(rotxn, block_hash)?; + if n > height { + return Err(Error::NoAncestor { + block_hash: orig_block_hash, + depth: orig_n, + }); + } + if n == 1 { + let parent = self.get_header(rotxn, block_hash)?.prev_side_hash; + return Ok(parent); + } else { + let exp_ancestor_index = u32::ilog2(n) - 1; + block_hash = self + .exponential_ancestors + .get(rotxn, &block_hash)? + .unwrap()[exp_ancestor_index as usize]; + n -= 2 << exp_ancestor_index; + } + } + Ok(block_hash) + } + + pub fn get_nth_main_ancestor( + &self, + rotxn: &RoTxn, + mut block_hash: bitcoin::BlockHash, + mut n: u32, + ) -> Result { + let orig_block_hash = block_hash; + let orig_n = n; + while n > 0 { + let height = self.get_main_height(rotxn, block_hash)?; + if n > height { + return Err(Error::NoMainAncestor { + block_hash: orig_block_hash, + depth: orig_n, + }); + } + if n == 1 { + let parent = + self.get_main_header(rotxn, block_hash)?.prev_blockhash; + return Ok(parent); + } else { + let exp_ancestor_index = u32::ilog2(n) - 1; + block_hash = self + .exponential_main_ancestors + .get(rotxn, &block_hash)? + .unwrap()[exp_ancestor_index as usize]; + n -= 2 << exp_ancestor_index; + } + } + Ok(block_hash) + } + + /// Returns true if the second specified block is a descendant of the first + /// specified block + /// Returns an error if either of the specified block headers do not exist + /// in the archive. + pub fn is_descendant( + &self, + rotxn: &RoTxn, + ancestor: BlockHash, + descendant: BlockHash, + ) -> Result { + if ancestor == descendant { + return Ok(true); + } + let ancestor_height = self.get_height(rotxn, ancestor)?; + let descendant_height = self.get_height(rotxn, descendant)?; + if ancestor_height > descendant_height { + return Ok(false); + } + let res = ancestor + == self.get_nth_ancestor( + rotxn, + descendant, + descendant_height - ancestor_height, + )?; + Ok(res) + } + + /// Returns true if the second specified mainchain block is a descendant of + /// the first specified block. + /// Returns an error if either of the specified block headers do not exist + /// in the archive. + pub fn is_main_descendant( + &self, + rotxn: &RoTxn, + ancestor: bitcoin::BlockHash, + descendant: bitcoin::BlockHash, + ) -> Result { + if ancestor == descendant { + return Ok(true); + } + let ancestor_height = self.get_main_height(rotxn, ancestor)?; + let descendant_height = self.get_main_height(rotxn, descendant)?; + if ancestor_height > descendant_height { + return Ok(false); + } + let res = ancestor + == self.get_nth_main_ancestor( + rotxn, + descendant, + descendant_height - ancestor_height, + )?; + Ok(res) + } + + /// Store a block body. The header must already exist. + pub fn put_accumulator( + &self, + rwtxn: &mut RwTxn, + block_hash: BlockHash, + accumulator: &Accumulator, + ) -> Result<(), Error> { + self.accumulators.put(rwtxn, &block_hash, accumulator)?; + Ok(()) + } + + /// Store a BMM verification result + pub fn put_bmm_verification( + &self, + rwtxn: &mut RwTxn, + block_hash: BlockHash, + verification_result: bool, + ) -> Result<(), Error> { + self.bmm_verifications + .put(rwtxn, &block_hash, &verification_result)?; + Ok(()) + } + + /// Store a block body. The header must already exist. + pub fn put_body( + &self, + rwtxn: &mut RwTxn, + block_hash: BlockHash, + body: &Body, + ) -> Result<(), Error> { + let header = self.get_header(rwtxn, block_hash)?; + if header.merkle_root != body.compute_merkle_root() { + return Err(Error::InvalidMerkleRoot); + } + self.bodies.put(rwtxn, &block_hash, body)?; + Ok(()) + } + + /// Store deposit info for a block + pub fn put_deposits( + &self, + rwtxn: &mut RwTxn, + block_hash: bitcoin::BlockHash, + mut deposits: Vec, + ) -> Result<(), Error> { + deposits.sort_by_key(|deposit| deposit.tx_index); + if !deposits + .iter() + .all(|deposit| deposit.block_hash == block_hash) + { + return Err(Error::DepositInvalidMainBlockHash); + }; + self.deposits.put(rwtxn, &block_hash, &deposits)?; + Ok(()) + } + + pub fn put_header( + &self, + rwtxn: &mut RwTxn, + header: &Header, + ) -> Result<(), Error> { + let Some(prev_height) = + self.try_get_height(rwtxn, header.prev_side_hash)? + else { + return Err(Error::InvalidPrevSideHash); + }; + let height = prev_height + 1; + let block_hash = header.hash(); + self.block_hash_to_height.put(rwtxn, &block_hash, &height)?; + self.headers.put(rwtxn, &block_hash, header)?; + // Add to successors for predecessor + { + let mut pred_successors = + self.get_successors(rwtxn, header.prev_side_hash)?; + pred_successors.insert(block_hash); + self.successors.put( + rwtxn, + &header.prev_side_hash, + &pred_successors, + )?; + } + self.successors.put(rwtxn, &block_hash, &HashSet::new())?; + // populate exponential ancestors + let mut exponential_ancestors = Vec::::new(); + if height >= 2 { + let grandparent = + self.get_nth_ancestor(rwtxn, header.prev_side_hash, 1)?; + exponential_ancestors.push(grandparent); + let mut next_exponential_ancestor_depth = 4u64; + while height as u64 >= next_exponential_ancestor_depth { + let next_exponential_ancestor = self.get_nth_ancestor( + rwtxn, + *exponential_ancestors.last().unwrap(), + next_exponential_ancestor_depth as u32 / 2, + )?; + exponential_ancestors.push(next_exponential_ancestor); + next_exponential_ancestor_depth *= 2; + } + } + self.exponential_ancestors.put( + rwtxn, + &block_hash, + &exponential_ancestors, + )?; + Ok(()) + } + + pub fn put_main_header( + &self, + rwtxn: &mut RwTxn, + header: &BitcoinHeader, + ) -> Result<(), Error> { + if self + .try_get_main_header(rwtxn, header.prev_blockhash)? + .is_none() + && header.prev_blockhash != bitcoin::BlockHash::all_zeros() + { + return Err(Error::NoMainHeader(header.prev_blockhash)); + } + let block_hash = header.hash; + let prev_height = self.get_main_height(rwtxn, header.prev_blockhash)?; + let height = prev_height + 1; + let total_work = + if header.prev_blockhash != bitcoin::BlockHash::all_zeros() { + let prev_work = + self.get_total_work(rwtxn, header.prev_blockhash)?; + prev_work + header.work() + } else { + header.work() + }; + self.main_block_hash_to_height + .put(rwtxn, &block_hash, &height)?; + self.main_headers.put(rwtxn, &block_hash, header)?; + self.total_work.put(rwtxn, &block_hash, &total_work)?; + // populate exponential ancestors + let mut exponential_ancestors = Vec::::new(); + if height >= 2 { + let grandparent = + self.get_nth_main_ancestor(rwtxn, header.prev_blockhash, 1)?; + exponential_ancestors.push(grandparent); + let mut next_exponential_ancestor_depth = 4u64; + while height as u64 >= next_exponential_ancestor_depth { + let next_exponential_ancestor = self.get_nth_main_ancestor( + rwtxn, + *exponential_ancestors.last().unwrap(), + next_exponential_ancestor_depth as u32 / 2, + )?; + exponential_ancestors.push(next_exponential_ancestor); + next_exponential_ancestor_depth *= 2; + } + } + self.exponential_main_ancestors.put( + rwtxn, + &block_hash, + &exponential_ancestors, + )?; + Ok(()) + } + + /// Return a fallible iterator over ancestors of a block, + /// starting with the specified block's header + pub fn ancestors<'a, 'rotxn>( + &'a self, + rotxn: &'a RoTxn<'rotxn>, + block_hash: BlockHash, + ) -> Ancestors<'a, 'rotxn> where { + Ancestors { + archive: self, + rotxn, + block_hash, + } + } + + /// Get missing bodies in the ancestry of the specified block, up to the + /// specified ancestor. + /// The specified ancestor must exist. + /// Blocks for which bodies are missing are returned oldest-to-newest. + pub fn get_missing_bodies( + &self, + rotxn: &RoTxn, + block_hash: BlockHash, + ancestor: BlockHash, + ) -> Result, Error> { + // TODO: check that ancestor is nth ancestor of block_hash + let mut res: Vec = self + .ancestors(rotxn, block_hash) + .take_while(|block_hash| Ok(*block_hash != ancestor)) + .filter_map(|block_hash| { + match self.try_get_body(rotxn, block_hash)? { + Some(_) => Ok(None), + None => Ok(Some(block_hash)), + } + }) + .collect()?; + res.reverse(); + Ok(res) + } + + /// Return a fallible iterator over ancestors of a mainchain block, + /// starting with the specified block's header + pub fn main_ancestors<'a>( + &'a self, + rotxn: &'a RoTxn, + mut block_hash: bitcoin::BlockHash, + ) -> impl FallibleIterator + 'a + { + fallible_iterator::from_fn(move || { + if block_hash == bitcoin::BlockHash::all_zeros() { + Ok(None) + } else { + let res = Some(block_hash); + let header = self.get_main_header(rotxn, block_hash)?; + block_hash = header.prev_blockhash; + Ok(res) + } + }) + } + + /// Find the last common ancestor of two blocks, if headers for both exist + pub fn last_common_ancestor( + &self, + rotxn: &RoTxn, + mut block_hash0: BlockHash, + mut block_hash1: BlockHash, + ) -> Result { + let mut height0 = self.get_height(rotxn, block_hash0)?; + let height1 = self.get_height(rotxn, block_hash1)?; + // Equalize heights to min(height0, height1) + match height0.cmp(&height1) { + Ordering::Equal => (), + Ordering::Less => { + block_hash1 = self.get_nth_ancestor( + rotxn, + block_hash1, + height1 - height0, + )?; + } + Ordering::Greater => { + block_hash0 = self.get_nth_ancestor( + rotxn, + block_hash0, + height0 - height1, + )?; + height0 = height1; + } + } + // if the block hashes are the same, return early + if block_hash0 == block_hash1 { + return Ok(block_hash0); + } + // use a binary search to find the last common ancestor + let mut lo_depth = 1; + let mut hi_depth = height0; + while lo_depth < hi_depth { + let mid_depth = (lo_depth + hi_depth) / 2; + let mid_ancestor0 = + self.get_nth_ancestor(rotxn, block_hash0, mid_depth)?; + let mid_ancestor1 = + self.get_nth_ancestor(rotxn, block_hash1, mid_depth)?; + if mid_ancestor0 == mid_ancestor1 { + hi_depth = mid_depth; + } else { + lo_depth = mid_depth + 1; + } + } + self.get_nth_ancestor(rotxn, block_hash0, hi_depth) + } + + /// Find the last common mainchain ancestor of two blocks, + /// if headers for both exist + pub fn last_common_main_ancestor( + &self, + rotxn: &RoTxn, + mut block_hash0: bitcoin::BlockHash, + mut block_hash1: bitcoin::BlockHash, + ) -> Result { + let mut height0 = self.get_main_height(rotxn, block_hash0)?; + let height1 = self.get_main_height(rotxn, block_hash1)?; + // Equalize heights to min(height0, height1) + match height0.cmp(&height1) { + Ordering::Equal => (), + Ordering::Less => { + block_hash1 = self.get_nth_main_ancestor( + rotxn, + block_hash1, + height1 - height0, + )?; + } + Ordering::Greater => { + block_hash0 = self.get_nth_main_ancestor( + rotxn, + block_hash0, + height0 - height1, + )?; + height0 = height1; + } + } + // if the block hashes are the same, return early + if block_hash0 == block_hash1 { + return Ok(block_hash0); + } + // use a binary search to find the last common ancestor + let mut lo_depth = 1; + let mut hi_depth = height0; + while lo_depth < hi_depth { + let mid_depth = (lo_depth + hi_depth) / 2; + let mid_ancestor0 = + self.get_nth_main_ancestor(rotxn, block_hash0, mid_depth)?; + let mid_ancestor1 = + self.get_nth_main_ancestor(rotxn, block_hash1, mid_depth)?; + if mid_ancestor0 == mid_ancestor1 { + hi_depth = mid_depth; + } else { + lo_depth = mid_depth + 1; + } + } + self.get_nth_main_ancestor(rotxn, block_hash0, hi_depth) + } + + /// Determine if two mainchain blocks are part of a shared lineage, + /// ie. one block is a descendent of the other + pub fn shared_mainchain_lineage( + &self, + rotxn: &RoTxn, + mut block_hash0: bitcoin::BlockHash, + mut block_hash1: bitcoin::BlockHash, + ) -> Result { + let height0 = self.get_main_height(rotxn, block_hash0)?; + let height1 = self.get_main_height(rotxn, block_hash1)?; + match height0.cmp(&height1) { + Ordering::Equal => (), + Ordering::Less => { + block_hash1 = self.get_nth_main_ancestor( + rotxn, + block_hash1, + height1 - height0, + )?; + } + Ordering::Greater => { + block_hash0 = self.get_nth_main_ancestor( + rotxn, + block_hash0, + height0 - height1, + )?; + } + } + Ok(block_hash0 == block_hash1) + } + + /// Compares two potential tips and returns the better tip, if there is one. + /// It is possible that neither tip is better, eg. if the mainchain lineage + /// is not shared and the tip with greater total work had lower height before + /// the common mainchain ancestor. + /// ie. the tip with either: + /// * if the mainchain lineage is shared: + /// * greater height + /// * equal height and greater total work + /// * if the mainchain lineage is not shared: + /// * greater height AND equal work + /// * greater or equal height AND greater total work + /// * greater total work AND greater or equal height before common + /// mainchain ancestor + /// * equal height AND equal total work AND greater height before common mainchain ancestor + // TODO: Review this rule + pub fn better_tip( + &self, + rotxn: &RoTxn, + block_hash0: BlockHash, + block_hash1: BlockHash, + ) -> Result, Error> { + let height0 = self.get_height(rotxn, block_hash0)?; + let height1 = self.get_height(rotxn, block_hash1)?; + match (height0, height1) { + (0, 0) => return Ok(None), + (0, _) => return Ok(Some(block_hash1)), + (_, 0) => return Ok(Some(block_hash0)), + (_, _) => (), + } + let header0 = self.get_header(rotxn, block_hash0)?; + let header1 = self.get_header(rotxn, block_hash1)?; + if self.shared_mainchain_lineage( + rotxn, + header0.prev_main_hash, + header1.prev_main_hash, + )? { + match height0.cmp(&height1) { + Ordering::Less => Ok(Some(block_hash1)), + Ordering::Greater => Ok(Some(block_hash0)), + Ordering::Equal => { + let work0 = + self.get_total_work(rotxn, header0.prev_main_hash)?; + let work1 = + self.get_total_work(rotxn, header1.prev_main_hash)?; + match work0.cmp(&work1) { + Ordering::Less => Ok(Some(block_hash1)), + Ordering::Greater => Ok(Some(block_hash0)), + Ordering::Equal => Ok(None), + } + } + } + } else { + let work0 = self.get_total_work(rotxn, header0.prev_main_hash)?; + let work1 = self.get_total_work(rotxn, header1.prev_main_hash)?; + match (height0.cmp(&height1), work0.cmp(&work1)) { + (Ordering::Less, Ordering::Equal) => Ok(Some(block_hash1)), + (Ordering::Greater, Ordering::Equal) => Ok(Some(block_hash0)), + (Ordering::Less | Ordering::Equal, Ordering::Less) => { + Ok(Some(block_hash1)) + } + (Ordering::Greater | Ordering::Equal, Ordering::Greater) => { + Ok(Some(block_hash0)) + } + (Ordering::Less, Ordering::Greater) + | (Ordering::Greater, Ordering::Less) + | (Ordering::Equal, Ordering::Equal) => { + let common_mainchain_ancestor = self + .last_common_main_ancestor( + rotxn, + header0.prev_main_hash, + header1.prev_main_hash, + )?; + let common_mainchain_ancestor_height = + self.get_main_height(rotxn, common_mainchain_ancestor)?; + let height_before_common_mainchain_ancestor0 = self + .ancestors(rotxn, block_hash0) + .find_map(|block_hash| { + if block_hash == BlockHash::default() { + return Ok(Some(0)); + }; + let header = self.get_header(rotxn, block_hash)?; + let main_height = self.get_main_height( + rotxn, + header.prev_main_hash, + )?; + if main_height > common_mainchain_ancestor_height { + return Ok(None); + }; + let height = self.get_height(rotxn, block_hash)?; + Ok(Some(height)) + })? + .unwrap(); + let height_before_common_mainchain_ancestor1 = self + .ancestors(rotxn, block_hash1) + .find_map(|block_hash| { + if block_hash == BlockHash::default() { + return Ok(Some(0)); + }; + let header = self.get_header(rotxn, block_hash)?; + let main_height = self.get_main_height( + rotxn, + header.prev_main_hash, + )?; + if main_height > common_mainchain_ancestor_height { + return Ok(None); + }; + let height = self.get_height(rotxn, block_hash)?; + Ok(Some(height)) + })? + .unwrap(); + match ( + work0.cmp(&work1), + height_before_common_mainchain_ancestor0 + .cmp(&height_before_common_mainchain_ancestor1), + ) { + (Ordering::Less, Ordering::Less | Ordering::Equal) => { + Ok(Some(block_hash1)) + } + (Ordering::Less, Ordering::Greater) => Ok(None), + ( + Ordering::Greater, + Ordering::Greater | Ordering::Equal, + ) => Ok(Some(block_hash0)), + (Ordering::Greater, Ordering::Less) => Ok(None), + (Ordering::Equal, Ordering::Less) => { + Ok(Some(block_hash1)) + } + (Ordering::Equal, Ordering::Greater) => { + Ok(Some(block_hash0)) + } + (Ordering::Equal, Ordering::Equal) => Ok(None), + } + } + } + } + } +} + +/// Return a fallible iterator over ancestors of a block, +/// starting with the specified block. +/// created by [`Archive::ancestors`] +pub struct Ancestors<'a, 'rotxn> { + archive: &'a Archive, + rotxn: &'a RoTxn<'rotxn>, + block_hash: BlockHash, +} + +impl<'a, 'rotxn> FallibleIterator for Ancestors<'a, 'rotxn> { + type Item = BlockHash; + type Error = Error; + + fn next(&mut self) -> Result, Self::Error> { + if self.block_hash == BlockHash::default() { + Ok(None) + } else { + let res = self.block_hash; + let header = + self.archive.get_header(self.rotxn, self.block_hash)?; + self.block_hash = header.prev_side_hash; + Ok(Some(res)) + } + } +} diff --git a/lib/authorization.rs b/lib/authorization.rs new file mode 100644 index 0000000..737814f --- /dev/null +++ b/lib/authorization.rs @@ -0,0 +1,220 @@ +use borsh::BorshSerialize; +use rayon::iter::{IntoParallelRefIterator as _, ParallelIterator as _}; +use serde::{Deserialize, Serialize}; + +use crate::types::{ + Address, AuthorizedTransaction, Body, GetAddress, Transaction, Verify, +}; + +pub use ed25519_dalek::{ + Signature, SignatureError, Signer, SigningKey, Verifier, VerifyingKey, +}; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("borsh serialization error")] + BorshSerialize(#[from] borsh::io::Error), + #[error("ed25519_dalek error")] + DalekError(#[from] SignatureError), + #[error( + "wrong key for address: address = {address}, + hash(verifying_key) = {hash_verifying_key}" + )] + WrongKeyForAddress { + address: Address, + hash_verifying_key: Address, + }, +} + +fn borsh_serialize_verifying_key( + vk: &VerifyingKey, + writer: &mut W, +) -> borsh::io::Result<()> +where + W: borsh::io::Write, +{ + borsh::BorshSerialize::serialize(&vk.to_bytes(), writer) +} + +fn borsh_serialize_signature( + sig: &Signature, + writer: &mut W, +) -> borsh::io::Result<()> +where + W: borsh::io::Write, +{ + borsh::BorshSerialize::serialize(&sig.to_bytes(), writer) +} + +#[derive( + BorshSerialize, Debug, Clone, Deserialize, Eq, PartialEq, Serialize, +)] +pub struct Authorization { + #[borsh(serialize_with = "borsh_serialize_verifying_key")] + pub verifying_key: VerifyingKey, + #[borsh(serialize_with = "borsh_serialize_signature")] + pub signature: Signature, +} + +impl GetAddress for Authorization { + fn get_address(&self) -> Address { + get_address(&self.verifying_key) + } +} + +impl Verify for Authorization { + type Error = Error; + fn verify_transaction( + transaction: &AuthorizedTransaction, + ) -> Result<(), Self::Error> { + verify_authorized_transaction(transaction)?; + Ok(()) + } + + fn verify_body(body: &Body) -> Result<(), Self::Error> { + verify_authorizations(body)?; + Ok(()) + } +} + +pub fn get_address(verifying_key: &VerifyingKey) -> Address { + let mut hasher = blake3::Hasher::new(); + let mut reader = hasher.update(&verifying_key.to_bytes()).finalize_xof(); + let mut output: [u8; 20] = [0; 20]; + reader.fill(&mut output); + Address(output) +} + +struct Package<'a> { + messages: Vec<&'a [u8]>, + signatures: Vec, + verifying_keys: Vec, +} + +pub fn verify_authorized_transaction( + transaction: &AuthorizedTransaction, +) -> Result<(), Error> { + let tx_bytes_canonical = borsh::to_vec(&transaction.transaction)?; + let messages: Vec<_> = std::iter::repeat(tx_bytes_canonical.as_slice()) + .take(transaction.authorizations.len()) + .collect(); + let (verifying_keys, signatures): (Vec, Vec) = + transaction + .authorizations + .iter() + .map( + |Authorization { + verifying_key, + signature, + }| (verifying_key, signature), + ) + .unzip(); + ed25519_dalek::verify_batch(&messages, &signatures, &verifying_keys)?; + Ok(()) +} + +pub fn verify_authorizations(body: &Body) -> Result<(), Error> { + let input_numbers = body + .transactions + .iter() + .map(|transaction| transaction.inputs.len()); + let serialized_transactions: Vec> = body + .transactions + .par_iter() + .map(borsh::to_vec) + .collect::>()?; + let serialized_transactions = + serialized_transactions.iter().map(Vec::as_slice); + let messages = input_numbers.zip(serialized_transactions).flat_map( + |(input_number, serialized_transaction)| { + std::iter::repeat(serialized_transaction).take(input_number) + }, + ); + + let pairs = body.authorizations.iter().zip(messages).collect::>(); + + let num_threads = rayon::current_num_threads(); + let num_authorizations = body.authorizations.len(); + let package_size = num_authorizations / num_threads; + let mut packages: Vec = Vec::with_capacity(num_threads); + for i in 0..num_threads { + let mut package = Package { + messages: Vec::with_capacity(package_size), + signatures: Vec::with_capacity(package_size), + verifying_keys: Vec::with_capacity(package_size), + }; + for (authorization, message) in + &pairs[i * package_size..(i + 1) * package_size] + { + package.messages.push(*message); + package.signatures.push(authorization.signature); + package.verifying_keys.push(authorization.verifying_key); + } + packages.push(package); + } + for (authorization, message) in &pairs[num_threads * package_size..] { + packages[num_threads - 1].messages.push(*message); + packages[num_threads - 1] + .signatures + .push(authorization.signature); + packages[num_threads - 1] + .verifying_keys + .push(authorization.verifying_key); + } + assert_eq!( + packages.iter().map(|p| p.signatures.len()).sum::(), + body.authorizations.len() + ); + packages + .par_iter() + .map( + |Package { + messages, + signatures, + verifying_keys, + }| { + ed25519_dalek::verify_batch( + messages, + signatures, + verifying_keys, + ) + }, + ) + .collect::>()?; + Ok(()) +} + +pub fn sign( + signing_key: &SigningKey, + transaction: &Transaction, +) -> Result { + let tx_bytes_canonical = borsh::to_vec(&transaction)?; + Ok(signing_key.sign(&tx_bytes_canonical)) +} + +pub fn authorize( + addresses_signing_keys: &[(Address, &SigningKey)], + transaction: Transaction, +) -> Result { + let mut authorizations: Vec = + Vec::with_capacity(addresses_signing_keys.len()); + let tx_bytes_canonical = borsh::to_vec(&transaction)?; + for (address, signing_key) in addresses_signing_keys { + let hash_verifying_key = get_address(&signing_key.verifying_key()); + if *address != hash_verifying_key { + return Err(Error::WrongKeyForAddress { + address: *address, + hash_verifying_key, + }); + } + let authorization = Authorization { + verifying_key: signing_key.verifying_key(), + signature: signing_key.sign(&tx_bytes_canonical), + }; + authorizations.push(authorization); + } + Ok(AuthorizedTransaction { + authorizations, + transaction, + }) +} diff --git a/lib/src/lib.rs b/lib/lib.rs similarity index 96% rename from lib/src/lib.rs rename to lib/lib.rs index fd0005a..3e5aff9 100644 --- a/lib/src/lib.rs +++ b/lib/lib.rs @@ -1,3 +1,5 @@ +#![feature(let_chains)] + pub mod archive; pub mod authorization; pub mod mempool; @@ -8,8 +10,8 @@ pub mod state; pub mod types; pub mod wallet; -pub use heed; pub use bip300301; +pub use heed; /// Format `str_dest` with the proper `s{sidechain_number}_` prefix and a /// checksum postfix for calling createsidechaindeposit on mainchain. diff --git a/lib/mempool.rs b/lib/mempool.rs new file mode 100644 index 0000000..4314151 --- /dev/null +++ b/lib/mempool.rs @@ -0,0 +1,127 @@ +use std::collections::VecDeque; + +use heed::{types::SerdeBincode, Database, RoTxn, RwTxn}; + +use crate::types::{Accumulator, AuthorizedTransaction, OutPoint, Txid}; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("heed error")] + Heed(#[from] heed::Error), + #[error("Utreexo error: {0}")] + Utreexo(String), + #[error("can't add transaction, utxo double spent")] + UtxoDoubleSpent, +} + +#[derive(Clone)] +pub struct MemPool { + pub transactions: + Database, SerdeBincode>, + pub spent_utxos: Database, SerdeBincode>, +} + +impl MemPool { + pub const NUM_DBS: u32 = 2; + + pub fn new(env: &heed::Env) -> Result { + let mut rwtxn = env.write_txn()?; + let transactions = + env.create_database(&mut rwtxn, Some("transactions"))?; + let spent_utxos = + env.create_database(&mut rwtxn, Some("spent_utxos"))?; + rwtxn.commit()?; + Ok(Self { + transactions, + spent_utxos, + }) + } + + pub fn put( + &self, + txn: &mut RwTxn, + transaction: &AuthorizedTransaction, + ) -> Result<(), Error> { + let txid = transaction.transaction.txid(); + tracing::debug!("adding transaction {txid} to mempool"); + for (outpoint, _) in &transaction.transaction.inputs { + if self.spent_utxos.get(txn, outpoint)?.is_some() { + return Err(Error::UtxoDoubleSpent); + } + self.spent_utxos.put(txn, outpoint, &txid)?; + } + self.transactions.put(txn, &txid, transaction)?; + Ok(()) + } + + pub fn delete(&self, rwtxn: &mut RwTxn, txid: Txid) -> Result<(), Error> { + let mut pending_deletes = VecDeque::from([txid]); + while let Some(txid) = pending_deletes.pop_front() { + if let Some(tx) = self.transactions.get(rwtxn, &txid)? { + for (outpoint, _) in &tx.transaction.inputs { + self.spent_utxos.delete(rwtxn, outpoint)?; + } + self.transactions.delete(rwtxn, &txid)?; + for vout in 0..tx.transaction.outputs.len() { + let outpoint = OutPoint::Regular { + txid, + vout: vout as u32, + }; + if let Some(child_txid) = + self.spent_utxos.get(rwtxn, &outpoint)? + { + pending_deletes.push_back(child_txid); + } + } + } + } + Ok(()) + } + + pub fn take( + &self, + txn: &RoTxn, + number: usize, + ) -> Result, Error> { + let mut transactions = vec![]; + for item in self.transactions.iter(txn)?.take(number) { + let (_, transaction) = item?; + transactions.push(transaction); + } + Ok(transactions) + } + + pub fn take_all( + &self, + txn: &RoTxn, + ) -> Result, Error> { + let mut transactions = vec![]; + for item in self.transactions.iter(txn)? { + let (_, transaction) = item?; + transactions.push(transaction); + } + Ok(transactions) + } + + /// regenerate utreexo proofs for all txs in the mempool + pub fn regenerate_proofs( + &self, + rwtxn: &mut RwTxn, + accumulator: &Accumulator, + ) -> Result<(), Error> { + let mut iter = self.transactions.iter_mut(rwtxn)?; + while let Some(tx) = iter.next() { + let (txid, mut tx) = tx?; + let targets: Vec<_> = tx + .transaction + .inputs + .iter() + .map(|(_, utxo_hash)| utxo_hash.into()) + .collect(); + tx.transaction.proof = + accumulator.0.prove(&targets).map_err(Error::Utreexo)?; + unsafe { iter.put_current(&txid, &tx) }?; + } + Ok(()) + } +} diff --git a/lib/src/miner.rs b/lib/miner.rs similarity index 56% rename from lib/src/miner.rs rename to lib/miner.rs index 993b7c5..0c8b521 100644 --- a/lib/src/miner.rs +++ b/lib/miner.rs @@ -1,12 +1,22 @@ -use crate::types::*; -use bip300301::bitcoin; -use bip300301::Drivechain; -use bitcoin::hashes::Hash as _; -use std::net::SocketAddr; -use std::str::FromStr as _; +use std::{net::SocketAddr, str::FromStr as _, time::Duration}; + +use bip300301::{ + bitcoin::{self, hashes::Hash as _}, + Drivechain, +}; + +use crate::types::{Body, Header}; pub use bip300301::MainClient; +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("drivechain error")] + Drivechain(#[from] bip300301::Error), + #[error("invalid json: {json}")] + InvalidJson { json: serde_json::Value }, +} + #[derive(Clone)] pub struct Miner { pub drivechain: Drivechain, @@ -21,7 +31,8 @@ impl Miner { user: &str, password: &str, ) -> Result { - let drivechain = Drivechain::new(sidechain_number, main_addr, user, password)?; + let drivechain = + Drivechain::new(sidechain_number, main_addr, user, password)?; Ok(Self { drivechain, sidechain_number, @@ -44,45 +55,54 @@ impl Miner { height: u32, header: Header, body: Body, - ) -> Result<(), Error> { + ) -> Result { let str_hash_prev = header.prev_main_hash.to_string(); let critical_hash: [u8; 32] = header.hash().into(); let critical_hash = bitcoin::BlockHash::from_byte_array(critical_hash); + let amount = bitcoin::Amount::from_sat(amount); + let prev_bytes = &str_hash_prev[str_hash_prev.len() - 8..]; let value = self .drivechain .client .createbmmcriticaldatatx( - bitcoin::Amount::from_sat(amount).into(), + amount.into(), height, &critical_hash, self.sidechain_number, - &str_hash_prev[str_hash_prev.len() - 8..], + prev_bytes, ) .await .map_err(bip300301::Error::from)?; - bitcoin::Txid::from_str(value["txid"]["txid"].as_str().ok_or(Error::InvalidJson)?) - .map_err(bip300301::Error::from)?; + let txid = value["txid"]["txid"] + .as_str() + .map(|s| s.to_owned()) + .ok_or(Error::InvalidJson { json: value })?; + let txid = + bitcoin::Txid::from_str(&txid).map_err(bip300301::Error::from)?; + tracing::info!("created BMM tx: {txid}"); assert_eq!(header.merkle_root, body.compute_merkle_root()); self.block = Some((header, body)); - Ok(()) + Ok(txid) } - pub async fn confirm_bmm(&mut self) -> Result, Error> { + pub async fn confirm_bmm( + &mut self, + ) -> Result, Error> { + const VERIFY_BMM_POLL_INTERVAL: Duration = Duration::from_secs(15); if let Some((header, body)) = self.block.clone() { let block_hash = header.hash().into(); + tracing::trace!(%block_hash, "verifying bmm..."); self.drivechain - .verify_bmm(&header.prev_main_hash, &block_hash) + .verify_bmm( + &header.prev_main_hash, + &block_hash, + VERIFY_BMM_POLL_INTERVAL, + ) .await?; + tracing::trace!(%block_hash, "verified bmm"); self.block = None; return Ok(Some((header, body))); } Ok(None) } } -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error("drivechain error")] - Drivechain(#[from] bip300301::Error), - #[error("invalid jaon")] - InvalidJson, -} diff --git a/lib/net/mod.rs b/lib/net/mod.rs new file mode 100644 index 0000000..2e506e6 --- /dev/null +++ b/lib/net/mod.rs @@ -0,0 +1,351 @@ +use std::{ + collections::{hash_map, HashMap, HashSet}, + net::SocketAddr, + sync::Arc, +}; + +use fallible_iterator::{FallibleIterator, IteratorExt}; +use futures::{channel::mpsc, StreamExt}; +use heed::{ + types::{SerdeBincode, Unit}, + Database, +}; +use parking_lot::RwLock; +use quinn::{ClientConfig, Endpoint, ServerConfig}; +use tokio_stream::StreamNotifyClose; + +use crate::{archive::Archive, state::State, types::AuthorizedTransaction}; + +mod peer; + +use peer::{ + Connection, ConnectionContext as PeerConnectionCtxt, + ConnectionHandle as PeerConnectionHandle, +}; +pub use peer::{ + ConnectionError as PeerConnectionError, Info as PeerConnectionInfo, + Request as PeerRequest, Response as PeerResponse, +}; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("accept error")] + AcceptError, + #[error("address parse error")] + AddrParse(#[from] std::net::AddrParseError), + #[error("already connected to peer at {0}")] + AlreadyConnected(SocketAddr), + #[error("bincode error")] + Bincode(#[from] bincode::Error), + #[error("connect error")] + Connect(#[from] quinn::ConnectError), + #[error("connection error")] + Connection(#[from] quinn::ConnectionError), + #[error("heed error")] + Heed(#[from] heed::Error), + #[error("quinn error")] + Io(#[from] std::io::Error), + #[error("peer connection")] + PeerConnection(#[from] PeerConnectionError), + #[error("quinn rustls error")] + QuinnRustls(#[from] quinn::crypto::rustls::Error), + #[error("rcgen")] + RcGen(#[from] rcgen::RcgenError), + #[error("read to end error")] + ReadToEnd(#[from] quinn::ReadToEndError), + #[error("send datagram error")] + SendDatagram(#[from] quinn::SendDatagramError), + #[error("server endpoint closed")] + ServerEndpointClosed, + #[error("write error")] + Write(#[from] quinn::WriteError), +} + +pub fn make_client_endpoint(bind_addr: SocketAddr) -> Result { + let client_cfg = configure_client(); + let mut endpoint = Endpoint::client(bind_addr)?; + endpoint.set_default_client_config(client_cfg); + Ok(endpoint) +} + +// None indicates that the stream has ended +pub type PeerInfoRx = + mpsc::UnboundedReceiver<(SocketAddr, Option)>; + +// State. +// Archive. + +// Keep track of peer state +// Exchange metadata +// Bulk download +// Propagation +// +// Initial block download +// +// 1. Download headers +// 2. Download blocks +// 3. Update the state +#[derive(Clone)] +pub struct Net { + pub client: Endpoint, + pub server: Endpoint, + archive: Archive, + state: State, + active_peers: Arc>>, + // None indicates that the stream has ended + peer_info_tx: + mpsc::UnboundedSender<(SocketAddr, Option)>, + known_peers: Database, Unit>, +} + +impl Net { + pub const NUM_DBS: u32 = 1; + + fn add_active_peer( + &self, + addr: SocketAddr, + peer_connection_handle: PeerConnectionHandle, + ) -> Result<(), Error> { + let mut active_peers_write = self.active_peers.write(); + match active_peers_write.entry(addr) { + hash_map::Entry::Occupied(_) => Err(Error::AlreadyConnected(addr)), + hash_map::Entry::Vacant(active_peer_entry) => { + active_peer_entry.insert(peer_connection_handle); + Ok(()) + } + } + } + + pub fn remove_active_peer(&self, addr: SocketAddr) { + let mut active_peers_write = self.active_peers.write(); + if let Some(peer_connection) = active_peers_write.remove(&addr) { + drop(peer_connection); + tracing::info!("Disconnected from peer at {addr}") + } + } + + pub fn connect_peer( + &self, + env: heed::Env, + addr: SocketAddr, + ) -> Result<(), Error> { + if self.active_peers.read().contains_key(&addr) { + return Err(Error::AlreadyConnected(addr)); + } + let mut rwtxn = env.write_txn()?; + self.known_peers.put(&mut rwtxn, &addr, &())?; + rwtxn.commit()?; + let connection_ctxt = PeerConnectionCtxt { + env, + archive: self.archive.clone(), + state: self.state.clone(), + }; + let (connection_handle, info_rx) = + peer::connect(self.client.clone(), addr, connection_ctxt); + tokio::spawn({ + let info_rx = StreamNotifyClose::new(info_rx) + .map(move |info| Ok((addr, info))); + let peer_info_tx = self.peer_info_tx.clone(); + async move { + if let Err(_send_err) = info_rx.forward(peer_info_tx).await { + tracing::error!(%addr, "Failed to send peer connection info"); + } + } + }); + self.add_active_peer(addr, connection_handle)?; + Ok(()) + } + + pub fn new( + env: &heed::Env, + archive: Archive, + state: State, + bind_addr: SocketAddr, + ) -> Result<(Self, PeerInfoRx), Error> { + let (server, _) = make_server_endpoint(bind_addr)?; + let client = make_client_endpoint("0.0.0.0:0".parse()?)?; + let active_peers = Arc::new(RwLock::new(HashMap::new())); + let mut rwtxn = env.write_txn()?; + let known_peers = + env.create_database(&mut rwtxn, Some("known_peers"))?; + rwtxn.commit()?; + let (peer_info_tx, peer_info_rx) = mpsc::unbounded(); + let net = Net { + server, + client, + archive, + state, + active_peers, + peer_info_tx, + known_peers, + }; + #[allow(clippy::let_and_return)] + let known_peers: Vec<_> = { + let rotxn = env.read_txn()?; + let known_peers = net + .known_peers + .iter(&rotxn)? + .transpose_into_fallible() + .collect()?; + known_peers + }; + let () = known_peers.into_iter().try_for_each(|(peer_addr, _)| { + net.connect_peer(env.clone(), peer_addr) + })?; + Ok((net, peer_info_rx)) + } + + /// Accept the next incoming connection + pub async fn accept_incoming(&self, env: heed::Env) -> Result<(), Error> { + let connection = match self.server.accept().await { + Some(conn) => Connection(conn.await?), + None => return Err(Error::ServerEndpointClosed), + }; + let addr = connection.addr(); + if self.active_peers.read().contains_key(&addr) { + tracing::info!( + "already connected to {addr}, refusing duplicate connection", + ); + connection + .0 + .close(quinn::VarInt::from_u32(1), b"already connected"); + } + if connection.0.close_reason().is_some() { + return Ok(()); + } + tracing::info!("connected to peer at {addr}"); + let mut rwtxn = env.write_txn()?; + self.known_peers.put(&mut rwtxn, &addr, &())?; + rwtxn.commit()?; + let connection_ctxt = PeerConnectionCtxt { + env, + archive: self.archive.clone(), + state: self.state.clone(), + }; + let (connection_handle, info_rx) = + peer::handle(connection_ctxt, connection); + tokio::spawn({ + let info_rx = StreamNotifyClose::new(info_rx) + .map(move |info| Ok((addr, info))); + let peer_info_tx = self.peer_info_tx.clone(); + async move { + if let Err(_send_err) = info_rx.forward(peer_info_tx).await { + tracing::error!(%addr, "Failed to send peer connection info"); + } + } + }); + self.add_active_peer(addr, connection_handle)?; + Ok(()) + } + + // Push a request to the specified peers + pub fn push_request( + &self, + request: PeerRequest, + peers: &HashSet, + ) { + let active_peers_read = self.active_peers.read(); + for addr in peers { + let Some(peer_connection_handle) = active_peers_read.get(addr) + else { + continue; + }; + if let Err(_send_err) = peer_connection_handle + .forward_request_tx + .unbounded_send(request.clone()) + { + tracing::warn!( + "Failed to push request to peer at {addr}: {request:?}" + ) + } + } + } + + /// Push a tx to all active peers, except those in the provided set + pub fn push_tx( + &self, + exclude: HashSet, + tx: AuthorizedTransaction, + ) { + self.active_peers + .read() + .iter() + .filter(|(addr, _)| !exclude.contains(addr)) + .for_each(|(addr, peer_connection_handle)| { + if let Err(_send_err) = peer_connection_handle + .forward_request_tx + .unbounded_send(PeerRequest::PushTransaction { + transaction: tx.clone(), + }) + { + let txid = tx.transaction.txid(); + tracing::warn!("Failed to push tx {txid} to peer at {addr}") + } + }) + } +} + +/// Constructs a QUIC endpoint configured to listen for incoming connections on a certain address +/// and port. +/// +/// ## Returns +/// +/// - a stream of incoming QUIC connections +/// - server certificate serialized into DER format +#[allow(unused)] +pub fn make_server_endpoint( + bind_addr: SocketAddr, +) -> Result<(Endpoint, Vec), Error> { + let (server_config, server_cert) = configure_server()?; + let endpoint = Endpoint::server(server_config, bind_addr)?; + Ok((endpoint, server_cert)) +} + +/// Returns default server configuration along with its certificate. +fn configure_server() -> Result<(ServerConfig, Vec), Error> { + let cert = rcgen::generate_simple_self_signed(vec!["localhost".into()])?; + let cert_der = cert.serialize_der()?; + let priv_key = cert.serialize_private_key_der(); + let priv_key = rustls::PrivateKey(priv_key); + let cert_chain = vec![rustls::Certificate(cert_der.clone())]; + + let mut server_config = + ServerConfig::with_single_cert(cert_chain, priv_key)?; + let transport_config = Arc::get_mut(&mut server_config.transport).unwrap(); + transport_config.max_concurrent_uni_streams(1_u8.into()); + + Ok((server_config, cert_der)) +} + +/// Dummy certificate verifier that treats any certificate as valid. +/// NOTE, such verification is vulnerable to MITM attacks, but convenient for testing. +struct SkipServerVerification; + +impl SkipServerVerification { + fn new() -> Arc { + Arc::new(Self) + } +} + +impl rustls::client::ServerCertVerifier for SkipServerVerification { + fn verify_server_cert( + &self, + _end_entity: &rustls::Certificate, + _intermediates: &[rustls::Certificate], + _server_name: &rustls::ServerName, + _scts: &mut dyn Iterator, + _ocsp_response: &[u8], + _now: std::time::SystemTime, + ) -> Result { + Ok(rustls::client::ServerCertVerified::assertion()) + } +} + +fn configure_client() -> ClientConfig { + let crypto = rustls::ClientConfig::builder() + .with_safe_defaults() + .with_custom_certificate_verifier(SkipServerVerification::new()) + .with_no_client_auth(); + + ClientConfig::new(Arc::new(crypto)) +} diff --git a/lib/net/peer.rs b/lib/net/peer.rs new file mode 100644 index 0000000..4e086ff --- /dev/null +++ b/lib/net/peer.rs @@ -0,0 +1,761 @@ +use std::{collections::HashSet, net::SocketAddr}; + +use bip300301::bitcoin::{self, hashes::Hash as _}; +use borsh::BorshSerialize; +use fallible_iterator::FallibleIterator; +use futures::{channel::mpsc, stream, StreamExt, TryFutureExt, TryStreamExt}; +use quinn::{Endpoint, SendStream}; +use serde::{Deserialize, Serialize}; +use thiserror::Error; +use tokio::{ + spawn, + task::{JoinHandle, JoinSet}, + time::{interval, timeout, Duration}, +}; +use tokio_stream::wrappers::IntervalStream; + +use crate::{ + archive::{self, Archive}, + state::{self, State}, + types::{hash, AuthorizedTransaction, BlockHash, Body, Hash, Header, Txid}, +}; + +#[derive(Debug, Error)] +pub enum BanReason { + #[error("BMM verification failed for block {0}")] + BmmVerificationFailed(BlockHash), + #[error("Incorrect total work for block {block_hash}: {total_work:?}")] + IncorrectTotalWork { + block_hash: BlockHash, + total_work: Option, + }, +} + +#[must_use] +#[derive(Debug, Error)] +pub enum ConnectionError { + #[error("archive error")] + Archive(#[from] archive::Error), + #[error("bincode error")] + Bincode(#[from] bincode::Error), + #[error("connect error")] + Connect(#[from] quinn::ConnectError), + #[error("connection error")] + Connection(#[from] quinn::ConnectionError), + #[error("Heartbeat timeout")] + HeartbeatTimeout, + #[error("heed error")] + Heed(#[from] heed::Error), + #[error("peer should be banned; {0}")] + PeerBan(#[from] BanReason), + #[error("read to end error")] + ReadToEnd(#[from] quinn::ReadToEndError), + #[error("send datagram error")] + SendDatagram(#[from] quinn::SendDatagramError), + #[error("send forward request error")] + SendForwardRequest, + #[error("send info error")] + SendInfo, + #[error("state error")] + State(#[from] state::Error), + #[error("write error")] + Write(#[from] quinn::WriteError), +} + +impl From> for ConnectionError { + fn from(_: mpsc::TrySendError) -> Self { + Self::SendInfo + } +} + +impl From> for ConnectionError { + fn from(_: mpsc::TrySendError) -> Self { + Self::SendForwardRequest + } +} + +fn borsh_serialize_work( + work: &bitcoin::Work, + writer: &mut W, +) -> borsh::io::Result<()> +where + W: borsh::io::Write, +{ + borsh::BorshSerialize::serialize(&work.to_le_bytes(), writer) +} + +fn borsh_serialize_option_work( + work: &Option, + writer: &mut W, +) -> borsh::io::Result<()> +where + W: borsh::io::Write, +{ + #[derive(BorshSerialize)] + struct BorshWrapper( + #[borsh(serialize_with = "borsh_serialize_work")] bitcoin::Work, + ); + borsh::BorshSerialize::serialize(&work.map(BorshWrapper), writer) +} + +#[derive(BorshSerialize, Clone, Debug, Default, Deserialize, Serialize)] +pub struct PeerState { + block_height: u32, + tip: BlockHash, + #[borsh(serialize_with = "borsh_serialize_option_work")] + total_work: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum Response { + Block { + header: Header, + body: Body, + }, + /// Headers, from start to end + Headers(Vec
), + NoBlock { + block_hash: BlockHash, + }, + NoHeader { + block_hash: BlockHash, + }, + TransactionAccepted(Txid), + TransactionRejected(Txid), +} + +#[derive(BorshSerialize, Clone, Debug, Deserialize, Serialize)] +pub enum Request { + Heartbeat(PeerState), + GetBlock { + block_hash: BlockHash, + }, + /// Request headers up to [`end`] + GetHeaders { + /// Request headers AFTER (not including) the first ancestor found in + /// the specified list, if such an ancestor exists. + start: HashSet, + end: BlockHash, + /// Height is only relevant for the requester, + /// so serialization is skipped + #[serde(skip)] + height: Option, + }, + PushTransaction { + transaction: AuthorizedTransaction, + }, +} + +#[must_use] +#[derive(Debug)] +pub enum Info { + Error(ConnectionError), + /// Need BMM verification for the specified block + NeedBmmVerification(BlockHash), + /// Need Mainchain ancestors for the specified block hash + NeedMainchainAncestors(BlockHash), + /// New tip ready (body and header exist in archive, BMM verified) + NewTipReady(BlockHash), + NewTransaction(AuthorizedTransaction), + Response(Response, Request), +} + +impl From for Info { + fn from(err: ConnectionError) -> Self { + Self::Error(err) + } +} + +impl From> for Info +where + Info: From, +{ + fn from(res: Result) -> Self { + match res { + Ok(value) => value.into(), + Err(err) => Self::Error(err), + } + } +} + +#[derive(Clone)] +pub struct Connection(pub(super) quinn::Connection); + +impl Connection { + // 100KB limit for reading requests (tx size could be ~100KB) + pub const READ_REQUEST_LIMIT: usize = 100 * 1024; + + pub const HEARTBEAT_SEND_INTERVAL: Duration = Duration::from_secs(1); + + pub const HEARTBEAT_TIMEOUT_INTERVAL: Duration = Duration::from_secs(5); + + // 10MB limit for blocks + pub const READ_BLOCK_LIMIT: usize = 10 * 1024 * 1024; + + // 1KB limit per header + pub const READ_HEADER_LIMIT: usize = 1024; + + // 256B limit per tx ack (response size is ~192) + pub const READ_TX_ACK_LIMIT: usize = 256; + + pub const fn read_response_limit(req: &Request) -> usize { + match req { + Request::GetBlock { .. } => Self::READ_BLOCK_LIMIT, + Request::GetHeaders { + height: Some(height), + .. + } => *height as usize * Self::READ_HEADER_LIMIT, + // Should have no response, so limit zero + Request::Heartbeat(_) => 0, + Request::PushTransaction { .. } => Self::READ_TX_ACK_LIMIT, + // Should never happen, so limit zero + Request::GetHeaders { height: None, .. } => 0, + } + } + + pub fn addr(&self) -> SocketAddr { + self.0.remote_address() + } + + pub async fn new( + endpoint: &Endpoint, + addr: SocketAddr, + ) -> Result { + let connection = endpoint.connect(addr, "localhost")?.await?; + tracing::info!("Connected to peer at {addr}"); + Ok(Self(connection)) + } + + async fn receive_request( + &self, + ) -> Result<(Request, SendStream), ConnectionError> { + let (tx, mut rx) = self.0.accept_bi().await?; + let request_bytes = + rx.read_to_end(Connection::READ_REQUEST_LIMIT).await?; + let request: Request = bincode::deserialize(&request_bytes)?; + Ok((request, tx)) + } + + pub async fn request( + &self, + message: &Request, + ) -> Result, ConnectionError> { + let read_response_limit = Self::read_response_limit(message); + let (mut send, mut recv) = self.0.open_bi().await?; + let message = bincode::serialize(message)?; + send.write_all(&message).await?; + send.finish().await?; + if read_response_limit > 0 { + let response_bytes = recv.read_to_end(read_response_limit).await?; + let response: Response = bincode::deserialize(&response_bytes)?; + Ok(Some(response)) + } else { + Ok(None) + } + } +} + +pub struct ConnectionContext { + pub env: heed::Env, + pub archive: Archive, + pub state: State, +} + +struct ConnectionTask { + connection: Connection, + ctxt: ConnectionContext, + info_tx: mpsc::UnboundedSender, + peer_state: Option, + /// Push a request to forward to the peer + forward_request_tx: mpsc::UnboundedSender, + /// Receive requests to forward to the peer + forward_request_rx: mpsc::UnboundedReceiver, +} + +impl ConnectionTask { + async fn send_request( + conn: &Connection, + response_tx: &mpsc::UnboundedSender<( + Result, + Request, + )>, + request: Request, + ) { + let resp = match conn.request(&request).await { + Ok(Some(resp)) => Ok(resp), + Err(err) => Err(err), + Ok(None) => return, + }; + if response_tx.unbounded_send((resp, request)).is_err() { + let addr = conn.addr(); + tracing::error!(%addr, "Failed to send response") + }; + } + + async fn send_response( + mut response_tx: SendStream, + response: Response, + ) -> Result<(), ConnectionError> { + let response_bytes = bincode::serialize(&response)?; + response_tx + .write_all(&response_bytes) + .await + .map_err(ConnectionError::from) + } + + /// If a new tip is announced with greater height than the current tip: + /// * If the header does not exist, request it + /// * Verify height of the new tip. + /// * If the previous mainchain header does not exist, request it + /// * Verify PoW + /// * Verify BMM + /// * If ancestor bodies do not exist, request them + /// * Attempt to apply the new tip + async fn handle_heartbeat( + ctxt: &ConnectionContext, + info_tx: &mpsc::UnboundedSender, + forward_request_tx: &mpsc::UnboundedSender, + peer_state: &PeerState, + ) -> Result<(), ConnectionError> { + let (tip, tip_height, total_work) = { + let rotxn = ctxt.env.read_txn()?; + let tip = ctxt.state.get_tip(&rotxn)?; + let tip_height = ctxt.state.get_height(&rotxn)?; + let total_work = match ctxt.archive.try_get_header(&rotxn, tip)? { + None => None, + Some(header) + if header.prev_main_hash + == bitcoin::BlockHash::all_zeros() => + { + None + } + Some(header) => Some( + ctxt.archive + .get_total_work(&rotxn, header.prev_main_hash)?, + ), + }; + (tip, tip_height, total_work) + }; + let peer_height = peer_state.block_height; + if peer_height > tip_height + || (peer_height == tip_height && peer_state.total_work > total_work) + { + let header = { + let rotxn = ctxt.env.read_txn()?; + ctxt.archive.try_get_header(&rotxn, peer_state.tip)? + }; + let Some(header) = header else { + // Request headers + let request = Request::GetHeaders { + // TODO: provide alternative start points + start: HashSet::new(), + end: peer_state.tip, + height: Some(peer_state.block_height), + }; + forward_request_tx.unbounded_send(request)?; + return Ok(()); + }; + // Check mainchain headers + let prev_main_header = { + let rotxn = ctxt.env.read_txn()?; + ctxt.archive + .try_get_main_header(&rotxn, header.prev_main_hash)? + }; + let Some(_prev_main_header) = prev_main_header else { + let info = Info::NeedMainchainAncestors(header.hash()); + info_tx.unbounded_send(info)?; + return Ok(()); + }; + // Check PoW + let prev_main_total_work = { + let rotxn = ctxt.env.read_txn()?; + ctxt.archive.get_total_work(&rotxn, header.prev_main_hash)? + }; + if Some(prev_main_total_work) != peer_state.total_work { + let ban_reason = BanReason::IncorrectTotalWork { + block_hash: peer_state.tip, + total_work: peer_state.total_work, + }; + return Err(ConnectionError::PeerBan(ban_reason)); + } + // Verify BMM + { + let rotxn = ctxt.env.read_txn()?; + match ctxt + .archive + .try_get_bmm_verification(&rotxn, peer_state.tip)? + { + Some(true) => (), + Some(false) => { + let ban_reason = + BanReason::BmmVerificationFailed(peer_state.tip); + return Err(ConnectionError::PeerBan(ban_reason)); + } + None => { + let info = Info::NeedBmmVerification(peer_state.tip); + info_tx.unbounded_send(info)?; + return Ok(()); + } + } + }; + let missing_bodies: Vec = { + let rotxn = ctxt.env.read_txn()?; + let common_ancestor = ctxt.archive.last_common_ancestor( + &rotxn, + tip, + peer_state.tip, + )?; + ctxt.archive.get_missing_bodies( + &rotxn, + peer_state.tip, + common_ancestor, + )? + }; + if missing_bodies.is_empty() { + let info = Info::NewTipReady(peer_state.tip); + info_tx.unbounded_send(info)?; + } else { + // Request missing bodies + missing_bodies.into_iter().try_for_each(|block_hash| { + let request = Request::GetBlock { block_hash }; + forward_request_tx.unbounded_send(request) + })?; + } + } + Ok(()) + } + + async fn handle_get_block( + ctxt: &ConnectionContext, + response_tx: SendStream, + block_hash: BlockHash, + ) -> Result<(), ConnectionError> { + let (header, body) = { + let rotxn = ctxt.env.read_txn()?; + let header = ctxt.archive.try_get_header(&rotxn, block_hash)?; + let body = ctxt.archive.try_get_body(&rotxn, block_hash)?; + (header, body) + }; + let resp = match (header, body) { + (Some(header), Some(body)) => Response::Block { header, body }, + (_, _) => Response::NoBlock { block_hash }, + }; + Self::send_response(response_tx, resp).await + } + + async fn handle_get_headers( + ctxt: &ConnectionContext, + response_tx: SendStream, + mut start: HashSet, + end: BlockHash, + ) -> Result<(), ConnectionError> { + start.insert(BlockHash::default()); + let response = { + let rotxn = ctxt.env.read_txn()?; + if ctxt.archive.try_get_header(&rotxn, end)?.is_some() { + let mut headers: Vec
= ctxt + .archive + .ancestors(&rotxn, end) + .take_while(|block_hash| Ok(!start.contains(block_hash))) + .map(|block_hash| { + ctxt.archive.get_header(&rotxn, block_hash) + }) + .collect()?; + headers.reverse(); + Response::Headers(headers) + } else { + Response::NoHeader { block_hash: end } + } + }; + Self::send_response(response_tx, response).await + } + + async fn handle_push_tx( + ctxt: &ConnectionContext, + info_tx: &mpsc::UnboundedSender, + response_tx: SendStream, + tx: AuthorizedTransaction, + ) -> Result<(), ConnectionError> { + let txid = tx.transaction.txid(); + let validate_tx_result = { + let rotxn = ctxt.env.read_txn()?; + ctxt.state.validate_transaction(&rotxn, &tx) + }; + match validate_tx_result { + Err(err) => { + Self::send_response( + response_tx, + Response::TransactionRejected(txid), + ) + .await?; + Err(ConnectionError::from(err)) + } + Ok(_) => { + Self::send_response( + response_tx, + Response::TransactionAccepted(txid), + ) + .await?; + info_tx.unbounded_send(Info::NewTransaction(tx))?; + Ok(()) + } + } + } + + async fn handle_request( + ctxt: &ConnectionContext, + info_tx: &mpsc::UnboundedSender, + forward_request_tx: &mpsc::UnboundedSender, + peer_state: &mut Option, + response_tx: SendStream, + request: Request, + ) -> Result<(), ConnectionError> { + match request { + Request::Heartbeat(new_peer_state) => { + let () = Self::handle_heartbeat( + ctxt, + info_tx, + forward_request_tx, + &new_peer_state, + ) + .await?; + *peer_state = Some(new_peer_state); + Ok(()) + } + Request::GetBlock { block_hash } => { + Self::handle_get_block(ctxt, response_tx, block_hash).await + } + Request::GetHeaders { + start, + end, + height: _, + } => Self::handle_get_headers(ctxt, response_tx, start, end).await, + Request::PushTransaction { transaction } => { + Self::handle_push_tx(ctxt, info_tx, response_tx, transaction) + .await + } + } + } + + async fn run(mut self) -> Result<(), ConnectionError> { + enum MailboxItem { + ForwardRequest(Request), + /// Signals that a heartbeat message should be sent to the peer + Heartbeat, + Request((Request, SendStream)), + Response(Result, Request), + } + let forward_request_stream = self + .forward_request_rx + .map(|request| Ok(MailboxItem::ForwardRequest(request))); + let heartbeat_stream = + IntervalStream::new(interval(Connection::HEARTBEAT_SEND_INTERVAL)) + .map(|_| Ok(MailboxItem::Heartbeat)); + let request_stream = stream::try_unfold((), { + let conn = self.connection.clone(); + move |()| { + let conn = conn.clone(); + let fut = async move { + let item = timeout( + Connection::HEARTBEAT_TIMEOUT_INTERVAL, + conn.receive_request(), + ) + .map_err(|_| ConnectionError::HeartbeatTimeout) + .await??; + Result::<_, ConnectionError>::Ok(Some((item, ()))) + }; + Box::pin(fut) + } + }) + .map_ok(MailboxItem::Request); + let (response_tx, response_rx) = mpsc::unbounded(); + let response_stream = + response_rx.map(|(resp, req)| Ok(MailboxItem::Response(resp, req))); + let mut mailbox_stream = stream::select_all([ + forward_request_stream.boxed(), + heartbeat_stream.boxed(), + request_stream.boxed(), + response_stream.boxed(), + ]); + // spawn child tasks on a JoinSet so that they are dropped alongside this task + let mut task_set: JoinSet<()> = JoinSet::new(); + // Do not repeat requests + let mut pending_request_hashes = HashSet::::new(); + while let Some(mailbox_item) = mailbox_stream.try_next().await? { + match mailbox_item { + MailboxItem::ForwardRequest(request) => { + let request_hash = hash(&request); + if !pending_request_hashes.insert(request_hash) { + continue; + } + task_set.spawn({ + let connection = self.connection.clone(); + let response_tx = response_tx.clone(); + async move { + Self::send_request( + &connection, + &response_tx, + request, + ) + .await + } + }); + } + MailboxItem::Heartbeat => { + let (tip, tip_height, total_work) = { + let rotxn = self.ctxt.env.read_txn()?; + let tip = self.ctxt.state.get_tip(&rotxn)?; + let tip_height = self.ctxt.state.get_height(&rotxn)?; + let total_work = match self + .ctxt + .archive + .try_get_header(&rotxn, tip)? + { + None => None, + Some(header) + if header.prev_main_hash + == bitcoin::BlockHash::all_zeros() => + { + None + } + Some(header) => { + Some(self.ctxt.archive.get_total_work( + &rotxn, + header.prev_main_hash, + )?) + } + }; + (tip, tip_height, total_work) + }; + let heartbeat_msg = Request::Heartbeat(PeerState { + block_height: tip_height, + tip, + total_work, + }); + task_set.spawn({ + let connection = self.connection.clone(); + let response_tx = response_tx.clone(); + async move { + Self::send_request( + &connection, + &response_tx, + heartbeat_msg, + ) + .await; + } + }); + } + MailboxItem::Request((request, response_tx)) => { + let () = Self::handle_request( + &self.ctxt, + &self.info_tx, + &self.forward_request_tx, + &mut self.peer_state, + response_tx, + request, + ) + .await?; + } + MailboxItem::Response(resp, req) => { + let request_hash = hash(&req); + pending_request_hashes.remove(&request_hash); + let info = + resp.map(|resp| Info::Response(resp, req)).into(); + if self.info_tx.unbounded_send(info).is_err() { + tracing::error!("Failed to send response info") + }; + } + } + } + Ok(()) + } +} + +/// Connection killed on drop +pub struct ConnectionHandle { + task: JoinHandle<()>, + pub forward_request_tx: mpsc::UnboundedSender, +} + +impl Drop for ConnectionHandle { + fn drop(&mut self) { + self.task.abort() + } +} + +/// Handle an existing connection +pub fn handle( + ctxt: ConnectionContext, + connection: Connection, +) -> (ConnectionHandle, mpsc::UnboundedReceiver) { + let (forward_request_tx, forward_request_rx) = mpsc::unbounded(); + let (info_tx, info_rx) = mpsc::unbounded(); + let connection_task = { + let info_tx = info_tx.clone(); + let forward_request_tx = forward_request_tx.clone(); + move || async move { + let connection_task = ConnectionTask { + connection, + ctxt, + info_tx, + peer_state: None, + forward_request_tx, + forward_request_rx, + }; + connection_task.run().await + } + }; + let task = spawn(async move { + if let Err(err) = connection_task().await { + if let Err(send_error) = info_tx.unbounded_send(err.into()) + && let Info::Error(err) = send_error.into_inner() + { + tracing::warn!("Failed to send error to receiver: {err}") + } + } + }); + let connection_handle = ConnectionHandle { + task, + forward_request_tx, + }; + (connection_handle, info_rx) +} + +pub fn connect( + endpoint: Endpoint, + addr: SocketAddr, + ctxt: ConnectionContext, +) -> (ConnectionHandle, mpsc::UnboundedReceiver) { + let (forward_request_tx, forward_request_rx) = mpsc::unbounded(); + let (info_tx, info_rx) = mpsc::unbounded(); + let connection_task = { + let info_tx = info_tx.clone(); + let forward_request_tx = forward_request_tx.clone(); + move || async move { + let connection = Connection::new(&endpoint, addr).await?; + let connection_task = ConnectionTask { + connection, + ctxt, + info_tx, + peer_state: None, + forward_request_tx, + forward_request_rx, + }; + connection_task.run().await + } + }; + let task = spawn(async move { + if let Err(err) = connection_task().await { + if let Err(send_error) = info_tx.unbounded_send(err.into()) + && let Info::Error(err) = send_error.into_inner() + { + tracing::warn!("Failed to send error to receiver: {err}") + } + } + }); + let connection_handle = ConnectionHandle { + task, + forward_request_tx, + }; + (connection_handle, info_rx) +} diff --git a/lib/node/mainchain_task.rs b/lib/node/mainchain_task.rs new file mode 100644 index 0000000..71b2c98 --- /dev/null +++ b/lib/node/mainchain_task.rs @@ -0,0 +1,310 @@ +//! Task to communicate with mainchain node + +use std::sync::Arc; + +use bip300301::{ + bitcoin::{self, hashes::Hash as _}, + Drivechain, Header as BitcoinHeader, +}; +use fallible_iterator::FallibleIterator; +use futures::{ + channel::{ + mpsc::{self, UnboundedReceiver, UnboundedSender}, + oneshot, + }, + StreamExt, +}; +use thiserror::Error; +use tokio::{ + spawn, + task::{self, JoinHandle}, + time::Duration, +}; + +use crate::{ + archive::{self, Archive}, + types::BlockHash, +}; + +/// Request data from the mainchain node +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub(super) enum Request { + /// Request missing mainchain ancestor headers + AncestorHeaders(BlockHash), + /// Request recursive BMM verification + VerifyBmm(BlockHash), +} + +/// Response indicating that a request has been fulfilled successfully +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub(super) struct Response(pub Request); + +#[derive(Debug, Error)] +enum Error { + #[error("Archive error")] + Archive(#[from] archive::Error), + #[error("Drivechain error")] + Drivechain(#[from] bip300301::Error), + #[error("Heed error")] + Heed(#[from] heed::Error), + #[error("Send response error")] + SendResponse(Response), + #[error("Send response error (oneshot)")] + SendResponseOneshot(Response), +} + +struct MainchainTask { + env: heed::Env, + archive: Archive, + drivechain: Drivechain, + // receive a request, and optional oneshot sender to send the result to + // instead of sending on `response_tx` + request_rx: UnboundedReceiver<(Request, Option>)>, + response_tx: UnboundedSender, +} + +impl MainchainTask { + /// Request ancestor headers from the mainchain node, + /// including the specified header + async fn request_ancestor_headers( + env: &heed::Env, + archive: &Archive, + drivechain: &bip300301::Drivechain, + mut block_hash: bitcoin::BlockHash, + ) -> Result<(), Error> { + tracing::debug!("requesting ancestor headers for {block_hash}"); + let mut headers: Vec = Vec::new(); + loop { + if block_hash == bitcoin::BlockHash::all_zeros() { + break; + } else { + let rotxn = env.read_txn()?; + if archive.try_get_main_header(&rotxn, block_hash)?.is_some() { + break; + } + } + let header = drivechain.get_header(block_hash).await?; + block_hash = header.prev_blockhash; + headers.push(header); + } + headers.reverse(); + if headers.is_empty() { + Ok(()) + } else { + // Writing all headers during IBD can starve archive readers. + task::block_in_place(|| { + let mut rwtxn = env.write_txn()?; + headers.into_iter().try_for_each(|header| { + archive.put_main_header(&mut rwtxn, &header) + })?; + rwtxn.commit()?; + Ok(()) + }) + } + } + + /// Attempt to verify bmm for the specified block, + /// and store the verification result + async fn verify_bmm( + env: &heed::Env, + archive: &Archive, + drivechain: &bip300301::Drivechain, + block_hash: BlockHash, + ) -> Result { + use jsonrpsee::types::error::ErrorCode as JsonrpseeErrorCode; + const VERIFY_BMM_POLL_INTERVAL: Duration = Duration::from_secs(15); + let header = { + let rotxn = env.read_txn()?; + archive.get_header(&rotxn, block_hash)? + }; + let res = match drivechain + .verify_bmm( + &header.prev_main_hash, + &block_hash.into(), + VERIFY_BMM_POLL_INTERVAL, + ) + .await + { + Ok(()) => true, + Err(bip300301::Error::Jsonrpsee(jsonrpsee::core::Error::Call( + err, + ))) if JsonrpseeErrorCode::from(err.code()) + == JsonrpseeErrorCode::ServerError(-1) => + { + false + } + Err(err) => return Err(Error::from(err)), + }; + let mut rwtxn = env.write_txn()?; + let () = archive.put_bmm_verification(&mut rwtxn, block_hash, res)?; + rwtxn.commit()?; + Ok(res) + } + + /// Attempt to verify bmm recursively up to the specified block, + /// and store the verification results + async fn recursive_verify_bmm( + env: &heed::Env, + archive: &Archive, + drivechain: &bip300301::Drivechain, + block_hash: BlockHash, + ) -> Result<(), Error> { + tracing::debug!( + "requesting recursive BMM verification for {block_hash}" + ); + let blocks_to_verify: Vec = { + let rotxn = env.read_txn()?; + archive + .ancestors(&rotxn, block_hash) + .take_while(|block_hash| { + archive + .try_get_bmm_verification(&rotxn, *block_hash) + .map(|bmm_verification| bmm_verification.is_none()) + }) + .collect()? + }; + let mut blocks_to_verify_iter = blocks_to_verify.into_iter().rev(); + while let Some(block_hash) = blocks_to_verify_iter.next() { + if !Self::verify_bmm(env, archive, drivechain, block_hash).await? { + // mark descendent blocks as BMM failed, + // no need to request from mainchain node + let mut rwtxn = env.write_txn()?; + for block_hash in blocks_to_verify_iter { + let () = archive + .put_bmm_verification(&mut rwtxn, block_hash, false)?; + } + rwtxn.commit()?; + break; + } + } + Ok(()) + } + + async fn run(mut self) -> Result<(), Error> { + while let Some((request, response_tx)) = self.request_rx.next().await { + match request { + Request::AncestorHeaders(block_hash) => { + let header = { + let rotxn = self.env.read_txn()?; + self.archive.get_header(&rotxn, block_hash)? + }; + let () = Self::request_ancestor_headers( + &self.env, + &self.archive, + &self.drivechain, + header.prev_main_hash, + ) + .await?; + let response = Response(request); + if let Some(response_tx) = response_tx { + response_tx + .send(response) + .map_err(Error::SendResponseOneshot)?; + } else { + self.response_tx.unbounded_send(response).map_err( + |err| Error::SendResponse(err.into_inner()), + )?; + } + } + Request::VerifyBmm(block_hash) => { + let () = Self::recursive_verify_bmm( + &self.env, + &self.archive, + &self.drivechain, + block_hash, + ) + .await?; + let response = Response(request); + if let Some(response_tx) = response_tx { + response_tx + .send(response) + .map_err(Error::SendResponseOneshot)?; + } else { + self.response_tx.unbounded_send(response).map_err( + |err| Error::SendResponse(err.into_inner()), + )?; + } + } + } + } + Ok(()) + } +} + +/// Handle to the task to communicate with mainchain node. +/// Task is aborted on drop. +#[derive(Clone)] +pub(super) struct MainchainTaskHandle { + task: Arc>, + // send a request, and optional oneshot sender to receive the result on the + // corresponding oneshot receiver + request_tx: + mpsc::UnboundedSender<(Request, Option>)>, +} + +impl MainchainTaskHandle { + pub fn new( + env: heed::Env, + archive: Archive, + drivechain: Drivechain, + ) -> (Self, mpsc::UnboundedReceiver) { + let (request_tx, request_rx) = mpsc::unbounded(); + let (response_tx, response_rx) = mpsc::unbounded(); + let task = MainchainTask { + env, + archive, + drivechain, + request_rx, + response_tx, + }; + let task = spawn(async { + if let Err(err) = task.run().await { + let err = anyhow::Error::from(err); + tracing::error!("Mainchain task error: {err:#}"); + } + }); + let task_handle = MainchainTaskHandle { + task: Arc::new(task), + request_tx, + }; + (task_handle, response_rx) + } + + /// Send a request + pub fn request(&self, request: Request) -> Result<(), Request> { + self.request_tx + .unbounded_send((request, None)) + .map_err(|err| { + let (request, _) = err.into_inner(); + request + }) + } + + /// Send a request, and receive the response on a oneshot receiver instead + /// of the response stream + pub fn request_oneshot( + &self, + request: Request, + ) -> Result, Request> { + let (oneshot_tx, oneshot_rx) = oneshot::channel(); + let () = self + .request_tx + .unbounded_send((request, Some(oneshot_tx))) + .map_err(|err| { + let (request, _) = err.into_inner(); + request + })?; + Ok(oneshot_rx) + } +} + +impl Drop for MainchainTaskHandle { + // If only one reference exists (ie. within self), abort the net task. + fn drop(&mut self) { + // use `Arc::get_mut` since `Arc::into_inner` requires ownership of the + // Arc, and cloning would increase the reference count + if let Some(task) = Arc::get_mut(&mut self.task) { + task.abort() + } + } +} diff --git a/lib/node/mod.rs b/lib/node/mod.rs new file mode 100644 index 0000000..200f03f --- /dev/null +++ b/lib/node/mod.rs @@ -0,0 +1,511 @@ +use std::{ + collections::{HashMap, HashSet}, + fmt::Debug, + net::SocketAddr, + path::Path, +}; + +use bip300301::{bitcoin, DepositInfo}; +use fallible_iterator::FallibleIterator; +use tokio_util::task::LocalPoolHandle; + +use crate::{ + archive::{self, Archive}, + mempool::{self, MemPool}, + net::{self, Net}, + state::{self, State}, + types::{ + Accumulator, Address, AuthorizedTransaction, BlockHash, Body, GetValue, + Header, OutPoint, Output, SpentOutput, Transaction, Txid, + WithdrawalBundle, + }, +}; + +mod mainchain_task; +mod net_task; + +use mainchain_task::MainchainTaskHandle; + +use self::net_task::NetTaskHandle; + +pub const THIS_SIDECHAIN: u8 = 9; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("address parse error")] + AddrParse(#[from] std::net::AddrParseError), + #[error("archive error")] + Archive(#[from] archive::Error), + #[error("bincode error")] + Bincode(#[from] bincode::Error), + #[error("drivechain error")] + Drivechain(#[from] bip300301::Error), + #[error("heed error")] + Heed(#[from] heed::Error), + #[error("quinn error")] + Io(#[from] std::io::Error), + #[error("mempool error")] + MemPool(#[from] mempool::Error), + #[error("net error")] + Net(#[from] net::Error), + #[error("net task error")] + NetTask(#[from] net_task::Error), + #[error("peer info stream closed")] + PeerInfoRxClosed, + #[error("Receive mainchain task response cancelled")] + ReceiveMainchainTaskResponse, + #[error("Send mainchain task request failed")] + SendMainchainTaskRequest, + #[error("state error")] + State(#[from] state::Error), + #[error("Utreexo error: {0}")] + Utreexo(String), +} + +/// Request any missing two way peg data up to the specified block hash. +/// All ancestor headers must exist in the archive. +// TODO: deposits only for now +#[allow(dead_code)] +async fn request_two_way_peg_data( + env: &heed::Env, + archive: &Archive, + drivechain: &bip300301::Drivechain, + block_hash: bitcoin::BlockHash, +) -> Result<(), Error> { + // last block for which deposit info is known + let last_known_deposit_info = { + let rotxn = env.read_txn()?; + #[allow(clippy::let_and_return)] + let last_known_deposit_info = archive + .main_ancestors(&rotxn, block_hash) + .find(|block_hash| { + let deposits = archive.try_get_deposits(&rotxn, *block_hash)?; + Ok(deposits.is_some()) + })?; + last_known_deposit_info + }; + if last_known_deposit_info == Some(block_hash) { + return Ok(()); + } + let two_way_peg_data = drivechain + .get_two_way_peg_data(block_hash, last_known_deposit_info) + .await?; + let mut rwtxn = env.write_txn()?; + // Deposits by block, first-to-last within each block + let deposits_by_block: HashMap> = { + let mut deposits = HashMap::<_, Vec<_>>::new(); + two_way_peg_data.deposits.into_iter().for_each(|deposit| { + deposits + .entry(deposit.block_hash) + .or_default() + .push(deposit) + }); + let () = archive + .main_ancestors(&rwtxn, block_hash) + .take_while(|block_hash| { + Ok(last_known_deposit_info != Some(*block_hash)) + }) + .for_each(|block_hash| { + let _ = deposits.entry(block_hash).or_default(); + Ok(()) + })?; + deposits + }; + deposits_by_block + .into_iter() + .try_for_each(|(block_hash, deposits)| { + archive.put_deposits(&mut rwtxn, block_hash, deposits) + })?; + rwtxn.commit()?; + Ok(()) +} + +#[derive(Clone)] +pub struct Node { + archive: Archive, + drivechain: bip300301::Drivechain, + env: heed::Env, + _local_pool: LocalPoolHandle, + mainchain_task: MainchainTaskHandle, + mempool: MemPool, + net: Net, + net_task: NetTaskHandle, + state: State, +} + +impl Node { + pub fn new( + datadir: &Path, + bind_addr: SocketAddr, + main_addr: SocketAddr, + user: &str, + password: &str, + local_pool: LocalPoolHandle, + ) -> Result { + let env_path = datadir.join("data.mdb"); + // let _ = std::fs::remove_dir_all(&env_path); + std::fs::create_dir_all(&env_path)?; + let env = unsafe { + heed::EnvOpenOptions::new() + .map_size(1024 * 1024 * 1024) // 1GB + .max_dbs( + State::NUM_DBS + + Archive::NUM_DBS + + MemPool::NUM_DBS + + Net::NUM_DBS, + ) + .open(env_path)? + }; + let state = State::new(&env)?; + let archive = Archive::new(&env)?; + let mempool = MemPool::new(&env)?; + let drivechain = bip300301::Drivechain::new( + THIS_SIDECHAIN, + main_addr, + user, + password, + )?; + let (mainchain_task, mainchain_task_response_rx) = + MainchainTaskHandle::new( + env.clone(), + archive.clone(), + drivechain.clone(), + ); + let (net, peer_info_rx) = + Net::new(&env, archive.clone(), state.clone(), bind_addr)?; + + let net_task = NetTaskHandle::new( + local_pool.clone(), + env.clone(), + archive.clone(), + drivechain.clone(), + mainchain_task.clone(), + mainchain_task_response_rx, + mempool.clone(), + net.clone(), + peer_info_rx, + state.clone(), + ); + Ok(Self { + archive, + drivechain, + env, + _local_pool: local_pool, + mainchain_task, + mempool, + net, + net_task, + state, + }) + } + + pub fn drivechain(&self) -> &bip300301::Drivechain { + &self.drivechain + } + + pub fn get_height(&self) -> Result { + let txn = self.env.read_txn()?; + Ok(self.state.get_height(&txn)?) + } + + pub fn get_best_hash(&self) -> Result { + let txn = self.env.read_txn()?; + Ok(self.state.get_tip(&txn)?) + } + + pub async fn get_best_parentchain_hash( + &self, + ) -> Result { + use bip300301::MainClient; + let res = self + .drivechain + .client + .getbestblockhash() + .await + .map_err(bip300301::Error::Jsonrpsee)?; + Ok(res) + } + + pub fn submit_transaction( + &self, + transaction: AuthorizedTransaction, + ) -> Result<(), Error> { + { + let mut txn = self.env.write_txn()?; + self.state.validate_transaction(&txn, &transaction)?; + self.mempool.put(&mut txn, &transaction)?; + txn.commit()?; + } + self.net.push_tx(Default::default(), transaction); + Ok(()) + } + + pub fn get_spent_utxos( + &self, + outpoints: &[OutPoint], + ) -> Result, Error> { + let txn = self.env.read_txn()?; + let mut spent = vec![]; + for outpoint in outpoints { + if let Some(output) = self.state.stxos.get(&txn, outpoint)? { + spent.push((*outpoint, output)); + } + } + Ok(spent) + } + + pub fn get_utxos_by_addresses( + &self, + addresses: &HashSet
, + ) -> Result, Error> { + let txn = self.env.read_txn()?; + let utxos = self.state.get_utxos_by_addresses(&txn, addresses)?; + Ok(utxos) + } + + pub fn get_tip_accumulator(&self) -> Result { + let rotxn = self.env.read_txn()?; + Ok(self.state.get_accumulator(&rotxn)?) + } + + pub fn regenerate_proof(&self, tx: &mut Transaction) -> Result<(), Error> { + let rotxn = self.env.read_txn()?; + let () = self.state.regenerate_proof(&rotxn, tx)?; + Ok(()) + } + + pub fn try_get_accumulator( + &self, + block_hash: BlockHash, + ) -> Result, Error> { + let rotxn = self.env.read_txn()?; + Ok(self.archive.try_get_accumulator(&rotxn, block_hash)?) + } + + pub fn get_accumulator( + &self, + block_hash: BlockHash, + ) -> Result { + let rotxn = self.env.read_txn()?; + Ok(self.archive.get_accumulator(&rotxn, block_hash)?) + } + + pub fn try_get_header( + &self, + block_hash: BlockHash, + ) -> Result, Error> { + let txn = self.env.read_txn()?; + Ok(self.archive.try_get_header(&txn, block_hash)?) + } + + pub fn get_header(&self, block_hash: BlockHash) -> Result { + let txn = self.env.read_txn()?; + Ok(self.archive.get_header(&txn, block_hash)?) + } + + /// Get the block hash at the specified height in the current chain, + /// if it exists + pub fn try_get_block_hash( + &self, + height: u32, + ) -> Result, Error> { + let rotxn = self.env.read_txn()?; + let tip = self.state.get_tip(&rotxn)?; + let tip_height = self.state.get_height(&rotxn)?; + if tip_height >= height { + self.archive + .ancestors(&rotxn, tip) + .nth((tip_height - height) as usize) + .map_err(Error::from) + } else { + Ok(None) + } + } + + pub fn try_get_body( + &self, + block_hash: BlockHash, + ) -> Result, Error> { + let txn = self.env.read_txn()?; + Ok(self.archive.try_get_body(&txn, block_hash)?) + } + + pub fn get_body(&self, block_hash: BlockHash) -> Result { + let txn = self.env.read_txn()?; + Ok(self.archive.get_body(&txn, block_hash)?) + } + + pub fn get_all_transactions( + &self, + ) -> Result, Error> { + let txn = self.env.read_txn()?; + let transactions = self.mempool.take_all(&txn)?; + Ok(transactions) + } + + /// Get total sidechain wealth in Bitcoin + pub fn get_sidechain_wealth(&self) -> Result { + let txn = self.env.read_txn()?; + Ok(self.state.sidechain_wealth(&txn)?) + } + + pub fn get_transactions( + &self, + number: usize, + ) -> Result<(Vec, u64), Error> { + let mut txn = self.env.write_txn()?; + let transactions = self.mempool.take(&txn, number)?; + let mut fee: u64 = 0; + let mut returned_transactions = vec![]; + let mut spent_utxos = HashSet::new(); + for transaction in &transactions { + let inputs: HashSet<_> = + transaction.transaction.inputs.iter().copied().collect(); + if !spent_utxos.is_disjoint(&inputs) { + println!("UTXO double spent"); + self.mempool + .delete(&mut txn, transaction.transaction.txid())?; + continue; + } + if self.state.validate_transaction(&txn, transaction).is_err() { + self.mempool + .delete(&mut txn, transaction.transaction.txid())?; + continue; + } + let filled_transaction = self + .state + .fill_transaction(&txn, &transaction.transaction)?; + let value_in: u64 = filled_transaction + .spent_utxos + .iter() + .map(GetValue::get_value) + .sum(); + let value_out: u64 = filled_transaction + .transaction + .outputs + .iter() + .map(GetValue::get_value) + .sum(); + fee += value_in - value_out; + returned_transactions.push(transaction.clone()); + spent_utxos.extend(transaction.transaction.inputs.clone()); + } + txn.commit()?; + Ok((returned_transactions, fee)) + } + + pub fn get_pending_withdrawal_bundle( + &self, + ) -> Result, Error> { + let txn = self.env.read_txn()?; + let bundle = self + .state + .get_pending_withdrawal_bundle(&txn)? + .map(|(bundle, _)| bundle); + Ok(bundle) + } + + pub fn remove_from_mempool(&self, txid: Txid) -> Result<(), Error> { + let mut rwtxn = self.env.write_txn()?; + let () = self.mempool.delete(&mut rwtxn, txid)?; + rwtxn.commit()?; + Ok(()) + } + + pub fn connect_peer(&self, addr: SocketAddr) -> Result<(), Error> { + self.net + .connect_peer(self.env.clone(), addr) + .map_err(Error::from) + } + + /// Attempt to submit a block. + /// Returns `Ok(true)` if the block was accepted successfully as the new tip. + /// Returns `Ok(false)` if the block could not be submitted for some reason, + /// or was rejected as the new tip. + pub async fn submit_block( + &self, + header: &Header, + body: &Body, + ) -> Result { + let block_hash = header.hash(); + // Store the header, if ancestors exist + match self.try_get_header(header.prev_side_hash)? { + None => { + tracing::error!( + "Rejecting block {block_hash} due to missing ancestor headers", + ); + return Ok(false); + } + Some(_) => { + let mut rwtxn = self.env.write_txn()?; + let () = self.archive.put_header(&mut rwtxn, header)?; + rwtxn.commit()?; + } + } + // Request mainchain headers if they do not exist + let _: mainchain_task::Response = self + .mainchain_task + .request_oneshot(mainchain_task::Request::AncestorHeaders( + header.hash(), + )) + .map_err(|_| Error::SendMainchainTaskRequest)? + .await + .map_err(|_| Error::ReceiveMainchainTaskResponse)?; + // Verify BMM + let _: mainchain_task::Response = self + .mainchain_task + .request_oneshot(mainchain_task::Request::VerifyBmm(block_hash)) + .map_err(|_| Error::SendMainchainTaskRequest)? + .await + .map_err(|_| Error::ReceiveMainchainTaskResponse)?; + { + let rotxn = self.env.read_txn()?; + if !self.archive.get_bmm_verification(&rotxn, block_hash)? { + tracing::error!( + "Rejecting block {block_hash} due to failing BMM verification", + ); + return Ok(false); + } + rotxn.commit()?; + } + // Check that ancestor bodies exist, and store body + { + let rotxn = self.env.read_txn()?; + let tip = self.state.get_tip(&rotxn)?; + let common_ancestor = + self.archive.last_common_ancestor(&rotxn, tip, block_hash)?; + let missing_bodies = self.archive.get_missing_bodies( + &rotxn, + block_hash, + common_ancestor, + )?; + if !(missing_bodies.is_empty() + || missing_bodies == vec![block_hash]) + { + tracing::error!( + "Rejecting block {block_hash} due to missing ancestor bodies", + ); + return Ok(false); + } + rotxn.commit()?; + if missing_bodies == vec![block_hash] { + let mut rwtxn = self.env.write_txn()?; + let () = self.archive.put_body(&mut rwtxn, block_hash, body)?; + rwtxn.commit()?; + } + } + // Submit new tip + if !self.net_task.new_tip_ready_confirm(header.hash()).await? { + return Ok(false); + }; + let rotxn = self.env.read_txn()?; + let bundle = self.state.get_pending_withdrawal_bundle(&rotxn)?; + if let Some((bundle, _)) = bundle { + let () = self + .drivechain + .broadcast_withdrawal_bundle(bundle.transaction) + .await?; + } + Ok(true) + } +} diff --git a/lib/node/net_task.rs b/lib/node/net_task.rs new file mode 100644 index 0000000..f0424c6 --- /dev/null +++ b/lib/node/net_task.rs @@ -0,0 +1,851 @@ +//! Task to manage peers and their responses + +use std::{ + collections::{HashMap, HashSet}, + net::SocketAddr, + sync::Arc, +}; + +use bip300301::Drivechain; +use fallible_iterator::{FallibleIterator, IteratorExt}; +use futures::{ + channel::{ + mpsc::{self, UnboundedReceiver, UnboundedSender}, + oneshot, + }, + stream, StreamExt, +}; +use heed::RwTxn; +use thiserror::Error; +use tokio::task::JoinHandle; +use tokio_stream::StreamNotifyClose; +use tokio_util::task::LocalPoolHandle; + +use super::mainchain_task::{self, MainchainTaskHandle}; +use crate::{ + archive::{self, Archive}, + mempool::{self, MemPool}, + net::{ + self, Net, PeerConnectionInfo, PeerInfoRx, PeerRequest, PeerResponse, + }, + state::{self, State}, + types::{BlockHash, Body, Header}, +}; + +#[derive(Debug, Error)] +pub enum Error { + #[error("archive error")] + Archive(#[from] archive::Error), + #[error("drivechain error")] + Drivechain(#[from] bip300301::Error), + #[error("Forward mainchain task request failed")] + ForwardMainchainTaskRequest, + #[error("heed error")] + Heed(#[from] heed::Error), + #[error("mempool error")] + MemPool(#[from] mempool::Error), + #[error("Net error")] + Net(#[from] net::Error), + #[error("peer info stream closed")] + PeerInfoRxClosed, + #[error("Receive mainchain task response cancelled")] + ReceiveMainchainTaskResponse, + #[error("Receive reorg result cancelled (oneshot)")] + ReceiveReorgResultOneshot, + #[error("Send mainchain task request failed")] + SendMainchainTaskRequest, + #[error("Send new tip ready failed")] + SendNewTipReady, + #[error("Send reorg result error (oneshot)")] + SendReorgResultOneshot, + #[error("state error")] + State(#[from] state::Error), +} + +async fn connect_tip_( + rwtxn: &mut RwTxn<'_>, + archive: &Archive, + drivechain: &bip300301::Drivechain, + mempool: &MemPool, + state: &State, + header: &Header, + body: &Body, +) -> Result<(), Error> { + let last_deposit_block_hash = state.get_last_deposit_block_hash(rwtxn)?; + let two_way_peg_data = drivechain + .get_two_way_peg_data(header.prev_main_hash, last_deposit_block_hash) + .await?; + let block_hash = header.hash(); + let _fees: u64 = state.validate_block(rwtxn, header, body)?; + if tracing::enabled!(tracing::Level::DEBUG) { + let merkle_root = body.compute_merkle_root(); + let height = state.get_height(rwtxn)?; + let () = state.connect_block(rwtxn, header, body)?; + tracing::debug!(%height, %merkle_root, %block_hash, + "connected body") + } else { + let () = state.connect_block(rwtxn, header, body)?; + } + let () = state.connect_two_way_peg_data(rwtxn, &two_way_peg_data)?; + let accumulator = state.get_accumulator(rwtxn)?; + let () = archive.put_header(rwtxn, header)?; + let () = archive.put_body(rwtxn, block_hash, body)?; + let () = archive.put_accumulator(rwtxn, block_hash, &accumulator)?; + for transaction in &body.transactions { + let () = mempool.delete(rwtxn, transaction.txid())?; + } + let () = mempool.regenerate_proofs(rwtxn, &accumulator)?; + Ok(()) +} + +async fn disconnect_tip_( + rwtxn: &mut RwTxn<'_>, + archive: &Archive, + drivechain: &bip300301::Drivechain, + mempool: &MemPool, + state: &State, +) -> Result<(), Error> { + let tip_block_hash = state.get_tip(rwtxn)?; + let tip_header = archive.get_header(rwtxn, tip_block_hash)?; + let tip_body = archive.get_body(rwtxn, tip_block_hash)?; + let height = state.get_height(rwtxn)?; + let two_way_peg_data = { + let start_block_hash = state + .deposit_blocks + .rev_iter(rwtxn)? + .transpose_into_fallible() + .find_map(|(_, (block_hash, applied_height))| { + if applied_height < height - 1 { + Ok(Some(block_hash)) + } else { + Ok(None) + } + })?; + drivechain + .get_two_way_peg_data(tip_header.prev_main_hash, start_block_hash) + .await? + }; + let () = state.disconnect_two_way_peg_data(rwtxn, &two_way_peg_data)?; + let () = state.disconnect_tip(rwtxn, &tip_header, &tip_body)?; + // TODO: revert accumulator only necessary because rustreexo does not + // support undo yet + { + let new_tip = state.get_tip(rwtxn)?; + let accumulator = archive.get_accumulator(rwtxn, new_tip)?; + let () = state.utreexo_accumulator.put( + rwtxn, + &state::UnitKey, + &accumulator, + )?; + } + for transaction in tip_body.authorized_transactions().iter().rev() { + mempool.put(rwtxn, transaction)?; + } + let accumulator = state.get_accumulator(rwtxn)?; + mempool.regenerate_proofs(rwtxn, &accumulator)?; + Ok(()) +} + +/// Re-org to the specified tip, if it is better than the current tip. +/// The new tip block and all ancestor blocks must exist in the node's archive. +/// A result of `Ok(true)` indicates a successful re-org. +/// A result of `Ok(false)` indicates that no re-org was attempted. +async fn reorg_to_tip( + env: &heed::Env, + archive: &Archive, + drivechain: &bip300301::Drivechain, + mempool: &MemPool, + state: &State, + new_tip: BlockHash, +) -> Result { + let mut rwtxn = env.write_txn()?; + let tip = state.get_tip(&rwtxn)?; + let tip_height = state.get_height(&rwtxn)?; + // check that new tip is better than current tip + if archive.better_tip(&rwtxn, tip, new_tip)? != Some(new_tip) { + return Ok(false); + } + let common_ancestor = archive.last_common_ancestor(&rwtxn, tip, new_tip)?; + // Check that all necessary bodies exist before disconnecting tip + let blocks_to_apply: Vec<(Header, Body)> = archive + .ancestors(&rwtxn, new_tip) + .take_while(|block_hash| Ok(*block_hash != common_ancestor)) + .map(|block_hash| { + let header = archive.get_header(&rwtxn, block_hash)?; + let body = archive.get_body(&rwtxn, block_hash)?; + Ok((header, body)) + }) + .collect()?; + // Disconnect tip until common ancestor is reached + let common_ancestor_height = archive.get_height(&rwtxn, common_ancestor)?; + for _ in 0..tip_height - common_ancestor_height { + let () = + disconnect_tip_(&mut rwtxn, archive, drivechain, mempool, state) + .await?; + } + let tip = state.get_tip(&rwtxn)?; + assert_eq!(tip, common_ancestor); + // Apply blocks until new tip is reached + for (header, body) in blocks_to_apply.into_iter().rev() { + let () = connect_tip_( + &mut rwtxn, archive, drivechain, mempool, state, &header, &body, + ) + .await?; + } + let tip = state.get_tip(&rwtxn)?; + assert_eq!(tip, new_tip); + rwtxn.commit()?; + tracing::info!("reorged to tip: {new_tip}"); + Ok(true) +} + +#[derive(Clone)] +struct NetTaskContext { + env: heed::Env, + archive: Archive, + drivechain: Drivechain, + mainchain_task: MainchainTaskHandle, + mempool: MemPool, + net: Net, + state: State, +} + +/// Message indicating a tip that is ready to reorg to, with the address of the +/// peer connection that caused the request, if it originated from a peer. +/// If the request originates from this node, then the socket address is +/// None. +/// An optional oneshot sender can be used receive the result of attempting +/// to reorg to the new tip, on the corresponding oneshot receiver. +type NewTipReadyMessage = + (BlockHash, Option, Option>); + +struct NetTask { + ctxt: NetTaskContext, + /// Receive a request to forward to the mainchain task, with the address of + /// the peer connection that caused the request + forward_mainchain_task_request_rx: + UnboundedReceiver<(mainchain_task::Request, SocketAddr)>, + /// Push a request to forward to the mainchain task, with the address of + /// the peer connection that caused the request + forward_mainchain_task_request_tx: + UnboundedSender<(mainchain_task::Request, SocketAddr)>, + mainchain_task_response_rx: UnboundedReceiver, + /// Receive a tip that is ready to reorg to, with the address of the peer + /// connection that caused the request, if it originated from a peer. + /// If the request originates from this node, then the socket address is + /// None. + /// An optional oneshot sender can be used receive the result of attempting + /// to reorg to the new tip, on the corresponding oneshot receiver. + new_tip_ready_rx: UnboundedReceiver, + /// Push a tip that is ready to reorg to, with the address of the peer + /// connection that caused the request, if it originated from a peer. + /// If the request originates from this node, then the socket address is + /// None. + /// An optional oneshot sender can be used receive the result of attempting + /// to reorg to the new tip, on the corresponding oneshot receiver. + new_tip_ready_tx: UnboundedSender, + peer_info_rx: PeerInfoRx, +} + +impl NetTask { + async fn handle_response( + ctxt: &NetTaskContext, + // Attempt to switch to a descendant tip once a body has been + // stored, if all other ancestor bodies are available. + // Each descendant tip maps to the peers that sent that tip. + descendant_tips: &mut HashMap< + BlockHash, + HashMap>, + >, + forward_mainchain_task_request_tx: &UnboundedSender<( + mainchain_task::Request, + SocketAddr, + )>, + new_tip_ready_tx: &UnboundedSender, + addr: SocketAddr, + resp: PeerResponse, + req: PeerRequest, + ) -> Result<(), Error> { + match (req, resp) { + ( + req @ PeerRequest::GetBlock { block_hash }, + ref resp @ PeerResponse::Block { + ref header, + ref body, + }, + ) => { + if header.hash() != block_hash { + // Invalid response + tracing::warn!(%addr, ?req, ?resp,"Invalid response from peer; unexpected block hash"); + let () = ctxt.net.remove_active_peer(addr); + return Ok(()); + } + { + let mut rwtxn = ctxt.env.write_txn()?; + let () = + ctxt.archive.put_body(&mut rwtxn, block_hash, body)?; + rwtxn.commit()?; + } + // Check if any new tips can be applied, + // and send new tip ready if so + { + let rotxn = ctxt.env.read_txn()?; + let tip = ctxt.state.get_tip(&rotxn)?; + if header.prev_side_hash == tip { + let () = new_tip_ready_tx + .unbounded_send((block_hash, Some(addr), None)) + .map_err(|_| Error::SendNewTipReady)?; + } + let Some(descendant_tips) = + descendant_tips.remove(&block_hash) + else { + return Ok(()); + }; + for (descendant_tip, sources) in descendant_tips { + let common_ancestor = + ctxt.archive.last_common_ancestor( + &rotxn, + descendant_tip, + tip, + )?; + let missing_bodies = ctxt.archive.get_missing_bodies( + &rotxn, + descendant_tip, + common_ancestor, + )?; + if missing_bodies.is_empty() { + for addr in sources { + let () = new_tip_ready_tx + .unbounded_send(( + descendant_tip, + Some(addr), + None, + )) + .map_err(|_| Error::SendNewTipReady)?; + } + } + } + } + + Ok(()) + } + ( + PeerRequest::GetBlock { + block_hash: req_block_hash, + }, + PeerResponse::NoBlock { + block_hash: resp_block_hash, + }, + ) if req_block_hash == resp_block_hash => Ok(()), + ( + ref req @ PeerRequest::GetHeaders { + ref start, + end, + height: Some(height), + }, + PeerResponse::Headers(headers), + ) => { + // check that the end header is as requested + let Some(end_header) = headers.last() else { + tracing::warn!(%addr, ?req, "Invalid response from peer; missing end header"); + let () = ctxt.net.remove_active_peer(addr); + return Ok(()); + }; + let end_header_hash = end_header.hash(); + if end_header_hash != end { + tracing::warn!(%addr, ?req, ?end_header,"Invalid response from peer; unexpected end header"); + let () = ctxt.net.remove_active_peer(addr); + return Ok(()); + } + // Must be at least one header due to previous check + let start_hash = headers.first().unwrap().prev_side_hash; + // check that the first header is after a start block + if !(start.contains(&start_hash) + || start_hash == BlockHash::default()) + { + tracing::warn!(%addr, ?req, ?start_hash, "Invalid response from peer; invalid start hash"); + let () = ctxt.net.remove_active_peer(addr); + return Ok(()); + } + // check that the end header height is as expected + { + let rotxn = ctxt.env.read_txn()?; + let start_height = + ctxt.archive.get_height(&rotxn, start_hash)?; + if start_height + headers.len() as u32 != height { + tracing::warn!(%addr, ?req, ?start_hash, "Invalid response from peer; invalid end height"); + let () = ctxt.net.remove_active_peer(addr); + return Ok(()); + } + } + // check that headers are sequential based on prev_side_hash + let mut prev_side_hash = start_hash; + for header in &headers { + if header.prev_side_hash != prev_side_hash { + tracing::warn!(%addr, ?req, ?headers,"Invalid response from peer; non-sequential headers"); + let () = ctxt.net.remove_active_peer(addr); + return Ok(()); + } + prev_side_hash = header.hash(); + } + // Store new headers + let mut rwtxn = ctxt.env.write_txn()?; + for header in &headers { + let block_hash = header.hash(); + if ctxt + .archive + .try_get_header(&rwtxn, block_hash)? + .is_none() + { + if header.prev_side_hash == BlockHash::default() + || ctxt + .archive + .try_get_header(&rwtxn, header.prev_side_hash)? + .is_some() + { + ctxt.archive.put_header(&mut rwtxn, header)?; + } else { + break; + } + } + } + rwtxn.commit()?; + // Request mainchain headers + let request = + mainchain_task::Request::AncestorHeaders(end_header_hash); + let () = forward_mainchain_task_request_tx + .unbounded_send((request, addr)) + .map_err(|_| Error::ForwardMainchainTaskRequest)?; + Ok(()) + } + ( + PeerRequest::GetHeaders { + start: _, + end, + height: _, + }, + PeerResponse::NoHeader { block_hash }, + ) if end == block_hash => Ok(()), + ( + PeerRequest::PushTransaction { transaction: _ }, + PeerResponse::TransactionAccepted(_), + ) => Ok(()), + ( + PeerRequest::PushTransaction { transaction: _ }, + PeerResponse::TransactionRejected(_), + ) => Ok(()), + ( + req @ (PeerRequest::GetBlock { .. } + | PeerRequest::GetHeaders { .. } + | PeerRequest::Heartbeat(_) + | PeerRequest::PushTransaction { .. }), + resp, + ) => { + // Invalid response + tracing::warn!(%addr, ?req, ?resp,"Invalid response from peer"); + let () = ctxt.net.remove_active_peer(addr); + Ok(()) + } + } + } + + async fn run(self) -> Result<(), Error> { + enum MailboxItem { + AcceptConnection(Result<(), Error>), + // Forward a mainchain task request, along with the peer that + // caused the request + ForwardMainchainTaskRequest(mainchain_task::Request, SocketAddr), + MainchainTaskResponse(mainchain_task::Response), + // Apply new tip from peer or self. + // An optional oneshot sender can be used receive the result of + // attempting to reorg to the new tip, on the corresponding oneshot + // receiver. + NewTipReady( + BlockHash, + Option, + Option>, + ), + PeerInfo(Option<(SocketAddr, Option)>), + } + let accept_connections = stream::try_unfold((), |()| { + let env = self.ctxt.env.clone(); + let net = self.ctxt.net.clone(); + let fut = async move { + let () = net.accept_incoming(env).await?; + Result::<_, Error>::Ok(Some(((), ()))) + }; + Box::pin(fut) + }) + .map(MailboxItem::AcceptConnection); + let forward_request_stream = self + .forward_mainchain_task_request_rx + .map(|(request, addr)| { + MailboxItem::ForwardMainchainTaskRequest(request, addr) + }); + let mainchain_task_response_stream = self + .mainchain_task_response_rx + .map(MailboxItem::MainchainTaskResponse); + let new_tip_ready_stream = + self.new_tip_ready_rx.map(|(block_hash, addr, resp_tx)| { + MailboxItem::NewTipReady(block_hash, addr, resp_tx) + }); + let peer_info_stream = StreamNotifyClose::new(self.peer_info_rx) + .map(MailboxItem::PeerInfo); + let mut mailbox_stream = stream::select_all([ + accept_connections.boxed(), + forward_request_stream.boxed(), + mainchain_task_response_stream.boxed(), + new_tip_ready_stream.boxed(), + peer_info_stream.boxed(), + ]); + // Attempt to switch to a descendant tip once a body has been + // stored, if all other ancestor bodies are available. + // Each descendant tip maps to the peers that sent that tip. + let mut descendant_tips = + HashMap::>>::new( + ); + // Map associating mainchain task requests with the peer(s) that + // caused the request + let mut mainchain_task_request_sources = + HashMap::>::new(); + while let Some(mailbox_item) = mailbox_stream.next().await { + match mailbox_item { + MailboxItem::AcceptConnection(res) => res?, + MailboxItem::ForwardMainchainTaskRequest(request, peer) => { + mainchain_task_request_sources + .entry(request) + .or_default() + .insert(peer); + let () = self + .ctxt + .mainchain_task + .request(request) + .map_err(|_| Error::SendMainchainTaskRequest)?; + } + MailboxItem::MainchainTaskResponse(response) => { + let request = response.0; + match request { + mainchain_task::Request::AncestorHeaders( + block_hash, + ) => { + let Some(sources) = + mainchain_task_request_sources.remove(&request) + else { + continue; + }; + // request verify BMM + for addr in sources { + let request = + mainchain_task::Request::VerifyBmm( + block_hash, + ); + let () = self + .forward_mainchain_task_request_tx + .unbounded_send((request, addr)) + .map_err(|_| { + Error::ForwardMainchainTaskRequest + })?; + } + } + mainchain_task::Request::VerifyBmm(block_hash) => { + let Some(sources) = + mainchain_task_request_sources.remove(&request) + else { + continue; + }; + let verify_bmm_result = { + let rotxn = self.ctxt.env.read_txn()?; + self.ctxt + .archive + .get_bmm_verification(&rotxn, block_hash)? + }; + if !verify_bmm_result { + for addr in &sources { + tracing::warn!( + %addr, + %block_hash, + "Invalid response from peer; BMM verification failed" + ); + let () = + self.ctxt.net.remove_active_peer(*addr); + } + } + let missing_bodies: Vec = { + let rotxn = self.ctxt.env.read_txn()?; + let tip = self.ctxt.state.get_tip(&rotxn)?; + let last_common_ancestor = + self.ctxt.archive.last_common_ancestor( + &rotxn, tip, block_hash, + )?; + self.ctxt.archive.get_missing_bodies( + &rotxn, + block_hash, + last_common_ancestor, + )? + }; + if missing_bodies.is_empty() { + for addr in sources { + let () = self + .new_tip_ready_tx + .unbounded_send(( + block_hash, + Some(addr), + None, + )) + .map_err(|_| Error::SendNewTipReady)?; + } + } else { + let rotxn = self.ctxt.env.read_txn()?; + // Request missing bodies, update descendent tips + for missing_body in missing_bodies { + descendant_tips + .entry(missing_body) + .or_default() + .entry(block_hash) + .or_default() + .extend(&sources); + // tips descended from the missing body, + // that are alo ancestors of `block_hash` + let lineage_tips: Vec = + descendant_tips[&missing_body] + .keys() + .map(Ok) + .transpose_into_fallible() + .filter(|tip| { + self.ctxt.archive.is_descendant( + &rotxn, **tip, block_hash, + ) + }) + .cloned() + .collect()?; + for lineage_tip in lineage_tips.into_iter() + { + let updated_sources: HashSet< + SocketAddr, + > = descendant_tips[&missing_body] + [&lineage_tip] + .difference(&sources) + .cloned() + .collect(); + if updated_sources.is_empty() { + descendant_tips + .get_mut(&missing_body) + .unwrap() + .remove(&lineage_tip); + } else { + descendant_tips + .get_mut(&missing_body) + .unwrap() + .insert( + lineage_tip, + updated_sources, + ); + } + } + let request = PeerRequest::GetBlock { + block_hash: missing_body, + }; + let () = self + .ctxt + .net + .push_request(request, &sources); + } + } + } + } + } + MailboxItem::NewTipReady(new_tip, _addr, resp_tx) => { + let reorg_applied = reorg_to_tip( + &self.ctxt.env, + &self.ctxt.archive, + &self.ctxt.drivechain, + &self.ctxt.mempool, + &self.ctxt.state, + new_tip, + ) + .await?; + if let Some(resp_tx) = resp_tx { + let () = resp_tx + .send(reorg_applied) + .map_err(|_| Error::SendReorgResultOneshot)?; + } + } + MailboxItem::PeerInfo(None) => { + return Err(Error::PeerInfoRxClosed) + } + MailboxItem::PeerInfo(Some((addr, None))) => { + // peer connection is closed, remove it + tracing::warn!(%addr, "Connection to peer closed"); + let () = self.ctxt.net.remove_active_peer(addr); + continue; + } + MailboxItem::PeerInfo(Some((addr, Some(peer_info)))) => { + match peer_info { + PeerConnectionInfo::Error(err) => { + let err = anyhow::anyhow!(err); + tracing::error!(%addr, err = format!("{err:#}"), "Peer connection error"); + let () = self.ctxt.net.remove_active_peer(addr); + } + PeerConnectionInfo::NeedBmmVerification(block_hash) => { + let request = + mainchain_task::Request::VerifyBmm(block_hash); + let () = self + .forward_mainchain_task_request_tx + .unbounded_send((request, addr)) + .map_err(|_| { + Error::ForwardMainchainTaskRequest + })?; + } + PeerConnectionInfo::NeedMainchainAncestors( + block_hash, + ) => { + let request = + mainchain_task::Request::AncestorHeaders( + block_hash, + ); + let () = self + .forward_mainchain_task_request_tx + .unbounded_send((request, addr)) + .map_err(|_| { + Error::ForwardMainchainTaskRequest + })?; + } + PeerConnectionInfo::NewTipReady(new_tip) => { + self.new_tip_ready_tx + .unbounded_send((new_tip, Some(addr), None)) + .map_err(|_| Error::SendNewTipReady)?; + } + PeerConnectionInfo::NewTransaction(mut new_tx) => { + let mut rwtxn = self.ctxt.env.write_txn()?; + let () = self.ctxt.state.regenerate_proof( + &rwtxn, + &mut new_tx.transaction, + )?; + self.ctxt.mempool.put(&mut rwtxn, &new_tx)?; + rwtxn.commit()?; + // broadcast + let () = self + .ctxt + .net + .push_tx(HashSet::from_iter([addr]), new_tx); + } + PeerConnectionInfo::Response(resp, req) => { + let () = Self::handle_response( + &self.ctxt, + &mut descendant_tips, + &self.forward_mainchain_task_request_tx, + &self.new_tip_ready_tx, + addr, + resp, + req, + ) + .await?; + } + } + } + } + } + Ok(()) + } +} + +/// Handle to the net task. +/// Task is aborted on drop. +#[derive(Clone)] +pub(super) struct NetTaskHandle { + task: Arc>, + /// Push a tip that is ready to reorg to, with the address of the peer + /// connection that caused the request, if it originated from a peer. + /// If the request originates from this node, then the socket address is + /// None. + /// An optional oneshot sender can be used receive the result of attempting + /// to reorg to the new tip, on the corresponding oneshot receiver. + new_tip_ready_tx: UnboundedSender, +} + +impl NetTaskHandle { + #[allow(clippy::too_many_arguments)] + pub fn new( + local_pool: LocalPoolHandle, + env: heed::Env, + archive: Archive, + drivechain: Drivechain, + mainchain_task: MainchainTaskHandle, + mainchain_task_response_rx: UnboundedReceiver, + mempool: MemPool, + net: Net, + peer_info_rx: PeerInfoRx, + state: State, + ) -> Self { + let ctxt = NetTaskContext { + env, + archive, + drivechain, + mainchain_task, + mempool, + net, + state, + }; + let ( + forward_mainchain_task_request_tx, + forward_mainchain_task_request_rx, + ) = mpsc::unbounded(); + let (new_tip_ready_tx, new_tip_ready_rx) = mpsc::unbounded(); + let task = NetTask { + ctxt, + forward_mainchain_task_request_tx, + forward_mainchain_task_request_rx, + mainchain_task_response_rx, + new_tip_ready_tx: new_tip_ready_tx.clone(), + new_tip_ready_rx, + peer_info_rx, + }; + let task = local_pool.spawn_pinned(|| async { + if let Err(err) = task.run().await { + let err = anyhow::Error::from(err); + tracing::error!("Net task error: {err:#}"); + } + }); + NetTaskHandle { + task: Arc::new(task), + new_tip_ready_tx, + } + } + + /// Push a tip that is ready to reorg to. + #[allow(dead_code)] + pub fn new_tip_ready(&self, new_tip: BlockHash) -> Result<(), Error> { + self.new_tip_ready_tx + .unbounded_send((new_tip, None, None)) + .map_err(|_| Error::SendNewTipReady) + } + + /// Push a tip that is ready to reorg to, and await successful application. + /// A result of Ok(true) indicates that the tip was applied and reorged + /// to successfully. + /// A result of Ok(false) indicates that the tip was not reorged to. + pub async fn new_tip_ready_confirm( + &self, + new_tip: BlockHash, + ) -> Result { + let (oneshot_tx, oneshot_rx) = oneshot::channel(); + let () = self + .new_tip_ready_tx + .unbounded_send((new_tip, None, Some(oneshot_tx))) + .map_err(|_| Error::SendNewTipReady)?; + oneshot_rx + .await + .map_err(|_| Error::ReceiveReorgResultOneshot) + } +} + +impl Drop for NetTaskHandle { + // If only one reference exists (ie. within self), abort the net task. + fn drop(&mut self) { + // use `Arc::get_mut` since `Arc::into_inner` requires ownership of the + // Arc, and cloning would increase the reference count + if let Some(task) = Arc::get_mut(&mut self.task) { + task.abort() + } + } +} diff --git a/lib/src/archive.rs b/lib/src/archive.rs deleted file mode 100644 index 6c95b72..0000000 --- a/lib/src/archive.rs +++ /dev/null @@ -1,93 +0,0 @@ -use crate::types::*; -use crate::types::{hash, BlockHash, Body}; -use heed::byteorder::{BigEndian, ByteOrder}; -use heed::types::*; -use heed::{Database, RoTxn, RwTxn}; - -#[derive(Clone)] -pub struct Archive { - headers: Database, SerdeBincode
>, - bodies: Database, SerdeBincode>, - hash_to_height: Database, OwnedType<[u8; 4]>>, -} - -impl Archive { - pub const NUM_DBS: u32 = 3; - - pub fn new(env: &heed::Env) -> Result { - let headers = env.create_database(Some("headers"))?; - let bodies = env.create_database(Some("bodies"))?; - let hash_to_height = env.create_database(Some("hash_to_height"))?; - Ok(Self { - headers, - bodies, - hash_to_height, - }) - } - - pub fn get_header(&self, txn: &RoTxn, height: u32) -> Result, Error> { - let height = height.to_be_bytes(); - let header = self.headers.get(txn, &height)?; - Ok(header) - } - - pub fn get_body(&self, txn: &RoTxn, height: u32) -> Result, Error> { - let height = height.to_be_bytes(); - let header = self.bodies.get(txn, &height)?; - Ok(header) - } - - pub fn get_best_hash(&self, txn: &RoTxn) -> Result { - let best_hash = match self.headers.last(txn)? { - Some((_, header)) => hash(&header).into(), - None => [0; 32].into(), - }; - Ok(best_hash) - } - - pub fn get_height(&self, txn: &RoTxn) -> Result { - let height = match self.headers.last(txn)? { - Some((height, _)) => BigEndian::read_u32(&height), - None => 0, - }; - Ok(height) - } - - pub fn put_body(&self, txn: &mut RwTxn, header: &Header, body: &Body) -> Result<(), Error> { - if header.merkle_root != body.compute_merkle_root() { - return Err(Error::InvalidMerkleRoot); - } - let hash = header.hash(); - let height = self - .hash_to_height - .get(txn, &hash.into())? - .ok_or(Error::NoHeader(hash))?; - self.bodies.put(txn, &height, body)?; - Ok(()) - } - - pub fn append_header(&self, txn: &mut RwTxn, header: &Header) -> Result<(), Error> { - let height = self.get_height(txn)?; - let best_hash = self.get_best_hash(txn)?; - if header.prev_side_hash != best_hash { - return Err(Error::InvalidPrevSideHash); - } - let new_height = (height + 1).to_be_bytes(); - self.headers.put(txn, &new_height, header)?; - self.hash_to_height - .put(txn, &header.hash().into(), &new_height)?; - Ok(()) - } -} - -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error("heed error")] - Heed(#[from] heed::Error), - #[error("invalid previous side hash")] - InvalidPrevSideHash, - #[error("invalid merkle root")] - InvalidMerkleRoot, - #[error("no header with hash {0}")] - NoHeader(BlockHash), -} diff --git a/lib/src/authorization.rs b/lib/src/authorization.rs deleted file mode 100644 index 21e73b7..0000000 --- a/lib/src/authorization.rs +++ /dev/null @@ -1,171 +0,0 @@ -use crate::types::blake3; -use crate::types::{Address, AuthorizedTransaction, Body, GetAddress, Transaction, Verify}; -pub use ed25519_dalek::{Keypair, PublicKey, Signature, SignatureError, Signer, Verifier}; -use rayon::prelude::*; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct Authorization { - pub public_key: PublicKey, - pub signature: Signature, -} - -impl GetAddress for Authorization { - fn get_address(&self) -> Address { - get_address(&self.public_key) - } -} - -impl Verify for Authorization { - type Error = Error; - fn verify_transaction(transaction: &AuthorizedTransaction) -> Result<(), Self::Error> { - verify_authorized_transaction(transaction)?; - Ok(()) - } - - fn verify_body(body: &Body) -> Result<(), Self::Error> { - verify_authorizations(body)?; - Ok(()) - } -} - -pub fn get_address(public_key: &PublicKey) -> Address { - let mut hasher = blake3::Hasher::new(); - let mut reader = hasher.update(&public_key.to_bytes()).finalize_xof(); - let mut output: [u8; 20] = [0; 20]; - reader.fill(&mut output); - Address(output) -} - -struct Package<'a> { - messages: Vec<&'a [u8]>, - signatures: Vec, - public_keys: Vec, -} - -pub fn verify_authorized_transaction(transaction: &AuthorizedTransaction) -> Result<(), Error> { - let serialized_transaction = bincode::serialize(&transaction.transaction)?; - let messages: Vec<_> = std::iter::repeat(serialized_transaction.as_slice()) - .take(transaction.authorizations.len()) - .collect(); - let (public_keys, signatures): (Vec, Vec) = transaction - .authorizations - .iter() - .map( - |Authorization { - public_key, - signature, - }| (public_key, signature), - ) - .unzip(); - ed25519_dalek::verify_batch(&messages, &signatures, &public_keys)?; - Ok(()) -} - -pub fn verify_authorizations(body: &Body) -> Result<(), Error> { - let input_numbers = body - .transactions - .iter() - .map(|transaction| transaction.inputs.len()); - let serialized_transactions: Vec> = body - .transactions - .par_iter() - .map(bincode::serialize) - .collect::>()?; - let serialized_transactions = serialized_transactions.iter().map(Vec::as_slice); - let messages = input_numbers.zip(serialized_transactions).flat_map( - |(input_number, serialized_transaction)| { - std::iter::repeat(serialized_transaction).take(input_number) - }, - ); - - let pairs = body.authorizations.iter().zip(messages).collect::>(); - - let num_threads = rayon::current_num_threads(); - let num_authorizations = body.authorizations.len(); - let package_size = num_authorizations / num_threads; - let mut packages: Vec = Vec::with_capacity(num_threads); - for i in 0..num_threads { - let mut package = Package { - messages: Vec::with_capacity(package_size), - signatures: Vec::with_capacity(package_size), - public_keys: Vec::with_capacity(package_size), - }; - for (authorization, message) in &pairs[i * package_size..(i + 1) * package_size] { - package.messages.push(*message); - package.signatures.push(authorization.signature); - package.public_keys.push(authorization.public_key); - } - packages.push(package); - } - for (authorization, message) in &pairs[num_threads * package_size..] { - packages[num_threads - 1].messages.push(*message); - packages[num_threads - 1] - .signatures - .push(authorization.signature); - packages[num_threads - 1] - .public_keys - .push(authorization.public_key); - } - assert_eq!( - packages.iter().map(|p| p.signatures.len()).sum::(), - body.authorizations.len() - ); - packages - .par_iter() - .map( - |Package { - messages, - signatures, - public_keys, - }| ed25519_dalek::verify_batch(messages, signatures, public_keys), - ) - .collect::>()?; - Ok(()) -} - -pub fn sign(keypair: &Keypair, transaction: &Transaction) -> Result { - let message = bincode::serialize(&transaction)?; - Ok(keypair.sign(&message)) -} - -pub fn authorize( - addresses_keypairs: &[(Address, &Keypair)], - transaction: Transaction, -) -> Result { - let mut authorizations: Vec = Vec::with_capacity(addresses_keypairs.len()); - let message = bincode::serialize(&transaction)?; - for (address, keypair) in addresses_keypairs { - let hash_public_key = get_address(&keypair.public); - if *address != hash_public_key { - return Err(Error::WrongKeypairForAddress { - address: *address, - hash_public_key, - }); - } - let authorization = Authorization { - public_key: keypair.public, - signature: keypair.sign(&message), - }; - authorizations.push(authorization); - } - Ok(AuthorizedTransaction { - authorizations, - transaction, - }) -} - -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error( - "wrong keypair for address: address = {address}, hash(public_key) = {hash_public_key}" - )] - WrongKeypairForAddress { - address: Address, - hash_public_key: Address, - }, - #[error("ed25519_dalek error")] - DalekError(#[from] SignatureError), - #[error("bincode error")] - BincodeError(#[from] bincode::Error), -} diff --git a/lib/src/mempool.rs b/lib/src/mempool.rs deleted file mode 100644 index 32ce5af..0000000 --- a/lib/src/mempool.rs +++ /dev/null @@ -1,69 +0,0 @@ -use crate::types::{AuthorizedTransaction, OutPoint, Txid}; -use heed::types::*; -use heed::{Database, RoTxn, RwTxn}; - -#[derive(Clone)] -pub struct MemPool { - pub transactions: Database, SerdeBincode>, - pub spent_utxos: Database, Unit>, -} - -impl MemPool { - pub const NUM_DBS: u32 = 2; - - pub fn new(env: &heed::Env) -> Result { - let transactions = env.create_database(Some("transactions"))?; - let spent_utxos = env.create_database(Some("spent_utxos"))?; - Ok(Self { - transactions, - spent_utxos, - }) - } - - pub fn put(&self, txn: &mut RwTxn, transaction: &AuthorizedTransaction) -> Result<(), Error> { - println!( - "adding transaction {} to mempool", - transaction.transaction.txid() - ); - for input in &transaction.transaction.inputs { - if self.spent_utxos.get(txn, input)?.is_some() { - return Err(Error::UtxoDoubleSpent); - } - self.spent_utxos.put(txn, input, &())?; - } - self.transactions - .put(txn, &transaction.transaction.txid().into(), &transaction)?; - Ok(()) - } - - pub fn delete(&self, txn: &mut RwTxn, txid: &Txid) -> Result<(), Error> { - self.transactions.delete(txn, txid.into())?; - Ok(()) - } - - pub fn take(&self, txn: &RoTxn, number: usize) -> Result, Error> { - let mut transactions = vec![]; - for item in self.transactions.iter(txn)?.take(number) { - let (_, transaction) = item?; - transactions.push(transaction); - } - Ok(transactions) - } - - pub fn take_all(&self, txn: &RoTxn) -> Result, Error> { - let mut transactions = vec![]; - for item in self.transactions.iter(txn)? { - let (_, transaction) = item?; - transactions.push(transaction); - } - Ok(transactions) - } -} - -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error("heed error")] - Heed(#[from] heed::Error), - #[error("can't add transaction, utxo double spent")] - UtxoDoubleSpent, -} diff --git a/lib/src/net.rs b/lib/src/net.rs deleted file mode 100644 index 730fe7b..0000000 --- a/lib/src/net.rs +++ /dev/null @@ -1,212 +0,0 @@ -use crate::types::{AuthorizedTransaction, Body, Header}; -use quinn::{ClientConfig, Connection, Endpoint, ServerConfig}; -use serde::{Deserialize, Serialize}; -use tokio::sync::RwLock; - -pub use quinn; -use std::collections::HashMap; -use std::{net::SocketAddr, sync::Arc}; - -pub const READ_LIMIT: usize = 1024; - -// State. -// Archive. - -// Keep track of peer state -// Exchange metadata -// Bulk download -// Propagation -// -// Initial block download -// -// 1. Download headers -// 2. Download blocks -// 3. Update the state -#[derive(Clone)] -pub struct Net { - pub client: Endpoint, - pub server: Endpoint, - pub peers: Arc>>, -} - -#[derive(Clone)] -pub struct Peer { - pub state: Arc>>, - pub connection: Connection, -} - -impl Peer { - pub fn heart_beat(&self, state: &PeerState) -> Result<(), Error> { - let message = bincode::serialize(state)?; - self.connection.send_datagram(bytes::Bytes::from(message))?; - Ok(()) - } - - pub async fn request(&self, message: &Request) -> Result { - let (mut send, mut recv) = self.connection.open_bi().await?; - let message = bincode::serialize(message)?; - send.write_all(&message).await?; - send.finish().await?; - let response = recv.read_to_end(READ_LIMIT).await?; - let response: Response = bincode::deserialize(&response)?; - Ok(response) - } -} - -#[derive(Debug, Serialize, Deserialize)] -pub enum Request { - GetBlock { height: u32 }, - PushTransaction { transaction: AuthorizedTransaction }, -} - -#[derive(Debug, Serialize, Deserialize)] -pub enum Response { - Block { header: Header, body: Body }, - NoBlock, - TransactionAccepted, - TransactionRejected, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct PeerState { - pub block_height: u32, -} - -impl Default for PeerState { - fn default() -> Self { - Self { block_height: 0 } - } -} - -impl Net { - pub fn new(bind_addr: SocketAddr) -> Result { - let (server, _) = make_server_endpoint(bind_addr)?; - let client = make_client_endpoint("0.0.0.0:0".parse()?)?; - let peers = Arc::new(RwLock::new(HashMap::new())); - Ok(Net { - server, - client, - peers, - }) - } - pub async fn connect(&self, addr: SocketAddr) -> Result { - for peer in self.peers.read().await.values() { - if peer.connection.remote_address() == addr { - return Err(Error::AlreadyConnected(addr)); - } - } - let connection = self.client.connect(addr, "localhost")?.await?; - let peer = Peer { - state: Arc::new(RwLock::new(None)), - connection, - }; - self.peers - .write() - .await - .insert(peer.connection.stable_id(), peer.clone()); - Ok(peer) - } - - pub async fn disconnect(&self, stable_id: usize) -> Result, Error> { - let peer = self.peers.write().await.remove(&stable_id); - Ok(peer) - } -} - -#[allow(unused)] -pub fn make_client_endpoint(bind_addr: SocketAddr) -> Result { - let client_cfg = configure_client(); - let mut endpoint = Endpoint::client(bind_addr)?; - endpoint.set_default_client_config(client_cfg); - Ok(endpoint) -} - -/// Constructs a QUIC endpoint configured to listen for incoming connections on a certain address -/// and port. -/// -/// ## Returns -/// -/// - a stream of incoming QUIC connections -/// - server certificate serialized into DER format -#[allow(unused)] -pub fn make_server_endpoint(bind_addr: SocketAddr) -> Result<(Endpoint, Vec), Error> { - let (server_config, server_cert) = configure_server()?; - let endpoint = Endpoint::server(server_config, bind_addr)?; - Ok((endpoint, server_cert)) -} - -/// Returns default server configuration along with its certificate. -fn configure_server() -> Result<(ServerConfig, Vec), Error> { - let cert = rcgen::generate_simple_self_signed(vec!["localhost".into()])?; - let cert_der = cert.serialize_der()?; - let priv_key = cert.serialize_private_key_der(); - let priv_key = rustls::PrivateKey(priv_key); - let cert_chain = vec![rustls::Certificate(cert_der.clone())]; - - let mut server_config = ServerConfig::with_single_cert(cert_chain, priv_key)?; - let transport_config = Arc::get_mut(&mut server_config.transport).unwrap(); - transport_config.max_concurrent_uni_streams(1_u8.into()); - - Ok((server_config, cert_der)) -} - -/// Dummy certificate verifier that treats any certificate as valid. -/// NOTE, such verification is vulnerable to MITM attacks, but convenient for testing. -struct SkipServerVerification; - -impl SkipServerVerification { - fn new() -> Arc { - Arc::new(Self) - } -} - -impl rustls::client::ServerCertVerifier for SkipServerVerification { - fn verify_server_cert( - &self, - _end_entity: &rustls::Certificate, - _intermediates: &[rustls::Certificate], - _server_name: &rustls::ServerName, - _scts: &mut dyn Iterator, - _ocsp_response: &[u8], - _now: std::time::SystemTime, - ) -> Result { - Ok(rustls::client::ServerCertVerified::assertion()) - } -} - -fn configure_client() -> ClientConfig { - let crypto = rustls::ClientConfig::builder() - .with_safe_defaults() - .with_custom_certificate_verifier(SkipServerVerification::new()) - .with_no_client_auth(); - - ClientConfig::new(Arc::new(crypto)) -} - -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error("address parse error")] - AddrParse(#[from] std::net::AddrParseError), - #[error("quinn error")] - Io(#[from] std::io::Error), - #[error("connect error")] - Connect(#[from] quinn::ConnectError), - #[error("connection error")] - Connection(#[from] quinn::ConnectionError), - #[error("rcgen")] - RcGen(#[from] rcgen::RcgenError), - #[error("accept error")] - AcceptError, - #[error("read to end error")] - ReadToEnd(#[from] quinn::ReadToEndError), - #[error("write error")] - Write(#[from] quinn::WriteError), - #[error("send datagram error")] - SendDatagram(#[from] quinn::SendDatagramError), - #[error("quinn rustls error")] - QuinnRustls(#[from] quinn::crypto::rustls::Error), - #[error("bincode error")] - Bincode(#[from] bincode::Error), - #[error("already connected to peer at {0}")] - AlreadyConnected(SocketAddr), -} diff --git a/lib/src/node.rs b/lib/src/node.rs deleted file mode 100644 index eeba2f7..0000000 --- a/lib/src/node.rs +++ /dev/null @@ -1,490 +0,0 @@ -use crate::net::{PeerState, Request, Response}; -use crate::{authorization::Authorization, types::*}; -use heed::RoTxn; -use std::{ - collections::{HashMap, HashSet}, - fmt::Debug, - net::SocketAddr, - path::Path, - sync::Arc, -}; -use tokio::sync::RwLock; - -pub const THIS_SIDECHAIN: u8 = 9; - -#[derive(Clone)] -pub struct Node { - net: crate::net::Net, - state: crate::state::State, - archive: crate::archive::Archive, - mempool: crate::mempool::MemPool, - drivechain: bip300301::Drivechain, - env: heed::Env, -} - -impl Node { - pub fn new( - datadir: &Path, - bind_addr: SocketAddr, - main_addr: SocketAddr, - user: &str, - password: &str, - ) -> Result { - let env_path = datadir.join("data.mdb"); - // let _ = std::fs::remove_dir_all(&env_path); - std::fs::create_dir_all(&env_path)?; - let env = heed::EnvOpenOptions::new() - .map_size(10 * 1024 * 1024) // 10MB - .max_dbs( - crate::state::State::NUM_DBS - + crate::archive::Archive::NUM_DBS - + crate::mempool::MemPool::NUM_DBS, - ) - .open(env_path)?; - let state = crate::state::State::new(&env)?; - let archive = crate::archive::Archive::new(&env)?; - let mempool = crate::mempool::MemPool::new(&env)?; - let drivechain = bip300301::Drivechain::new(THIS_SIDECHAIN, main_addr, user, password)?; - let net = crate::net::Net::new(bind_addr)?; - Ok(Self { - net, - state, - archive, - mempool, - drivechain, - env, - }) - } - - pub fn get_height(&self) -> Result { - let txn = self.env.read_txn()?; - Ok(self.archive.get_height(&txn)?) - } - - pub fn get_best_hash(&self) -> Result { - let txn = self.env.read_txn()?; - Ok(self.archive.get_best_hash(&txn)?) - } - - pub fn validate_transaction( - &self, - txn: &RoTxn, - transaction: &AuthorizedTransaction, - ) -> Result { - let filled_transaction = self.state.fill_transaction(txn, &transaction.transaction)?; - for (authorization, spent_utxo) in transaction - .authorizations - .iter() - .zip(filled_transaction.spent_utxos.iter()) - { - if authorization.get_address() != spent_utxo.address { - return Err(crate::state::Error::WrongPubKeyForAddress.into()); - } - } - if Authorization::verify_transaction(transaction).is_err() { - return Err(crate::state::Error::AuthorizationError.into()); - } - let fee = self - .state - .validate_filled_transaction(&filled_transaction)?; - Ok(fee) - } - - pub async fn submit_transaction( - &self, - transaction: &AuthorizedTransaction, - ) -> Result<(), Error> { - { - let mut txn = self.env.write_txn()?; - self.validate_transaction(&txn, &transaction)?; - self.mempool.put(&mut txn, &transaction)?; - txn.commit()?; - } - for peer in self.net.peers.read().await.values() { - peer.request(&Request::PushTransaction { - transaction: transaction.clone(), - }) - .await?; - } - Ok(()) - } - - pub fn get_spent_utxos(&self, outpoints: &[OutPoint]) -> Result, Error> { - let txn = self.env.read_txn()?; - let mut spent = vec![]; - for outpoint in outpoints { - if self.state.utxos.get(&txn, outpoint)?.is_none() { - spent.push(*outpoint); - } - } - Ok(spent) - } - - pub fn get_utxos_by_addresses( - &self, - addresses: &HashSet
, - ) -> Result, Error> { - let txn = self.env.read_txn()?; - let utxos = self.state.get_utxos_by_addresses(&txn, addresses)?; - Ok(utxos) - } - - pub fn get_header(&self, height: u32) -> Result, Error> { - let txn = self.env.read_txn()?; - Ok(self.archive.get_header(&txn, height)?) - } - - pub fn get_body(&self, height: u32) -> Result, Error> { - let txn = self.env.read_txn()?; - Ok(self.archive.get_body(&txn, height)?) - } - - pub fn get_all_transactions(&self) -> Result, Error> { - let txn = self.env.read_txn()?; - let transactions = self.mempool.take_all(&txn)?; - Ok(transactions) - } - - pub fn get_transactions( - &self, - number: usize, - ) -> Result<(Vec, u64), Error> { - let mut txn = self.env.write_txn()?; - let transactions = self.mempool.take(&txn, number)?; - let mut fee: u64 = 0; - let mut returned_transactions = vec![]; - let mut spent_utxos = HashSet::new(); - for transaction in &transactions { - let inputs: HashSet<_> = transaction.transaction.inputs.iter().copied().collect(); - if !spent_utxos.is_disjoint(&inputs) { - println!("UTXO double spent"); - self.mempool - .delete(&mut txn, &transaction.transaction.txid())?; - continue; - } - if self.validate_transaction(&txn, transaction).is_err() { - self.mempool - .delete(&mut txn, &transaction.transaction.txid())?; - continue; - } - let filled_transaction = self - .state - .fill_transaction(&txn, &transaction.transaction)?; - let value_in: u64 = filled_transaction - .spent_utxos - .iter() - .map(GetValue::get_value) - .sum(); - let value_out: u64 = filled_transaction - .transaction - .outputs - .iter() - .map(GetValue::get_value) - .sum(); - fee += value_in - value_out; - returned_transactions.push(transaction.clone()); - spent_utxos.extend(transaction.transaction.inputs.clone()); - } - txn.commit()?; - Ok((returned_transactions, fee)) - } - - pub fn get_pending_withdrawal_bundle(&self) -> Result, Error> { - let txn = self.env.read_txn()?; - Ok(self.state.get_pending_withdrawal_bundle(&txn)?) - } - - pub async fn submit_block(&self, header: &Header, body: &Body) -> Result<(), Error> { - let last_deposit_block_hash = { - let txn = self.env.read_txn()?; - self.state.get_last_deposit_block_hash(&txn)? - }; - let bundle = { - let two_way_peg_data = self - .drivechain - .get_two_way_peg_data(header.prev_main_hash, last_deposit_block_hash) - .await?; - let mut txn = self.env.write_txn()?; - let height = self.archive.get_height(&txn)?; - self.state.validate_body(&txn, &body, height)?; - self.state.connect_body(&mut txn, &body)?; - self.state - .connect_two_way_peg_data(&mut txn, &two_way_peg_data, height)?; - let bundle = self.state.get_pending_withdrawal_bundle(&txn)?; - self.archive.append_header(&mut txn, &header)?; - self.archive.put_body(&mut txn, &header, &body)?; - for transaction in &body.transactions { - self.mempool.delete(&mut txn, &transaction.txid())?; - } - txn.commit()?; - bundle - }; - if let Some(bundle) = bundle { - let _ = self - .drivechain - .broadcast_withdrawal_bundle(bundle.transaction) - .await; - } - Ok(()) - } - - pub async fn connect(&self, addr: SocketAddr) -> Result<(), Error> { - let peer = self.net.connect(addr).await?; - let peer0 = peer.clone(); - let node0 = self.clone(); - tokio::spawn(async move { - loop { - match node0.peer_listen(&peer0).await { - Ok(_) => {} - Err(err) => { - println!("{:?}", err); - break; - } - } - } - }); - let peer0 = peer.clone(); - let node0 = self.clone(); - tokio::spawn(async move { - loop { - match node0.heart_beat_listen(&peer0).await { - Ok(_) => {} - Err(err) => { - println!("{:?}", err); - break; - } - } - } - }); - Ok(()) - } - - pub async fn heart_beat_listen(&self, peer: &crate::net::Peer) -> Result<(), Error> { - let message = match peer.connection.read_datagram().await { - Ok(message) => message, - Err(err) => { - self.net - .peers - .write() - .await - .remove(&peer.connection.stable_id()); - let addr = peer.connection.stable_id(); - println!("connection {addr} closed"); - return Err(crate::net::Error::from(err).into()); - } - }; - let state: PeerState = bincode::deserialize(&message)?; - *peer.state.write().await = Some(state); - Ok(()) - } - - pub async fn peer_listen(&self, peer: &crate::net::Peer) -> Result<(), Error> { - let (mut send, mut recv) = peer - .connection - .accept_bi() - .await - .map_err(crate::net::Error::from)?; - let data = recv - .read_to_end(crate::net::READ_LIMIT) - .await - .map_err(crate::net::Error::from)?; - let message: Request = bincode::deserialize(&data)?; - match message { - Request::GetBlock { height } => { - let (header, body) = { - let txn = self.env.read_txn()?; - ( - self.archive.get_header(&txn, height)?, - self.archive.get_body(&txn, height)?, - ) - }; - let response = match (header, body) { - (Some(header), Some(body)) => Response::Block { header, body }, - (_, _) => Response::NoBlock, - }; - let response = bincode::serialize(&response)?; - send.write_all(&response) - .await - .map_err(crate::net::Error::from)?; - send.finish().await.map_err(crate::net::Error::from)?; - } - Request::PushTransaction { transaction } => { - let valid = { - let txn = self.env.read_txn()?; - self.validate_transaction(&txn, &transaction) - }; - match valid { - Err(err) => { - let response = Response::TransactionRejected; - let response = bincode::serialize(&response)?; - send.write_all(&response) - .await - .map_err(crate::net::Error::from)?; - return Err(err.into()); - } - Ok(_) => { - { - let mut txn = self.env.write_txn()?; - println!("adding transaction to mempool: {:?}", &transaction); - self.mempool.put(&mut txn, &transaction)?; - txn.commit()?; - } - for peer0 in self.net.peers.read().await.values() { - if peer0.connection.stable_id() == peer.connection.stable_id() { - continue; - } - peer0 - .request(&Request::PushTransaction { - transaction: transaction.clone(), - }) - .await?; - } - let response = Response::TransactionAccepted; - let response = bincode::serialize(&response)?; - send.write_all(&response) - .await - .map_err(crate::net::Error::from)?; - return Ok(()); - } - } - } - }; - Ok(()) - } - - pub fn run(&mut self) -> Result<(), Error> { - // Listening to connections. - let node = self.clone(); - tokio::spawn(async move { - loop { - let incoming_conn = node.net.server.accept().await.unwrap(); - let connection = incoming_conn.await.unwrap(); - for peer in node.net.peers.read().await.values() { - if peer.connection.remote_address() == connection.remote_address() { - println!( - "already connected to {} refusing duplicate connection", - connection.remote_address() - ); - connection - .close(crate::net::quinn::VarInt::from_u32(1), b"already connected"); - } - } - if connection.close_reason().is_some() { - continue; - } - println!( - "[server] connection accepted: addr={} id={}", - connection.remote_address(), - connection.stable_id(), - ); - let peer = crate::net::Peer { - state: Arc::new(RwLock::new(None)), - connection, - }; - let node0 = node.clone(); - let peer0 = peer.clone(); - tokio::spawn(async move { - loop { - match node0.peer_listen(&peer0).await { - Ok(_) => {} - Err(err) => { - println!("{:?}", err); - break; - } - } - } - }); - let node0 = node.clone(); - let peer0 = peer.clone(); - tokio::spawn(async move { - loop { - match node0.heart_beat_listen(&peer0).await { - Ok(_) => {} - Err(err) => { - println!("{:?}", err); - break; - } - } - } - }); - node.net - .peers - .write() - .await - .insert(peer.connection.stable_id(), peer); - } - }); - - // Heart beat. - let node = self.clone(); - tokio::spawn(async move { - loop { - for peer in node.net.peers.read().await.values() { - let block_height = { - let txn = node.env.read_txn().unwrap(); - node.archive.get_height(&txn).unwrap() - }; - let state = PeerState { block_height }; - peer.heart_beat(&state).unwrap(); - } - tokio::time::sleep(std::time::Duration::from_secs(1)).await; - } - }); - - // Request missing headers. - let node = self.clone(); - tokio::spawn(async move { - loop { - for peer in node.net.peers.read().await.values() { - if let Some(state) = &peer.state.read().await.as_ref() { - let height = { - let txn = node.env.read_txn().unwrap(); - node.archive.get_height(&txn).unwrap() - }; - if state.block_height > height { - let response = peer - .request(&Request::GetBlock { height: height + 1 }) - .await - .unwrap(); - match response { - Response::Block { header, body } => { - println!("got new header {:?}", &header); - node.submit_block(&header, &body).await.unwrap(); - } - Response::NoBlock => {} - Response::TransactionAccepted => {} - Response::TransactionRejected => {} - }; - } - } - } - tokio::time::sleep(std::time::Duration::from_secs(1)).await; - } - }); - Ok(()) - } -} - -pub trait CustomError {} - -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error("heed error")] - Heed(#[from] heed::Error), - #[error("address parse error")] - AddrParse(#[from] std::net::AddrParseError), - #[error("quinn error")] - Io(#[from] std::io::Error), - #[error("net error")] - Net(#[from] crate::net::Error), - #[error("archive error")] - Archive(#[from] crate::archive::Error), - #[error("drivechain error")] - Drivechain(#[from] bip300301::Error), - #[error("mempool error")] - MemPool(#[from] crate::mempool::Error), - #[error("state error")] - State(#[from] crate::state::Error), - #[error("bincode error")] - Bincode(#[from] bincode::Error), -} diff --git a/lib/src/state.rs b/lib/src/state.rs deleted file mode 100644 index ac425f9..0000000 --- a/lib/src/state.rs +++ /dev/null @@ -1,440 +0,0 @@ -use crate::authorization::Authorization; -use crate::types::*; -use bip300301::TwoWayPegData; -use bip300301::{bitcoin, WithdrawalBundleStatus}; -pub use heed; -use heed::types::*; -use heed::{Database, RoTxn, RwTxn}; -use std::collections::{HashMap, HashSet}; -use std::fmt::Debug; - -#[derive(Clone)] -pub struct State { - pub utxos: Database, SerdeBincode>, - pub pending_withdrawal_bundle: Database, SerdeBincode>, - pub last_withdrawal_bundle_failure_height: Database, OwnedType>, - pub last_deposit_block: Database, SerdeBincode>, -} - -impl State { - pub const NUM_DBS: u32 = 4; - pub const WITHDRAWAL_BUNDLE_FAILURE_GAP: u32 = 4; - - pub fn new(env: &heed::Env) -> Result { - let utxos = env.create_database(Some("utxos"))?; - - let pending_withdrawal_bundle = env.create_database(Some("pending_withdrawal_bundle"))?; - let last_withdrawal_bundle_failure_height = - env.create_database(Some("last_withdrawal_bundle_failure_height"))?; - let last_deposit_block = env.create_database(Some("last_deposit_block"))?; - Ok(Self { - utxos, - pending_withdrawal_bundle, - last_withdrawal_bundle_failure_height, - last_deposit_block, - }) - } - - pub fn get_utxos(&self, txn: &RoTxn) -> Result, Error> { - let mut utxos = HashMap::new(); - for item in self.utxos.iter(txn)? { - let (outpoint, output) = item?; - utxos.insert(outpoint, output); - } - Ok(utxos) - } - - pub fn get_utxos_by_addresses( - &self, - txn: &RoTxn, - addresses: &HashSet
, - ) -> Result, Error> { - let mut utxos = HashMap::new(); - for item in self.utxos.iter(txn)? { - let (outpoint, output) = item?; - if addresses.contains(&output.address) { - utxos.insert(outpoint, output); - } - } - Ok(utxos) - } - - pub fn fill_transaction( - &self, - txn: &RoTxn, - transaction: &Transaction, - ) -> Result { - let mut spent_utxos = vec![]; - for input in &transaction.inputs { - let utxo = self - .utxos - .get(txn, input)? - .ok_or(Error::NoUtxo { outpoint: *input })?; - spent_utxos.push(utxo); - } - Ok(FilledTransaction { - spent_utxos, - transaction: transaction.clone(), - }) - } - - fn collect_withdrawal_bundle( - &self, - txn: &RoTxn, - block_height: u32, - ) -> Result, Error> { - use bitcoin::blockdata::{opcodes, script}; - // Weight of a bundle with 0 outputs. - const BUNDLE_0_WEIGHT: u64 = 504; - // Weight of a single output. - const OUTPUT_WEIGHT: u64 = 128; - // Turns out to be 3121. - const MAX_BUNDLE_OUTPUTS: usize = ((bitcoin::policy::MAX_STANDARD_TX_WEIGHT as u64 - - BUNDLE_0_WEIGHT) - / OUTPUT_WEIGHT) as usize; - - // Aggregate all outputs by destination. - // destination -> (value, mainchain fee, spent_utxos) - let mut address_to_aggregated_withdrawal = HashMap::< - bitcoin::Address, - AggregatedWithdrawal, - >::new(); - for item in self.utxos.iter(txn)? { - let (outpoint, output) = item?; - if let Content::Withdrawal { - value, - ref main_address, - main_fee, - } = output.content - { - let aggregated = address_to_aggregated_withdrawal - .entry(main_address.clone()) - .or_insert(AggregatedWithdrawal { - spent_utxos: HashMap::new(), - main_address: main_address.clone(), - value: 0, - main_fee: 0, - }); - // Add up all values. - aggregated.value += value; - // Set maximum mainchain fee. - if main_fee > aggregated.main_fee { - aggregated.main_fee = main_fee; - } - aggregated.spent_utxos.insert(outpoint, output); - } - } - if address_to_aggregated_withdrawal.is_empty() { - return Ok(None); - } - let mut aggregated_withdrawals: Vec<_> = - address_to_aggregated_withdrawal.into_values().collect(); - aggregated_withdrawals.sort_by_key(|a| std::cmp::Reverse(a.clone())); - let mut fee = 0; - let mut spent_utxos = HashMap::::new(); - let mut bundle_outputs = vec![]; - for aggregated in &aggregated_withdrawals { - if bundle_outputs.len() > MAX_BUNDLE_OUTPUTS { - break; - } - let bundle_output = bitcoin::TxOut { - value: aggregated.value, - script_pubkey: aggregated.main_address.payload.script_pubkey(), - }; - spent_utxos.extend(aggregated.spent_utxos.clone()); - bundle_outputs.push(bundle_output); - fee += aggregated.main_fee; - } - let txin = bitcoin::TxIn { - script_sig: script::Builder::new() - // OP_FALSE == OP_0 - .push_opcode(opcodes::OP_FALSE) - .into_script(), - ..bitcoin::TxIn::default() - }; - // Create return dest output. - // The destination string for the change of a WT^ - let script = script::Builder::new() - .push_opcode(opcodes::all::OP_RETURN) - .push_slice([68; 1]) - .into_script(); - let return_dest_txout = bitcoin::TxOut { - value: 0, - script_pubkey: script, - }; - // Create mainchain fee output. - let script = script::Builder::new() - .push_opcode(opcodes::all::OP_RETURN) - .push_slice(fee.to_le_bytes()) - .into_script(); - let mainchain_fee_txout = bitcoin::TxOut { - value: 0, - script_pubkey: script, - }; - // Create inputs commitment. - let inputs: Vec = [ - // Commit to inputs. - spent_utxos.keys().copied().collect(), - // Commit to block height. - vec![OutPoint::Regular { - txid: [0; 32].into(), - vout: block_height, - }], - ] - .concat(); - let commitment = hash(&inputs); - let script = script::Builder::new() - .push_opcode(opcodes::all::OP_RETURN) - .push_slice(&commitment) - .into_script(); - let inputs_commitment_txout = bitcoin::TxOut { - value: 0, - script_pubkey: script, - }; - let transaction = bitcoin::Transaction { - version: 2, - lock_time: bitcoin::blockdata::locktime::absolute::LockTime::ZERO, - input: vec![txin], - output: [ - vec![ - return_dest_txout, - mainchain_fee_txout, - inputs_commitment_txout, - ], - bundle_outputs, - ] - .concat(), - }; - if transaction.weight().to_wu() > bitcoin::policy::MAX_STANDARD_TX_WEIGHT as u64 { - Err(Error::BundleTooHeavy { - weight: transaction.weight().to_wu(), - max_weight: bitcoin::policy::MAX_STANDARD_TX_WEIGHT as u64, - })?; - } - Ok(Some(WithdrawalBundle { - spent_utxos, - transaction, - })) - } - - pub fn get_pending_withdrawal_bundle( - &self, - txn: &RoTxn, - ) -> Result, Error> { - Ok(self.pending_withdrawal_bundle.get(txn, &0)?) - } - - pub fn validate_filled_transaction( - &self, - transaction: &FilledTransaction, - ) -> Result { - let mut value_in: u64 = 0; - let mut value_out: u64 = 0; - for utxo in &transaction.spent_utxos { - value_in += utxo.get_value(); - } - for output in &transaction.transaction.outputs { - value_out += output.get_value(); - } - if value_out > value_in { - return Err(Error::NotEnoughValueIn); - } - Ok(value_in - value_out) - } - - const LIMIT_GROWTH_EXPONENT: f64 = 1.04; - - pub fn body_sigops_limit(height: u32) -> usize { - // Starting body size limit is 8MB = 8 * 1024 * 1024 B - // 2 input 2 output transaction is 392 B - // 2 * ceil(8 * 1024 * 1024 B / 392 B) = 42800 - const START: usize = 42800; - let month = height / (6 * 24 * 30); - if month < 120 { - (START as f64 * Self::LIMIT_GROWTH_EXPONENT.powi(month as i32)).floor() as usize - } else { - // 1.04 ** 120 = 110.6625 - // So we are rounding up. - START * 111 - } - } - - // in bytes - pub fn body_size_limit(height: u32) -> usize { - // 8MB starting body size limit. - const START: usize = 8 * 1024 * 1024; - let month = height / (6 * 24 * 30); - if month < 120 { - (START as f64 * Self::LIMIT_GROWTH_EXPONENT.powi(month as i32)).floor() as usize - } else { - // 1.04 ** 120 = 110.6625 - // So we are rounding up. - START * 111 - } - } - - pub fn validate_body(&self, txn: &RoTxn, body: &Body, height: u32) -> Result { - if body.authorizations.len() > Self::body_sigops_limit(height) { - return Err(Error::TooManySigops); - } - if bincode::serialize(&body)?.len() > Self::body_size_limit(height) { - return Err(Error::BodyTooLarge); - } - let mut coinbase_value: u64 = 0; - for output in &body.coinbase { - coinbase_value += output.get_value(); - } - let mut total_fees: u64 = 0; - let mut spent_utxos = HashSet::new(); - let filled_transactions: Vec<_> = body - .transactions - .iter() - .map(|t| self.fill_transaction(txn, t)) - .collect::>()?; - for filled_transaction in &filled_transactions { - for input in &filled_transaction.transaction.inputs { - if spent_utxos.contains(input) { - return Err(Error::UtxoDoubleSpent); - } - spent_utxos.insert(*input); - } - total_fees += self.validate_filled_transaction(filled_transaction)?; - } - if coinbase_value > total_fees { - return Err(Error::NotEnoughFees); - } - let spent_utxos = filled_transactions - .iter() - .flat_map(|t| t.spent_utxos.iter()); - for (authorization, spent_utxo) in body.authorizations.iter().zip(spent_utxos) { - if authorization.get_address() != spent_utxo.address { - return Err(Error::WrongPubKeyForAddress); - } - } - if Authorization::verify_body(body).is_err() { - return Err(Error::AuthorizationError); - } - Ok(total_fees) - } - - pub fn get_last_deposit_block_hash( - &self, - txn: &RoTxn, - ) -> Result, Error> { - Ok(self.last_deposit_block.get(&txn, &0)?) - } - - pub fn connect_two_way_peg_data( - &self, - txn: &mut RwTxn, - two_way_peg_data: &TwoWayPegData, - block_height: u32, - ) -> Result<(), Error> { - // Handle deposits. - if let Some(deposit_block_hash) = two_way_peg_data.deposit_block_hash { - self.last_deposit_block.put(txn, &0, &deposit_block_hash)?; - } - for (outpoint, deposit) in &two_way_peg_data.deposits { - if let Ok(address) = deposit.address.parse() { - let outpoint = OutPoint::Deposit(*outpoint); - let output = Output { - address, - content: Content::Value(deposit.value), - }; - self.utxos.put(txn, &outpoint, &output)?; - } - } - - // Handle withdrawals. - let last_withdrawal_bundle_failure_height = self - .last_withdrawal_bundle_failure_height - .get(txn, &0)? - .unwrap_or(0); - if (block_height + 1) - last_withdrawal_bundle_failure_height - > Self::WITHDRAWAL_BUNDLE_FAILURE_GAP - && self.pending_withdrawal_bundle.get(txn, &0)?.is_none() - { - if let Some(bundle) = self.collect_withdrawal_bundle(txn, block_height + 1)? { - for outpoint in bundle.spent_utxos.keys() { - self.utxos.delete(txn, outpoint)?; - } - self.pending_withdrawal_bundle.put(txn, &0, &bundle)?; - } - } - for (txid, status) in &two_way_peg_data.bundle_statuses { - if let Some(bundle) = self.pending_withdrawal_bundle.get(txn, &0)? { - if bundle.transaction.txid() != *txid { - continue; - } - match status { - WithdrawalBundleStatus::Failed => { - self.last_withdrawal_bundle_failure_height.put( - txn, - &0, - &(block_height + 1), - )?; - self.pending_withdrawal_bundle.delete(txn, &0)?; - for (outpoint, output) in &bundle.spent_utxos { - self.utxos.put(txn, outpoint, output)?; - } - } - WithdrawalBundleStatus::Confirmed => { - self.pending_withdrawal_bundle.delete(txn, &0)?; - } - } - } - } - Ok(()) - } - - pub fn connect_body(&self, txn: &mut RwTxn, body: &Body) -> Result<(), Error> { - let merkle_root = body.compute_merkle_root(); - for (vout, output) in body.coinbase.iter().enumerate() { - let outpoint = OutPoint::Coinbase { - merkle_root, - vout: vout as u32, - }; - self.utxos.put(txn, &outpoint, output)?; - } - for transaction in &body.transactions { - let txid = transaction.txid(); - for input in &transaction.inputs { - self.utxos.delete(txn, input)?; - } - for (vout, output) in transaction.outputs.iter().enumerate() { - let outpoint = OutPoint::Regular { - txid, - vout: vout as u32, - }; - self.utxos.put(txn, &outpoint, output)?; - } - } - Ok(()) - } -} - -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error("failed to verify authorization")] - AuthorizationError, - #[error("heed error")] - Heed(#[from] heed::Error), - #[error("binvode error")] - Bincode(#[from] bincode::Error), - #[error("utxo {outpoint} doesn't exist")] - NoUtxo { outpoint: OutPoint }, - #[error("value in is less than value out")] - NotEnoughValueIn, - #[error("total fees less than coinbase value")] - NotEnoughFees, - #[error("utxo double spent")] - UtxoDoubleSpent, - #[error("wrong public key for address")] - WrongPubKeyForAddress, - #[error("bundle too heavy {weight} > {max_weight}")] - BundleTooHeavy { weight: u64, max_weight: u64 }, - #[error("too many sigops")] - TooManySigops, - #[error("body too large")] - BodyTooLarge, -} diff --git a/lib/src/types/mod.rs b/lib/src/types/mod.rs deleted file mode 100644 index 7e3c536..0000000 --- a/lib/src/types/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -use bip300301::bitcoin; -use serde::{Deserialize, Serialize}; -use std::{cmp::Ordering, collections::HashMap}; - -mod address; -mod hashes; -mod types; - -pub use blake3; -pub use bs58; -pub use serde; -pub use types::*; - -/* -// Replace () with a type (usually an enum) for output data specific for your sidechain. -pub type Output = types::Output<()>; -pub type Transaction = types::Transaction<()>; -pub type FilledTransaction = types::FilledTransaction<()>; -pub type AuthorizedTransaction = types::AuthorizedTransaction; -pub type Body = types::Body; -*/ - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Header { - pub merkle_root: MerkleRoot, - pub prev_side_hash: BlockHash, - pub prev_main_hash: bitcoin::BlockHash, -} - -impl Header { - pub fn hash(&self) -> BlockHash { - types::hash(self).into() - } -} - -#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)] -pub enum WithdrawalBundleStatus { - Failed, - Confirmed, -} - -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct WithdrawalBundle { - pub spent_utxos: HashMap, - pub transaction: bitcoin::Transaction, -} - -#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct TwoWayPegData { - pub deposits: HashMap, - pub deposit_block_hash: Option, - pub bundle_statuses: HashMap, -} - -/* -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct DisconnectData { - pub spent_utxos: HashMap, - pub deposits: Vec, - pub pending_bundles: Vec, - pub spent_bundles: HashMap>, - pub spent_withdrawals: HashMap, - pub failed_withdrawals: Vec, -} -*/ - -#[derive(Eq, PartialEq, Clone, Debug)] -pub struct AggregatedWithdrawal { - pub spent_utxos: HashMap, - pub main_address: bitcoin::Address, - pub value: u64, - pub main_fee: u64, -} - -impl Ord for AggregatedWithdrawal { - fn cmp(&self, other: &Self) -> Ordering { - if self == other { - Ordering::Equal - } else if self.main_fee > other.main_fee - || self.value > other.value - || self.main_address > other.main_address - { - Ordering::Greater - } else { - Ordering::Less - } - } -} - -impl PartialOrd for AggregatedWithdrawal { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} diff --git a/lib/src/types/types.rs b/lib/src/types/types.rs deleted file mode 100644 index e297b27..0000000 --- a/lib/src/types/types.rs +++ /dev/null @@ -1,201 +0,0 @@ -use crate::authorization::Authorization; -pub use crate::types::address::*; -pub use crate::types::hashes::*; -use bip300301::bitcoin; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; - -#[derive(Hash, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub enum OutPoint { - // Created by transactions. - Regular { txid: Txid, vout: u32 }, - // Created by block bodies. - Coinbase { merkle_root: MerkleRoot, vout: u32 }, - // Created by mainchain deposits. - Deposit(bitcoin::OutPoint), -} - -impl std::fmt::Display for OutPoint { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Regular { txid, vout } => write!(f, "regular {txid} {vout}"), - Self::Coinbase { merkle_root, vout } => write!(f, "coinbase {merkle_root} {vout}"), - Self::Deposit(bitcoin::OutPoint { txid, vout }) => write!(f, "deposit {txid} {vout}"), - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct Output { - pub address: Address, - pub content: Content, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum Content { - Value(u64), - Withdrawal { - value: u64, - main_fee: u64, - main_address: bitcoin::Address, - }, -} - -impl Content { - pub fn is_value(&self) -> bool { - matches!(self, Self::Value(_)) - } - pub fn is_withdrawal(&self) -> bool { - matches!(self, Self::Withdrawal { .. }) - } -} - -impl GetValue for Output { - #[inline(always)] - fn get_value(&self) -> u64 { - self.content.get_value() - } -} - -impl GetValue for Content { - #[inline(always)] - fn get_value(&self) -> u64 { - match self { - Self::Value(value) => *value, - Self::Withdrawal { value, .. } => *value, - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Transaction { - pub inputs: Vec, - pub outputs: Vec, -} - -impl Transaction { - pub fn txid(&self) -> Txid { - hash(self).into() - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct FilledTransaction { - pub transaction: Transaction, - pub spent_utxos: Vec, -} - -impl FilledTransaction { - pub fn get_value_in(&self) -> u64 { - self.spent_utxos.iter().map(GetValue::get_value).sum() - } - - pub fn get_value_out(&self) -> u64 { - self.transaction - .outputs - .iter() - .map(GetValue::get_value) - .sum() - } - - pub fn get_fee(&self) -> Option { - let value_in = self.get_value_in(); - let value_out = self.get_value_out(); - if value_in < value_out { - None - } else { - Some(value_in - value_out) - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct AuthorizedTransaction { - pub transaction: Transaction, - /// Authorization is called witness in Bitcoin. - pub authorizations: Vec, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Body { - pub coinbase: Vec, - pub transactions: Vec, - pub authorizations: Vec, -} - -impl Body { - pub fn new(authorized_transactions: Vec, coinbase: Vec) -> Self { - let mut authorizations = Vec::with_capacity( - authorized_transactions - .iter() - .map(|t| t.transaction.inputs.len()) - .sum(), - ); - let mut transactions = Vec::with_capacity(authorized_transactions.len()); - for at in authorized_transactions.into_iter() { - authorizations.extend(at.authorizations); - transactions.push(at.transaction); - } - Self { - coinbase, - transactions, - authorizations, - } - } - - pub fn compute_merkle_root(&self) -> MerkleRoot { - // FIXME: Compute actual merkle root instead of just a hash. - hash(&(&self.coinbase, &self.transactions)).into() - } - - pub fn get_inputs(&self) -> Vec { - self.transactions - .iter() - .flat_map(|tx| tx.inputs.iter()) - .copied() - .collect() - } - - pub fn get_outputs(&self) -> HashMap { - let mut outputs = HashMap::new(); - let merkle_root = self.compute_merkle_root(); - for (vout, output) in self.coinbase.iter().enumerate() { - let vout = vout as u32; - let outpoint = OutPoint::Coinbase { merkle_root, vout }; - outputs.insert(outpoint, output.clone()); - } - for transaction in &self.transactions { - let txid = transaction.txid(); - for (vout, output) in transaction.outputs.iter().enumerate() { - let vout = vout as u32; - let outpoint = OutPoint::Regular { txid, vout }; - outputs.insert(outpoint, output.clone()); - } - } - outputs - } - - pub fn get_coinbase_value(&self) -> u64 { - self.coinbase.iter().map(|output| output.get_value()).sum() - } -} - -pub trait GetAddress { - fn get_address(&self) -> Address; -} - -pub trait GetValue { - fn get_value(&self) -> u64; -} - -impl GetValue for () { - fn get_value(&self) -> u64 { - 0 - } -} - -pub trait Verify { - type Error; - fn verify_transaction(transaction: &AuthorizedTransaction) -> Result<(), Self::Error>; - fn verify_body(body: &Body) -> Result<(), Self::Error>; -} diff --git a/lib/state.rs b/lib/state.rs new file mode 100644 index 0000000..2c826df --- /dev/null +++ b/lib/state.rs @@ -0,0 +1,1097 @@ +use std::collections::{BTreeMap, HashMap, HashSet}; + +use heed::{types::SerdeBincode, Database, RoTxn, RwTxn}; + +use bip300301::{ + bitcoin::{ + self, transaction::Version as BitcoinTxVersion, Amount as BitcoinAmount, + }, + TwoWayPegData, WithdrawalBundleStatus, +}; +use rustreexo::accumulator::{node_hash::NodeHash, proof::Proof}; +use serde::{Deserialize, Serialize}; + +use crate::{ + authorization::Authorization, + types::{ + hash, Accumulator, Address, AggregatedWithdrawal, + AuthorizedTransaction, BlockHash, Body, FilledTransaction, GetAddress, + GetValue, Header, InPoint, MerkleRoot, OutPoint, Output, OutputContent, + PointedOutput, SpentOutput, Transaction, Txid, Verify, + WithdrawalBundle, + }, +}; + +#[derive(Debug, thiserror::Error)] +pub enum InvalidHeaderError { + #[error("expected block hash {expected}, but computed {computed}")] + BlockHash { + expected: BlockHash, + computed: BlockHash, + }, + #[error("expected previous sidechain block hash {expected}, but received {received}")] + PrevSideHash { + expected: BlockHash, + received: BlockHash, + }, +} + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("failed to verify authorization")] + AuthorizationError, + #[error("binvode error")] + Bincode(#[from] bincode::Error), + #[error("body too large")] + BodyTooLarge, + #[error("bundle too heavy {weight} > {max_weight}")] + BundleTooHeavy { weight: u64, max_weight: u64 }, + #[error("invalid body: expected merkle root {expected}, but computed {computed}")] + InvalidBody { + expected: MerkleRoot, + computed: MerkleRoot, + }, + #[error("invalid header: {0}")] + InvalidHeader(InvalidHeaderError), + #[error("heed error")] + Heed(#[from] heed::Error), + #[error("deposit block doesn't exist")] + NoDepositBlock, + #[error("total fees less than coinbase value")] + NotEnoughFees, + #[error("no tip")] + NoTip, + #[error("stxo {outpoint} doesn't exist")] + NoStxo { outpoint: OutPoint }, + #[error("value in is less than value out")] + NotEnoughValueIn, + #[error("utxo {outpoint} doesn't exist")] + NoUtxo { outpoint: OutPoint }, + #[error("utreexo error: {0}")] + Utreexo(String), + #[error("Utreexo proof verification failed for tx {txid}")] + UtreexoProofFailed { txid: Txid }, + #[error("Computed Utreexo roots do not match the header roots")] + UtreexoRootsMismatch, + #[error("utxo double spent")] + UtxoDoubleSpent, + #[error("too many sigops")] + TooManySigops, + #[error("wrong public key for address")] + WrongPubKeyForAddress, +} + +/// Unit key. LMDB can't use zero-sized keys, so this encodes to a single byte +#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)] +pub struct UnitKey; + +impl<'de> Deserialize<'de> for UnitKey { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + // Deserialize any byte (ignoring it) and return UnitKey + let _ = u8::deserialize(deserializer)?; + Ok(UnitKey) + } +} + +impl Serialize for UnitKey { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + // Always serialize to the same arbitrary byte + serializer.serialize_u8(0x69) + } +} + +#[derive(Clone)] +pub struct State { + /// Current tip + tip: Database, SerdeBincode>, + /// Current height + height: Database, SerdeBincode>, + pub utxos: Database, SerdeBincode>, + pub stxos: Database, SerdeBincode>, + /// Pending withdrawal bundle and block height + pub pending_withdrawal_bundle: + Database, SerdeBincode<(WithdrawalBundle, u32)>>, + /// Mapping from block height to withdrawal bundle and status + pub withdrawal_bundles: Database< + SerdeBincode, + SerdeBincode<(WithdrawalBundle, WithdrawalBundleStatus)>, + >, + /// deposit blocks and the height at which they were applied, keyed sequentially + pub deposit_blocks: + Database, SerdeBincode<(bitcoin::BlockHash, u32)>>, + pub utreexo_accumulator: + Database, SerdeBincode>, +} + +impl State { + pub const NUM_DBS: u32 = 8; + pub const WITHDRAWAL_BUNDLE_FAILURE_GAP: u32 = 4; + + pub fn new(env: &heed::Env) -> Result { + let mut rwtxn = env.write_txn()?; + let tip = env.create_database(&mut rwtxn, Some("tip"))?; + let height = env.create_database(&mut rwtxn, Some("height"))?; + let utxos = env.create_database(&mut rwtxn, Some("utxos"))?; + let stxos = env.create_database(&mut rwtxn, Some("stxos"))?; + let pending_withdrawal_bundle = + env.create_database(&mut rwtxn, Some("pending_withdrawal_bundle"))?; + let withdrawal_bundles = + env.create_database(&mut rwtxn, Some("withdrawal_bundles"))?; + let deposit_blocks = + env.create_database(&mut rwtxn, Some("deposit_blocks"))?; + let utreexo_accumulator = + env.create_database(&mut rwtxn, Some("utreexo_accumulator"))?; + rwtxn.commit()?; + Ok(Self { + tip, + height, + utxos, + stxos, + pending_withdrawal_bundle, + withdrawal_bundles, + deposit_blocks, + utreexo_accumulator, + }) + } + + pub fn get_tip(&self, rotxn: &RoTxn) -> Result { + let tip = self.tip.get(rotxn, &UnitKey)?.unwrap_or_default(); + Ok(tip) + } + + pub fn get_height(&self, rotxn: &RoTxn) -> Result { + let height = self.height.get(rotxn, &UnitKey)?.unwrap_or_default(); + Ok(height) + } + + pub fn get_utxos( + &self, + txn: &RoTxn, + ) -> Result, Error> { + let mut utxos = HashMap::new(); + for item in self.utxos.iter(txn)? { + let (outpoint, output) = item?; + utxos.insert(outpoint, output); + } + Ok(utxos) + } + + pub fn get_utxos_by_addresses( + &self, + txn: &RoTxn, + addresses: &HashSet
, + ) -> Result, Error> { + let mut utxos = HashMap::new(); + for item in self.utxos.iter(txn)? { + let (outpoint, output) = item?; + if addresses.contains(&output.address) { + utxos.insert(outpoint, output); + } + } + Ok(utxos) + } + + /// Get the latest failed withdrawal bundle, and the height at which it failed + fn get_latest_failed_withdrawal_bundle( + &self, + rotxn: &RoTxn, + ) -> Result, Error> { + for item in self.withdrawal_bundles.rev_iter(rotxn)? { + if let (height, (bundle, WithdrawalBundleStatus::Failed)) = item? { + let res = Some((height, bundle)); + return Ok(res); + } + } + Ok(None) + } + + /// Get the current Utreexo accumulator + pub fn get_accumulator(&self, rotxn: &RoTxn) -> Result { + let accumulator = self + .utreexo_accumulator + .get(rotxn, &UnitKey)? + .unwrap_or_default(); + Ok(accumulator) + } + + /// Regenerate utreexo proof for a tx + pub fn regenerate_proof( + &self, + rotxn: &RoTxn, + tx: &mut Transaction, + ) -> Result<(), Error> { + let accumulator = self.get_accumulator(rotxn)?; + let targets: Vec<_> = tx + .inputs + .iter() + .map(|(_, utxo_hash)| utxo_hash.into()) + .collect(); + tx.proof = accumulator.0.prove(&targets).map_err(Error::Utreexo)?; + Ok(()) + } + + /// Get a Utreexo proof for the provided utxos + pub fn get_utreexo_proof<'a, Utxos>( + &self, + rotxn: &RoTxn, + utxos: Utxos, + ) -> Result + where + Utxos: IntoIterator, + { + let accumulator = self.get_accumulator(rotxn)?; + let targets: Vec = + utxos.into_iter().map(NodeHash::from).collect(); + let proof = accumulator.0.prove(&targets).map_err(Error::Utreexo)?; + Ok(proof) + } + + pub fn fill_transaction( + &self, + txn: &RoTxn, + transaction: &Transaction, + ) -> Result { + let mut spent_utxos = vec![]; + for (outpoint, _) in &transaction.inputs { + let utxo = self.utxos.get(txn, outpoint)?.ok_or(Error::NoUtxo { + outpoint: *outpoint, + })?; + spent_utxos.push(utxo); + } + Ok(FilledTransaction { + spent_utxos, + transaction: transaction.clone(), + }) + } + + fn collect_withdrawal_bundle( + &self, + txn: &RoTxn, + block_height: u32, + ) -> Result, Error> { + use bitcoin::blockdata::{opcodes, script}; + // Weight of a bundle with 0 outputs. + const BUNDLE_0_WEIGHT: u64 = 504; + // Weight of a single output. + const OUTPUT_WEIGHT: u64 = 128; + // Turns out to be 3121. + const MAX_BUNDLE_OUTPUTS: usize = + ((bitcoin::policy::MAX_STANDARD_TX_WEIGHT as u64 - BUNDLE_0_WEIGHT) + / OUTPUT_WEIGHT) as usize; + + // Aggregate all outputs by destination. + // destination -> (value, mainchain fee, spent_utxos) + let mut address_to_aggregated_withdrawal = HashMap::< + bitcoin::Address, + AggregatedWithdrawal, + >::new(); + for item in self.utxos.iter(txn)? { + let (outpoint, output) = item?; + if let OutputContent::Withdrawal { + value, + ref main_address, + main_fee, + } = output.content + { + let aggregated = address_to_aggregated_withdrawal + .entry(main_address.clone()) + .or_insert(AggregatedWithdrawal { + spend_utxos: HashMap::new(), + main_address: main_address.clone(), + value: 0, + main_fee: 0, + }); + // Add up all values. + aggregated.value += value; + // Set maximum mainchain fee. + if main_fee > aggregated.main_fee { + aggregated.main_fee = main_fee; + } + aggregated.spend_utxos.insert(outpoint, output); + } + } + if address_to_aggregated_withdrawal.is_empty() { + return Ok(None); + } + let mut aggregated_withdrawals: Vec<_> = + address_to_aggregated_withdrawal.into_values().collect(); + aggregated_withdrawals.sort_by_key(|a| std::cmp::Reverse(a.clone())); + let mut fee = 0; + let mut spend_utxos = BTreeMap::::new(); + let mut bundle_outputs = vec![]; + for aggregated in &aggregated_withdrawals { + if bundle_outputs.len() > MAX_BUNDLE_OUTPUTS { + break; + } + let bundle_output = bitcoin::TxOut { + value: BitcoinAmount::from_sat(aggregated.value), + script_pubkey: aggregated + .main_address + .payload() + .script_pubkey(), + }; + spend_utxos.extend(aggregated.spend_utxos.clone()); + bundle_outputs.push(bundle_output); + fee += aggregated.main_fee; + } + let txin = bitcoin::TxIn { + script_sig: script::Builder::new() + // OP_FALSE == OP_0 + .push_opcode(opcodes::OP_FALSE) + .into_script(), + ..bitcoin::TxIn::default() + }; + // Create return dest output. + // The destination string for the change of a WT^ + let script = script::Builder::new() + .push_opcode(opcodes::all::OP_RETURN) + .push_slice([68; 1]) + .into_script(); + let return_dest_txout = bitcoin::TxOut { + value: BitcoinAmount::ZERO, + script_pubkey: script, + }; + // Create mainchain fee output. + let script = script::Builder::new() + .push_opcode(opcodes::all::OP_RETURN) + .push_slice(fee.to_le_bytes()) + .into_script(); + let mainchain_fee_txout = bitcoin::TxOut { + value: BitcoinAmount::ZERO, + script_pubkey: script, + }; + // Create inputs commitment. + let inputs: Vec = [ + // Commit to inputs. + spend_utxos.keys().copied().collect(), + // Commit to block height. + vec![OutPoint::Regular { + txid: [0; 32].into(), + vout: block_height, + }], + ] + .concat(); + let commitment = hash(&inputs); + let script = script::Builder::new() + .push_opcode(opcodes::all::OP_RETURN) + .push_slice(commitment) + .into_script(); + let inputs_commitment_txout = bitcoin::TxOut { + value: BitcoinAmount::ZERO, + script_pubkey: script, + }; + let transaction = bitcoin::Transaction { + version: BitcoinTxVersion::TWO, + lock_time: bitcoin::blockdata::locktime::absolute::LockTime::ZERO, + input: vec![txin], + output: [ + vec![ + return_dest_txout, + mainchain_fee_txout, + inputs_commitment_txout, + ], + bundle_outputs, + ] + .concat(), + }; + if transaction.weight().to_wu() + > bitcoin::policy::MAX_STANDARD_TX_WEIGHT as u64 + { + Err(Error::BundleTooHeavy { + weight: transaction.weight().to_wu(), + max_weight: bitcoin::policy::MAX_STANDARD_TX_WEIGHT as u64, + })?; + } + Ok(Some(WithdrawalBundle { + spend_utxos, + transaction, + })) + } + + /// Get pending withdrawal bundle and block height + pub fn get_pending_withdrawal_bundle( + &self, + txn: &RoTxn, + ) -> Result, Error> { + Ok(self.pending_withdrawal_bundle.get(txn, &UnitKey)?) + } + + pub fn validate_filled_transaction( + &self, + transaction: &FilledTransaction, + ) -> Result { + let mut value_in: u64 = 0; + let mut value_out: u64 = 0; + for utxo in &transaction.spent_utxos { + value_in += utxo.get_value(); + } + for output in &transaction.transaction.outputs { + value_out += output.get_value(); + } + if value_out > value_in { + return Err(Error::NotEnoughValueIn); + } + Ok(value_in - value_out) + } + + pub fn validate_transaction( + &self, + rotxn: &RoTxn, + transaction: &AuthorizedTransaction, + ) -> Result { + let filled_transaction = + self.fill_transaction(rotxn, &transaction.transaction)?; + for (authorization, spent_utxo) in transaction + .authorizations + .iter() + .zip(filled_transaction.spent_utxos.iter()) + { + if authorization.get_address() != spent_utxo.address { + return Err(Error::WrongPubKeyForAddress); + } + } + if Authorization::verify_transaction(transaction).is_err() { + return Err(Error::AuthorizationError); + } + let fee = self.validate_filled_transaction(&filled_transaction)?; + Ok(fee) + } + + const LIMIT_GROWTH_EXPONENT: f64 = 1.04; + + pub fn body_sigops_limit(height: u32) -> usize { + // Starting body size limit is 8MB = 8 * 1024 * 1024 B + // 2 input 2 output transaction is 392 B + // 2 * ceil(8 * 1024 * 1024 B / 392 B) = 42800 + const START: usize = 42800; + let month = height / (6 * 24 * 30); + if month < 120 { + (START as f64 * Self::LIMIT_GROWTH_EXPONENT.powi(month as i32)) + .floor() as usize + } else { + // 1.04 ** 120 = 110.6625 + // So we are rounding up. + START * 111 + } + } + + // in bytes + pub fn body_size_limit(height: u32) -> usize { + // 8MB starting body size limit. + const START: usize = 8 * 1024 * 1024; + let month = height / (6 * 24 * 30); + if month < 120 { + (START as f64 * Self::LIMIT_GROWTH_EXPONENT.powi(month as i32)) + .floor() as usize + } else { + // 1.04 ** 120 = 110.6625 + // So we are rounding up. + START * 111 + } + } + + pub fn validate_block( + &self, + rotxn: &RoTxn, + header: &Header, + body: &Body, + ) -> Result { + let tip_hash = self.get_tip(rotxn)?; + if header.prev_side_hash != tip_hash { + let err = InvalidHeaderError::PrevSideHash { + expected: tip_hash, + received: header.prev_side_hash, + }; + return Err(Error::InvalidHeader(err)); + }; + let height = self.get_height(rotxn)?; + if body.authorizations.len() > Self::body_sigops_limit(height) { + return Err(Error::TooManySigops); + } + if bincode::serialize(&body)?.len() > Self::body_size_limit(height) { + return Err(Error::BodyTooLarge); + } + let mut accumulator = self + .utreexo_accumulator + .get(rotxn, &UnitKey)? + .unwrap_or_default(); + // New leaves for the accumulator + let mut accumulator_add = Vec::::new(); + // Accumulator leaves to delete + let mut accumulator_del = Vec::::new(); + let mut coinbase_value: u64 = 0; + let merkle_root = body.compute_merkle_root(); + if merkle_root != header.merkle_root { + let err = Error::InvalidBody { + expected: merkle_root, + computed: header.merkle_root, + }; + return Err(err); + } + for (vout, output) in body.coinbase.iter().enumerate() { + coinbase_value += output.get_value(); + let outpoint = OutPoint::Coinbase { + merkle_root, + vout: vout as u32, + }; + let pointed_output = PointedOutput { + outpoint, + output: output.clone(), + }; + accumulator_add.push((&pointed_output).into()); + } + let mut total_fees: u64 = 0; + let mut spent_utxos = HashSet::new(); + let filled_transactions: Vec<_> = body + .transactions + .iter() + .map(|t| self.fill_transaction(rotxn, t)) + .collect::>()?; + for filled_transaction in &filled_transactions { + let txid = filled_transaction.transaction.txid(); + // hashes of spent utxos, used to verify the utreexo proof + let mut spent_utxo_hashes = Vec::::new(); + for (outpoint, utxo_hash) in &filled_transaction.transaction.inputs + { + if spent_utxos.contains(outpoint) { + return Err(Error::UtxoDoubleSpent); + } + spent_utxos.insert(*outpoint); + spent_utxo_hashes.push(utxo_hash.into()); + accumulator_del.push(utxo_hash.into()); + } + for (vout, output) in + filled_transaction.transaction.outputs.iter().enumerate() + { + let outpoint = OutPoint::Regular { + txid, + vout: vout as u32, + }; + let pointed_output = PointedOutput { + outpoint, + output: output.clone(), + }; + accumulator_add.push((&pointed_output).into()); + } + total_fees += + self.validate_filled_transaction(filled_transaction)?; + // verify utreexo proof + if !accumulator + .0 + .verify( + &filled_transaction.transaction.proof, + &spent_utxo_hashes, + ) + .map_err(Error::Utreexo)? + { + return Err(Error::UtreexoProofFailed { txid }); + } + } + if coinbase_value > total_fees { + return Err(Error::NotEnoughFees); + } + let spent_utxos = filled_transactions + .iter() + .flat_map(|t| t.spent_utxos.iter()); + for (authorization, spent_utxo) in + body.authorizations.iter().zip(spent_utxos) + { + if authorization.get_address() != spent_utxo.address { + return Err(Error::WrongPubKeyForAddress); + } + } + if Authorization::verify_body(body).is_err() { + return Err(Error::AuthorizationError); + } + accumulator + .0 + .modify(&accumulator_add, &accumulator_del) + .map_err(Error::Utreexo)?; + let roots: Vec = accumulator + .0 + .get_roots() + .iter() + .map(|node| node.get_data()) + .collect(); + if roots != header.roots { + return Err(Error::UtreexoRootsMismatch); + } + Ok(total_fees) + } + + pub fn get_last_deposit_block_hash( + &self, + rotxn: &RoTxn, + ) -> Result, Error> { + let block_hash = self + .deposit_blocks + .last(rotxn)? + .map(|(_, (block_hash, _))| block_hash); + Ok(block_hash) + } + + pub fn connect_two_way_peg_data( + &self, + rwtxn: &mut RwTxn, + two_way_peg_data: &TwoWayPegData, + ) -> Result<(), Error> { + let block_height = self.get_height(rwtxn)?; + let mut accumulator = self + .utreexo_accumulator + .get(rwtxn, &UnitKey)? + .unwrap_or_default(); + // New leaves for the accumulator + let mut accumulator_add = Vec::::new(); + // Accumulator leaves to delete + let mut accumulator_del = HashSet::::new(); + // Handle deposits. + if let Some(deposit_block_hash) = two_way_peg_data.deposit_block_hash { + let deposit_block_seq_idx = self + .deposit_blocks + .last(rwtxn)? + .map_or(0, |(seq_idx, _)| seq_idx + 1); + self.deposit_blocks.put( + rwtxn, + &deposit_block_seq_idx, + &(deposit_block_hash, block_height - 1), + )?; + } + for deposit in &two_way_peg_data.deposits { + if let Ok(address) = deposit.output.address.parse() { + let outpoint = OutPoint::Deposit(deposit.outpoint); + let output = Output { + address, + content: OutputContent::Value(deposit.output.value), + }; + self.utxos.put(rwtxn, &outpoint, &output)?; + let utxo_hash = hash(&PointedOutput { outpoint, output }); + accumulator_add.push(utxo_hash.into()) + } + } + + // Handle withdrawals. + let last_withdrawal_bundle_failure_height = self + .get_latest_failed_withdrawal_bundle(rwtxn)? + .map(|(height, _bundle)| height) + .unwrap_or_default(); + if block_height - last_withdrawal_bundle_failure_height + > Self::WITHDRAWAL_BUNDLE_FAILURE_GAP + && self + .pending_withdrawal_bundle + .get(rwtxn, &UnitKey)? + .is_none() + { + if let Some(bundle) = + self.collect_withdrawal_bundle(rwtxn, block_height)? + { + for (outpoint, spend_output) in &bundle.spend_utxos { + let utxo_hash = hash(&PointedOutput { + outpoint: *outpoint, + output: spend_output.clone(), + }); + accumulator_del.insert(utxo_hash.into()); + self.utxos.delete(rwtxn, outpoint)?; + let txid = bundle.transaction.txid(); + let spent_output = SpentOutput { + output: spend_output.clone(), + inpoint: InPoint::Withdrawal { txid }, + }; + self.stxos.put(rwtxn, outpoint, &spent_output)?; + } + self.pending_withdrawal_bundle.put( + rwtxn, + &UnitKey, + &(bundle, block_height), + )?; + } + } + for (txid, status) in &two_way_peg_data.bundle_statuses { + if let Some((bundle, bundle_block_height)) = + self.pending_withdrawal_bundle.get(rwtxn, &UnitKey)? + { + if bundle.transaction.txid() != *txid { + continue; + } + assert_eq!(bundle_block_height, block_height); + self.withdrawal_bundles.put( + rwtxn, + &block_height, + &(bundle.clone(), *status), + )?; + self.pending_withdrawal_bundle.delete(rwtxn, &UnitKey)?; + if let WithdrawalBundleStatus::Failed = status { + for (outpoint, output) in &bundle.spend_utxos { + self.stxos.delete(rwtxn, outpoint)?; + self.utxos.put(rwtxn, outpoint, output)?; + let utxo_hash = hash(&PointedOutput { + outpoint: *outpoint, + output: output.clone(), + }); + accumulator_del.remove(&utxo_hash.into()); + } + } + } + } + let accumulator_del: Vec<_> = accumulator_del.into_iter().collect(); + accumulator + .0 + .modify(&accumulator_add, &accumulator_del) + .map_err(Error::Utreexo)?; + self.utreexo_accumulator + .put(rwtxn, &UnitKey, &accumulator)?; + Ok(()) + } + + pub fn disconnect_two_way_peg_data( + &self, + rwtxn: &mut RwTxn, + two_way_peg_data: &TwoWayPegData, + ) -> Result<(), Error> { + let block_height = self.get_height(rwtxn)?; + let mut accumulator = self + .utreexo_accumulator + .get(rwtxn, &UnitKey)? + .unwrap_or_default(); + // New leaves for the accumulator + let mut accumulator_add = Vec::::new(); + // Accumulator leaves to delete + let mut accumulator_del = HashSet::::new(); + + // Restore pending withdrawal bundle + for (txid, status) in two_way_peg_data.bundle_statuses.iter().rev() { + if let Some(( + latest_bundle_height, + (latest_bundle, latest_bundle_status), + )) = self.withdrawal_bundles.last(rwtxn)? + { + if latest_bundle.transaction.txid() != *txid { + continue; + } + assert_eq!(*status, latest_bundle_status); + assert_eq!(latest_bundle_height, block_height); + self.withdrawal_bundles + .delete(rwtxn, &latest_bundle_height)?; + self.pending_withdrawal_bundle.put( + rwtxn, + &UnitKey, + &(latest_bundle.clone(), latest_bundle_height), + )?; + if *status == WithdrawalBundleStatus::Failed { + for (outpoint, output) in + latest_bundle.spend_utxos.into_iter().rev() + { + let spent_output = SpentOutput { + output: output.clone(), + inpoint: InPoint::Withdrawal { txid: *txid }, + }; + self.stxos.put(rwtxn, &outpoint, &spent_output)?; + if self.utxos.delete(rwtxn, &outpoint)? { + return Err(Error::NoUtxo { outpoint }); + }; + let utxo_hash = + hash(&PointedOutput { outpoint, output }); + accumulator_add.push(utxo_hash.into()); + } + } + } + } + // Handle withdrawals. + let last_withdrawal_bundle_failure_height = self + .get_latest_failed_withdrawal_bundle(rwtxn)? + .map(|(height, _bundle)| height) + .unwrap_or_default(); + if block_height - last_withdrawal_bundle_failure_height + > Self::WITHDRAWAL_BUNDLE_FAILURE_GAP + && let Some((bundle, bundle_height)) = + self.pending_withdrawal_bundle.get(rwtxn, &UnitKey)? + && bundle_height == block_height + { + self.pending_withdrawal_bundle.delete(rwtxn, &UnitKey)?; + for (outpoint, output) in bundle.spend_utxos.into_iter().rev() { + let utxo_hash = hash(&PointedOutput { + outpoint, + output: output.clone(), + }); + accumulator_add.push(utxo_hash.into()); + if !self.stxos.delete(rwtxn, &outpoint)? { + return Err(Error::NoStxo { outpoint }); + }; + self.utxos.put(rwtxn, &outpoint, &output)?; + } + } + // Handle deposits. + if let Some(deposit_block_hash) = two_way_peg_data.deposit_block_hash { + let ( + last_deposit_block_seq_idx, + (last_deposit_block_hash, last_deposit_block_height), + ) = self + .deposit_blocks + .last(rwtxn)? + .ok_or(Error::NoDepositBlock)?; + assert_eq!(deposit_block_hash, last_deposit_block_hash); + assert_eq!(block_height - 1, last_deposit_block_height); + if !self + .deposit_blocks + .delete(rwtxn, &last_deposit_block_seq_idx)? + { + return Err(Error::NoDepositBlock); + }; + } + for deposit in two_way_peg_data.deposits.iter().rev() { + if let Ok(address) = deposit.output.address.parse() { + let outpoint = OutPoint::Deposit(deposit.outpoint); + let output = Output { + address, + content: OutputContent::Value(deposit.output.value), + }; + if !self.utxos.delete(rwtxn, &outpoint)? { + return Err(Error::NoUtxo { outpoint }); + } + let utxo_hash = hash(&PointedOutput { outpoint, output }); + accumulator_del.insert(utxo_hash.into()); + } + } + let accumulator_del: Vec<_> = accumulator_del.into_iter().collect(); + accumulator + .0 + .modify(&accumulator_add, &accumulator_del) + .map_err(Error::Utreexo)?; + self.utreexo_accumulator + .put(rwtxn, &UnitKey, &accumulator)?; + Ok(()) + } + + pub fn connect_block( + &self, + rwtxn: &mut RwTxn, + header: &Header, + body: &Body, + ) -> Result<(), Error> { + let tip_hash = self.tip.get(rwtxn, &UnitKey)?.unwrap_or_default(); + if tip_hash != header.prev_side_hash { + let err = InvalidHeaderError::PrevSideHash { + expected: tip_hash, + received: header.prev_side_hash, + }; + return Err(Error::InvalidHeader(err)); + } + let merkle_root = body.compute_merkle_root(); + if merkle_root != header.merkle_root { + let err = Error::InvalidBody { + expected: merkle_root, + computed: header.merkle_root, + }; + return Err(err); + } + let mut accumulator = self + .utreexo_accumulator + .get(rwtxn, &UnitKey)? + .unwrap_or_default(); + // New leaves for the accumulator + let mut accumulator_add = Vec::::new(); + // Accumulator leaves to delete + let mut accumulator_del = Vec::::new(); + for (vout, output) in body.coinbase.iter().enumerate() { + let outpoint = OutPoint::Coinbase { + merkle_root, + vout: vout as u32, + }; + let pointed_output = PointedOutput { + outpoint, + output: output.clone(), + }; + accumulator_add.push((&pointed_output).into()); + self.utxos.put(rwtxn, &outpoint, output)?; + } + for transaction in &body.transactions { + let txid = transaction.txid(); + for (vin, (outpoint, utxo_hash)) in + transaction.inputs.iter().enumerate() + { + let spent_output = + self.utxos.get(rwtxn, outpoint)?.ok_or(Error::NoUtxo { + outpoint: *outpoint, + })?; + accumulator_del.push(utxo_hash.into()); + self.utxos.delete(rwtxn, outpoint)?; + let spent_output = SpentOutput { + output: spent_output, + inpoint: InPoint::Regular { + txid, + vin: vin as u32, + }, + }; + self.stxos.put(rwtxn, outpoint, &spent_output)?; + } + for (vout, output) in transaction.outputs.iter().enumerate() { + let outpoint = OutPoint::Regular { + txid, + vout: vout as u32, + }; + let pointed_output = PointedOutput { + outpoint, + output: output.clone(), + }; + accumulator_add.push((&pointed_output).into()); + self.utxos.put(rwtxn, &outpoint, output)?; + } + } + let block_hash = header.hash(); + let height = self.get_height(rwtxn)?; + self.tip.put(rwtxn, &UnitKey, &block_hash)?; + self.height.put(rwtxn, &UnitKey, &(height + 1))?; + accumulator + .0 + .modify(&accumulator_add, &accumulator_del) + .map_err(Error::Utreexo)?; + self.utreexo_accumulator + .put(rwtxn, &UnitKey, &accumulator)?; + Ok(()) + } + + pub fn disconnect_tip( + &self, + rwtxn: &mut RwTxn, + header: &Header, + body: &Body, + ) -> Result<(), Error> { + let tip_hash = self.tip.get(rwtxn, &UnitKey)?.unwrap_or_default(); + if tip_hash != header.hash() { + let err = InvalidHeaderError::BlockHash { + expected: tip_hash, + computed: header.hash(), + }; + return Err(Error::InvalidHeader(err)); + } + let merkle_root = body.compute_merkle_root(); + if merkle_root != header.merkle_root { + let err = Error::InvalidBody { + expected: merkle_root, + computed: header.merkle_root, + }; + return Err(err); + } + let mut accumulator = self + .utreexo_accumulator + .get(rwtxn, &UnitKey)? + .unwrap_or_default(); + tracing::debug!("Got acc"); + // New leaves for the accumulator + let mut accumulator_add = Vec::::new(); + // Accumulator leaves to delete + let mut accumulator_del = Vec::::new(); + // revert txs, last-to-first + body.transactions.iter().rev().try_for_each(|tx| { + let txid = tx.txid(); + // delete UTXOs, last-to-first + tx.outputs.iter().enumerate().rev().try_for_each( + |(vout, output)| { + let outpoint = OutPoint::Regular { + txid, + vout: vout as u32, + }; + let pointed_output = PointedOutput { + outpoint, + output: output.clone(), + }; + accumulator_del.push((&pointed_output).into()); + if self.utxos.delete(rwtxn, &outpoint)? { + Ok(()) + } else { + Err(Error::NoUtxo { outpoint }) + } + }, + )?; + // unspend STXOs, last-to-first + tx.inputs + .iter() + .rev() + .try_for_each(|(outpoint, utxo_hash)| { + if let Some(spent_output) = + self.stxos.get(rwtxn, outpoint)? + { + accumulator_add.push(utxo_hash.into()); + self.stxos.delete(rwtxn, outpoint)?; + self.utxos.put( + rwtxn, + outpoint, + &spent_output.output, + )?; + Ok(()) + } else { + Err(Error::NoStxo { + outpoint: *outpoint, + }) + } + }) + })?; + // delete coinbase UTXOs, last-to-first + body.coinbase.iter().enumerate().rev().try_for_each( + |(vout, output)| { + let outpoint = OutPoint::Coinbase { + merkle_root, + vout: vout as u32, + }; + let pointed_output = PointedOutput { + outpoint, + output: output.clone(), + }; + accumulator_del.push((&pointed_output).into()); + if self.utxos.delete(rwtxn, &outpoint)? { + Ok(()) + } else { + Err(Error::NoUtxo { outpoint }) + } + }, + )?; + let height = self.get_height(rwtxn)?; + self.tip.put(rwtxn, &UnitKey, &header.prev_side_hash)?; + self.height.put(rwtxn, &UnitKey, &(height - 1))?; + accumulator + .0 + .modify(&accumulator_add, &accumulator_del) + .map_err(Error::Utreexo)?; + self.utreexo_accumulator + .put(rwtxn, &UnitKey, &accumulator)?; + Ok(()) + } + + /// Get total sidechain wealth in Bitcoin + pub fn sidechain_wealth( + &self, + rotxn: &RoTxn, + ) -> Result { + let mut total_deposit_utxo_value: u64 = 0; + self.utxos.iter(rotxn)?.try_for_each(|utxo| { + let (outpoint, output) = utxo?; + if let OutPoint::Deposit(_) = outpoint { + total_deposit_utxo_value += output.get_value(); + } + Ok::<_, Error>(()) + })?; + let mut total_deposit_stxo_value: u64 = 0; + let mut total_withdrawal_stxo_value: u64 = 0; + self.stxos.iter(rotxn)?.try_for_each(|stxo| { + let (outpoint, spent_output) = stxo?; + if let OutPoint::Deposit(_) = outpoint { + total_deposit_stxo_value += spent_output.output.get_value(); + } + if let InPoint::Withdrawal { .. } = spent_output.inpoint { + total_withdrawal_stxo_value += spent_output.output.get_value(); + } + Ok::<_, Error>(()) + })?; + + let total_wealth_sats: u64 = (total_deposit_utxo_value + + total_deposit_stxo_value) + - total_withdrawal_stxo_value; + let total_wealth = BitcoinAmount::from_sat(total_wealth_sats); + Ok(total_wealth) + } +} diff --git a/lib/src/types/address.rs b/lib/types/address.rs similarity index 89% rename from lib/src/types/address.rs rename to lib/types/address.rs index 6978346..ffdc44a 100644 --- a/lib/src/types/address.rs +++ b/lib/types/address.rs @@ -1,4 +1,17 @@ -#[derive(Clone, Copy, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)] +use borsh::BorshSerialize; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, thiserror::Error)] +pub enum AddressParseError { + #[error("bs58 error")] + Bs58(#[from] bs58::decode::Error), + #[error("wrong address length {0} != 20")] + WrongLength(usize), +} + +#[derive( + BorshSerialize, Clone, Copy, Deserialize, Eq, PartialEq, Hash, Serialize, +)] pub struct Address(pub [u8; 20]); impl Address { @@ -40,11 +53,3 @@ impl std::str::FromStr for Address { )?)) } } - -#[derive(Debug, thiserror::Error)] -pub enum AddressParseError { - #[error("bs58 error")] - Bs58(#[from] bs58::decode::Error), - #[error("wrong address length {0} != 20")] - WrongLength(usize), -} diff --git a/lib/src/types/hashes.rs b/lib/types/hashes.rs similarity index 67% rename from lib/src/types/hashes.rs rename to lib/types/hashes.rs index ee763c4..f86e936 100644 --- a/lib/src/types/hashes.rs +++ b/lib/types/hashes.rs @@ -1,10 +1,30 @@ +use std::str::FromStr; + use bip300301::bitcoin; use bitcoin::hashes::Hash as _; +use borsh::BorshSerialize; +use hex::FromHex; +use serde::{Deserialize, Serialize}; + +use super::serde_hexstr_human_readable; const BLAKE3_LENGTH: usize = 32; + pub type Hash = [u8; BLAKE3_LENGTH]; -#[derive(Default, Clone, Copy, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)] +#[derive( + BorshSerialize, + Clone, + Copy, + Default, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize, +)] pub struct BlockHash(pub Hash); impl From for BlockHash { @@ -44,7 +64,19 @@ impl std::fmt::Debug for BlockHash { } } -#[derive(Default, Clone, Copy, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)] +#[derive( + BorshSerialize, + Clone, + Copy, + Default, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize, +)] pub struct MerkleRoot(Hash); impl From for MerkleRoot { @@ -71,8 +103,22 @@ impl std::fmt::Debug for MerkleRoot { } } -#[derive(Default, Clone, Copy, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)] -pub struct Txid(pub Hash); +#[derive( + BorshSerialize, + Clone, + Copy, + Default, + Deserialize, + Eq, + Hash, + Serialize, + Ord, + PartialEq, + PartialOrd, +)] +#[repr(transparent)] +#[serde(transparent)] +pub struct Txid(#[serde(with = "serde_hexstr_human_readable")] pub Hash); impl Txid { pub fn as_slice(&self) -> &[u8] { @@ -110,8 +156,18 @@ impl std::fmt::Debug for Txid { } } -pub fn hash(data: &T) -> Hash { - let data_serialized = - bincode::serialize(data).expect("failed to serialize a type to compute a hash"); +impl FromStr for Txid { + type Err = hex::FromHexError; + fn from_str(s: &str) -> Result { + Hash::from_hex(s).map(Self) + } +} + +pub fn hash(data: &T) -> Hash +where + T: BorshSerialize, +{ + let data_serialized = borsh::to_vec(data) + .expect("failed to serialize with borsh to compute a hash"); blake3::hash(&data_serialized).into() } diff --git a/lib/types/mod.rs b/lib/types/mod.rs new file mode 100644 index 0000000..05fdc25 --- /dev/null +++ b/lib/types/mod.rs @@ -0,0 +1,204 @@ +use bip300301::bitcoin; +use borsh::BorshSerialize; +use rustreexo::accumulator::{node_hash::NodeHash, pollard::Pollard}; +use serde::{Deserialize, Serialize}; +use std::{ + cmp::Ordering, + collections::{BTreeMap, HashMap}, +}; + +mod address; +pub mod hashes; +mod transaction; + +pub use address::Address; +pub use hashes::{hash, BlockHash, Hash, MerkleRoot, Txid}; +pub use transaction::{ + AuthorizedTransaction, Body, Content as OutputContent, FilledTransaction, + GetAddress, GetValue, InPoint, OutPoint, Output, PointedOutput, + SpentOutput, Transaction, Verify, +}; + +/// (de)serialize as hex strings for human-readable forms like json, +/// and default serialization for non human-readable formats like bincode +mod serde_hexstr_human_readable { + use hex::{FromHex, ToHex}; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + pub fn serialize(data: T, serializer: S) -> Result + where + S: Serializer, + T: Serialize + ToHex, + { + if serializer.is_human_readable() { + hex::serde::serialize(data, serializer) + } else { + data.serialize(serializer) + } + } + + pub fn deserialize<'de, D, T>(deserializer: D) -> Result + where + D: Deserializer<'de>, + T: Deserialize<'de> + FromHex, + ::Error: std::fmt::Display, + { + if deserializer.is_human_readable() { + hex::serde::deserialize(deserializer) + } else { + T::deserialize(deserializer) + } + } +} + +fn borsh_serialize_utreexo_nodehash( + node_hash: &NodeHash, + writer: &mut W, +) -> borsh::io::Result<()> +where + W: borsh::io::Write, +{ + let bytes: &[u8; 32] = node_hash; + borsh::BorshSerialize::serialize(bytes, writer) +} + +fn borsh_serialize_utreexo_roots( + roots: &[NodeHash], + writer: &mut W, +) -> borsh::io::Result<()> +where + W: borsh::io::Write, +{ + #[derive(BorshSerialize)] + #[repr(transparent)] + struct SerializeNodeHash<'a>( + #[borsh(serialize_with = "borsh_serialize_utreexo_nodehash")] + &'a NodeHash, + ); + let roots: Vec = + roots.iter().map(SerializeNodeHash).collect(); + borsh::BorshSerialize::serialize(&roots, writer) +} + +fn borsh_serialize_bitcoin_block_hash( + block_hash: &bitcoin::BlockHash, + writer: &mut W, +) -> borsh::io::Result<()> +where + W: borsh::io::Write, +{ + let bytes: &[u8; 32] = block_hash.as_ref(); + borsh::BorshSerialize::serialize(bytes, writer) +} + +#[derive( + BorshSerialize, Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, +)] +pub struct Header { + pub merkle_root: MerkleRoot, + pub prev_side_hash: BlockHash, + #[borsh(serialize_with = "borsh_serialize_bitcoin_block_hash")] + pub prev_main_hash: bitcoin::BlockHash, + /// Utreexo roots + #[borsh(serialize_with = "borsh_serialize_utreexo_roots")] + pub roots: Vec, +} + +impl Header { + pub fn hash(&self) -> BlockHash { + hash(self).into() + } +} + +#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)] +pub enum WithdrawalBundleStatus { + Failed, + Confirmed, +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct WithdrawalBundle { + pub spend_utxos: BTreeMap, + pub transaction: bitcoin::Transaction, +} + +#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct TwoWayPegData { + pub deposits: HashMap, + pub deposit_block_hash: Option, + pub bundle_statuses: HashMap, +} + +/* +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct DisconnectData { + pub spent_utxos: HashMap, + pub deposits: Vec, + pub pending_bundles: Vec, + pub spent_bundles: HashMap>, + pub spent_withdrawals: HashMap, + pub failed_withdrawals: Vec, +} +*/ + +#[derive(Eq, PartialEq, Clone, Debug)] +pub struct AggregatedWithdrawal { + pub spend_utxos: HashMap, + pub main_address: bitcoin::Address, + pub value: u64, + pub main_fee: u64, +} + +impl Ord for AggregatedWithdrawal { + fn cmp(&self, other: &Self) -> Ordering { + if self == other { + Ordering::Equal + } else if self.main_fee > other.main_fee + || self.value > other.value + || self.main_address > other.main_address + { + Ordering::Greater + } else { + Ordering::Less + } + } +} + +impl PartialOrd for AggregatedWithdrawal { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +#[derive(Debug, Default)] +#[repr(transparent)] +pub struct Accumulator(pub Pollard); + +impl<'de> Deserialize<'de> for Accumulator { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let bytes: Vec = + as Deserialize>::deserialize(deserializer)?; + let pollard = Pollard::deserialize(&*bytes) + .inspect_err(|err| { + tracing::debug!("deserialize err: {err}\n bytes: {bytes:?}") + }) + .map_err(::custom)?; + Ok(Self(pollard)) + } +} + +impl Serialize for Accumulator { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut bytes = Vec::new(); + self.0 + .serialize(&mut bytes) + .map_err(::custom)?; + as Serialize>::serialize(&bytes, serializer) + } +} diff --git a/lib/types/transaction.rs b/lib/types/transaction.rs new file mode 100644 index 0000000..910347a --- /dev/null +++ b/lib/types/transaction.rs @@ -0,0 +1,364 @@ +use std::collections::HashMap; + +use bip300301::bitcoin; +use borsh::BorshSerialize; +use rustreexo::accumulator::{ + node_hash::NodeHash, pollard::Pollard, proof::Proof, +}; +use serde::{Deserialize, Serialize}; + +use super::{hash, Address, Hash, MerkleRoot, Txid}; +use crate::authorization::Authorization; + +fn borsh_serialize_bitcoin_outpoint( + block_hash: &bitcoin::OutPoint, + writer: &mut W, +) -> borsh::io::Result<()> +where + W: borsh::io::Write, +{ + let bitcoin::OutPoint { txid, vout } = block_hash; + let txid_bytes: &[u8; 32] = txid.as_ref(); + borsh::BorshSerialize::serialize(&(txid_bytes, vout), writer) +} + +#[derive( + BorshSerialize, + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize, +)] +pub enum OutPoint { + // Created by transactions. + Regular { + txid: Txid, + vout: u32, + }, + // Created by block bodies. + Coinbase { + merkle_root: MerkleRoot, + vout: u32, + }, + // Created by mainchain deposits. + Deposit( + #[borsh(serialize_with = "borsh_serialize_bitcoin_outpoint")] + bitcoin::OutPoint, + ), +} + +impl std::fmt::Display for OutPoint { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Regular { txid, vout } => write!(f, "regular {txid} {vout}"), + Self::Coinbase { merkle_root, vout } => { + write!(f, "coinbase {merkle_root} {vout}") + } + Self::Deposit(bitcoin::OutPoint { txid, vout }) => { + write!(f, "deposit {txid} {vout}") + } + } + } +} + +/// Reference to a tx input. +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +pub enum InPoint { + /// Transaction input + Regular { + txid: Txid, + // index of the spend in the inputs to spend_tx + vin: u32, + }, + // Created by mainchain withdrawals + Withdrawal { + txid: bitcoin::Txid, + }, +} + +fn borsh_serialize_bitcoin_address( + bitcoin_address: &bitcoin::Address, + writer: &mut W, +) -> borsh::io::Result<()> +where + V: bitcoin::address::NetworkValidation, + W: borsh::io::Write, +{ + let spk = bitcoin_address + .as_unchecked() + .assume_checked_ref() + .script_pubkey(); + borsh::BorshSerialize::serialize(spk.as_bytes(), writer) +} + +#[derive( + BorshSerialize, Clone, Debug, Deserialize, Eq, PartialEq, Serialize, +)] +pub enum Content { + Value(u64), + Withdrawal { + value: u64, + main_fee: u64, + #[borsh(serialize_with = "borsh_serialize_bitcoin_address")] + main_address: bitcoin::Address, + }, +} + +impl Content { + pub fn is_value(&self) -> bool { + matches!(self, Self::Value(_)) + } + pub fn is_withdrawal(&self) -> bool { + matches!(self, Self::Withdrawal { .. }) + } +} + +impl GetValue for Content { + #[inline(always)] + fn get_value(&self) -> u64 { + match self { + Self::Value(value) => *value, + Self::Withdrawal { value, .. } => *value, + } + } +} + +#[derive( + BorshSerialize, Clone, Debug, Deserialize, Eq, PartialEq, Serialize, +)] +pub struct Output { + pub address: Address, + pub content: Content, +} + +impl GetValue for Output { + #[inline(always)] + fn get_value(&self) -> u64 { + self.content.get_value() + } +} + +#[derive( + BorshSerialize, Clone, Debug, Deserialize, Eq, PartialEq, Serialize, +)] +pub struct PointedOutput { + pub outpoint: OutPoint, + pub output: Output, +} + +impl From<&PointedOutput> for NodeHash { + fn from(pointed_output: &PointedOutput) -> Self { + Self::new(hash(pointed_output)) + } +} + +#[derive(BorshSerialize, Clone, Debug, Default, Deserialize, Serialize)] +pub struct Transaction { + pub inputs: Vec<(OutPoint, Hash)>, + /// Utreexo proof for inputs + #[borsh(skip)] + pub proof: Proof, + pub outputs: Vec, +} + +impl Transaction { + pub fn txid(&self) -> Txid { + hash(self).into() + } +} + +/// Representation of a spent output +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct SpentOutput { + pub output: Output, + pub inpoint: InPoint, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct FilledTransaction { + pub transaction: Transaction, + pub spent_utxos: Vec, +} + +impl FilledTransaction { + pub fn get_value_in(&self) -> u64 { + self.spent_utxos.iter().map(GetValue::get_value).sum() + } + + pub fn get_value_out(&self) -> u64 { + self.transaction + .outputs + .iter() + .map(GetValue::get_value) + .sum() + } + + pub fn get_fee(&self) -> Option { + let value_in = self.get_value_in(); + let value_out = self.get_value_out(); + if value_in < value_out { + None + } else { + Some(value_in - value_out) + } + } +} + +#[derive(BorshSerialize, Clone, Debug, Deserialize, Serialize)] +pub struct AuthorizedTransaction { + pub transaction: Transaction, + /// Authorization is called witness in Bitcoin. + pub authorizations: Vec, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Body { + pub coinbase: Vec, + pub transactions: Vec, + pub authorizations: Vec, +} + +impl Body { + pub fn new( + authorized_transactions: Vec, + coinbase: Vec, + ) -> Self { + let mut authorizations = Vec::with_capacity( + authorized_transactions + .iter() + .map(|t| t.transaction.inputs.len()) + .sum(), + ); + let mut transactions = + Vec::with_capacity(authorized_transactions.len()); + for at in authorized_transactions.into_iter() { + authorizations.extend(at.authorizations); + transactions.push(at.transaction); + } + Self { + coinbase, + transactions, + authorizations, + } + } + + pub fn authorized_transactions(&self) -> Vec { + let mut authorizations_iter = self.authorizations.iter(); + self.transactions + .iter() + .map(|tx| { + let mut authorizations = Vec::with_capacity(tx.inputs.len()); + for _ in 0..tx.inputs.len() { + let auth = authorizations_iter.next().unwrap(); + authorizations.push(auth.clone()); + } + AuthorizedTransaction { + transaction: tx.clone(), + authorizations, + } + }) + .collect() + } + + pub fn compute_merkle_root(&self) -> MerkleRoot { + // FIXME: Compute actual merkle root instead of just a hash. + hash(&(&self.coinbase, &self.transactions)).into() + } + + // Modifies the pollard, without checking tx proofs + pub fn modify_pollard(&self, pollard: &mut Pollard) -> Result<(), String> { + // New leaves for the accumulator + let mut accumulator_add = Vec::::new(); + // Accumulator leaves to delete + let mut accumulator_del = Vec::::new(); + let merkle_root = self.compute_merkle_root(); + for (vout, output) in self.coinbase.iter().enumerate() { + let outpoint = OutPoint::Coinbase { + merkle_root, + vout: vout as u32, + }; + let pointed_output = PointedOutput { + outpoint, + output: output.clone(), + }; + accumulator_add.push((&pointed_output).into()); + } + for transaction in &self.transactions { + let txid = transaction.txid(); + for (_, utxo_hash) in transaction.inputs.iter() { + accumulator_del.push(utxo_hash.into()); + } + for (vout, output) in transaction.outputs.iter().enumerate() { + let outpoint = OutPoint::Regular { + txid, + vout: vout as u32, + }; + let pointed_output = PointedOutput { + outpoint, + output: output.clone(), + }; + accumulator_add.push((&pointed_output).into()); + } + } + pollard.modify(&accumulator_add, &accumulator_del) + } + + pub fn get_inputs(&self) -> Vec { + self.transactions + .iter() + .flat_map(|tx| tx.inputs.iter().map(|(outpoint, _)| outpoint)) + .copied() + .collect() + } + + pub fn get_outputs(&self) -> HashMap { + let mut outputs = HashMap::new(); + let merkle_root = self.compute_merkle_root(); + for (vout, output) in self.coinbase.iter().enumerate() { + let vout = vout as u32; + let outpoint = OutPoint::Coinbase { merkle_root, vout }; + outputs.insert(outpoint, output.clone()); + } + for transaction in &self.transactions { + let txid = transaction.txid(); + for (vout, output) in transaction.outputs.iter().enumerate() { + let vout = vout as u32; + let outpoint = OutPoint::Regular { txid, vout }; + outputs.insert(outpoint, output.clone()); + } + } + outputs + } + + pub fn get_coinbase_value(&self) -> u64 { + self.coinbase.iter().map(|output| output.get_value()).sum() + } +} + +pub trait GetAddress { + fn get_address(&self) -> Address; +} + +pub trait GetValue { + fn get_value(&self) -> u64; +} + +impl GetValue for () { + fn get_value(&self) -> u64 { + 0 + } +} + +pub trait Verify { + type Error; + fn verify_transaction( + transaction: &AuthorizedTransaction, + ) -> Result<(), Self::Error>; + fn verify_body(body: &Body) -> Result<(), Self::Error>; +} diff --git a/lib/src/wallet.rs b/lib/wallet.rs similarity index 51% rename from lib/src/wallet.rs rename to lib/wallet.rs index 2aca388..a38b165 100644 --- a/lib/src/wallet.rs +++ b/lib/wallet.rs @@ -1,53 +1,107 @@ -pub use crate::authorization::{get_address, Authorization}; -use crate::types::{ - Address, AuthorizedTransaction, Content, GetValue, OutPoint, Output, Transaction, +use std::{ + collections::{HashMap, HashSet}, + path::Path, }; + use bip300301::bitcoin; use byteorder::{BigEndian, ByteOrder}; -use ed25519_dalek_bip32::*; -use heed::types::*; -use heed::{Database, RoTxn}; -use std::collections::{HashMap, HashSet}; -use std::path::Path; +use ed25519_dalek_bip32::{ChildIndex, DerivationPath, ExtendedSigningKey}; +use heed::{ + types::{Bytes, SerdeBincode, U8}, + Database, RoTxn, +}; +use rustreexo::accumulator::node_hash::NodeHash; + +use crate::types::{hash, Accumulator, PointedOutput}; +pub use crate::{ + authorization::{get_address, Authorization}, + types::{ + Address, AuthorizedTransaction, GetValue, InPoint, OutPoint, Output, + OutputContent, SpentOutput, Transaction, + }, +}; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("address {address} does not exist")] + AddressDoesNotExist { address: crate::types::Address }, + #[error("authorization error")] + Authorization(#[from] crate::authorization::Error), + #[error("bip32 error")] + Bip32(#[from] ed25519_dalek_bip32::Error), + #[error("heed error")] + Heed(#[from] heed::Error), + #[error("io error")] + Io(#[from] std::io::Error), + #[error("no index for address {address}")] + NoIndex { address: Address }, + #[error("wallet doesn't have a seed")] + NoSeed, + #[error("not enough funds")] + NotEnoughFunds, + #[error("utxo doesn't exist")] + NoUtxo, + #[error("failed to parse mnemonic seed phrase")] + ParseMnemonic(#[source] anyhow::Error), + #[error("seed has already been set")] + SeedAlreadyExists, + #[error("utreexo error: {0}")] + Utreexo(String), +} #[derive(Clone)] pub struct Wallet { env: heed::Env, - // FIXME: Don't store the seed in plaintext. - seed: Database, OwnedType<[u8; 64]>>, - pub address_to_index: Database, OwnedType<[u8; 4]>>, - pub index_to_address: Database, SerdeBincode
>, + // Seed is always [u8; 64], but due to serde not implementing serialize + // for [T; 64], use heed's `Bytes` + // TODO: Don't store the seed in plaintext. + seed: Database, + pub address_to_index: + Database, SerdeBincode<[u8; 4]>>, + pub index_to_address: + Database, SerdeBincode
>, pub utxos: Database, SerdeBincode>, + pub stxos: Database, SerdeBincode>, } impl Wallet { - pub const NUM_DBS: u32 = 4; + pub const NUM_DBS: u32 = 5; pub fn new(path: &Path) -> Result { std::fs::create_dir_all(path)?; - let env = heed::EnvOpenOptions::new() - .map_size(10 * 1024 * 1024) // 10MB - .max_dbs(Self::NUM_DBS) - .open(path)?; - let seed_db = env.create_database(Some("seed"))?; - let address_to_index = env.create_database(Some("address_to_index"))?; - let index_to_address = env.create_database(Some("index_to_address"))?; - let utxos = env.create_database(Some("utxos"))?; + let env = unsafe { + heed::EnvOpenOptions::new() + .map_size(10 * 1024 * 1024) // 10MB + .max_dbs(Self::NUM_DBS) + .open(path)? + }; + let mut rwtxn = env.write_txn()?; + let seed_db = env.create_database(&mut rwtxn, Some("seed"))?; + let address_to_index = + env.create_database(&mut rwtxn, Some("address_to_index"))?; + let index_to_address = + env.create_database(&mut rwtxn, Some("index_to_address"))?; + let utxos = env.create_database(&mut rwtxn, Some("utxos"))?; + let stxos = env.create_database(&mut rwtxn, Some("stxos"))?; + rwtxn.commit()?; Ok(Self { env, seed: seed_db, address_to_index, index_to_address, utxos, + stxos, }) } - pub fn set_seed(&self, seed: &[u8; 64]) -> Result<(), Error> { + /// Overwrite the seed, or set it if it does not already exist. + pub fn overwrite_seed(&self, seed: &[u8; 64]) -> Result<(), Error> { let mut txn = self.env.write_txn()?; - self.seed.put(&mut txn, &0, &seed)?; + self.seed.put(&mut txn, &0, seed)?; self.address_to_index.clear(&mut txn)?; self.index_to_address.clear(&mut txn)?; self.utxos.clear(&mut txn)?; + self.stxos.clear(&mut txn)?; txn.commit()?; Ok(()) } @@ -57,8 +111,29 @@ impl Wallet { Ok(self.seed.get(&txn, &0)?.is_some()) } + /// Set the seed, if it does not already exist + pub fn set_seed(&self, seed: &[u8; 64]) -> Result<(), Error> { + if self.has_seed()? { + Err(Error::SeedAlreadyExists) + } else { + self.overwrite_seed(seed) + } + } + + /// Set the seed from a mnemonic seed phrase, + /// if the seed does not already exist + pub fn set_seed_from_mnemonic(&self, mnemonic: &str) -> Result<(), Error> { + let mnemonic = + bip39::Mnemonic::from_phrase(mnemonic, bip39::Language::English) + .map_err(Error::ParseMnemonic)?; + let seed = bip39::Seed::new(&mnemonic, ""); + let seed_bytes: [u8; 64] = seed.as_bytes().try_into().unwrap(); + self.set_seed(&seed_bytes) + } + pub fn create_withdrawal( &self, + accumulator: &Accumulator, main_address: bitcoin::Address, value: u64, main_fee: u64, @@ -66,11 +141,24 @@ impl Wallet { ) -> Result { let (total, coins) = self.select_coins(value + fee + main_fee)?; let change = total - value - fee; - let inputs = coins.into_keys().collect(); + + let inputs: Vec<_> = coins + .into_iter() + .map(|(outpoint, output)| { + let utxo_hash = hash(&PointedOutput { outpoint, output }); + (outpoint, utxo_hash) + }) + .collect(); + let input_utxo_hashes: Vec = + inputs.iter().map(|(_, hash)| hash.into()).collect(); + let proof = accumulator + .0 + .prove(&input_utxo_hashes) + .map_err(Error::Utreexo)?; let outputs = vec![ Output { address: self.get_new_address()?, - content: Content::Withdrawal { + content: OutputContent::Withdrawal { value, main_fee, main_address, @@ -78,35 +166,59 @@ impl Wallet { }, Output { address: self.get_new_address()?, - content: Content::Value(change), + content: OutputContent::Value(change), }, ]; - Ok(Transaction { inputs, outputs }) + Ok(Transaction { + inputs, + proof, + outputs, + }) } pub fn create_transaction( &self, + accumulator: &Accumulator, address: Address, value: u64, fee: u64, ) -> Result { let (total, coins) = self.select_coins(value + fee)?; let change = total - value - fee; - let inputs = coins.into_keys().collect(); + let inputs: Vec<_> = coins + .into_iter() + .map(|(outpoint, output)| { + let utxo_hash = hash(&PointedOutput { outpoint, output }); + (outpoint, utxo_hash) + }) + .collect(); + let input_utxo_hashes: Vec = + inputs.iter().map(|(_, hash)| hash.into()).collect(); + let proof = accumulator + .0 + .prove(&input_utxo_hashes) + .map_err(Error::Utreexo)?; let outputs = vec![ Output { address, - content: Content::Value(value), + content: OutputContent::Value(value), }, Output { address: self.get_new_address()?, - content: Content::Value(change), + content: OutputContent::Value(change), }, ]; - Ok(Transaction { inputs, outputs }) + Ok(Transaction { + inputs, + proof, + outputs, + }) } - pub fn select_coins(&self, value: u64) -> Result<(u64, HashMap), Error> { + pub fn select_coins( + &self, + value: u64, + ) -> Result<(u64, HashMap), Error> { let txn = self.env.read_txn()?; let mut utxos = vec![]; for item in self.utxos.iter(&txn)? { @@ -129,7 +241,7 @@ impl Wallet { if total < value { return Err(Error::NotEnoughFunds); } - return Ok((total, selected)); + Ok((total, selected)) } pub fn delete_utxos(&self, outpoints: &[OutPoint]) -> Result<(), Error> { @@ -141,7 +253,30 @@ impl Wallet { Ok(()) } - pub fn put_utxos(&self, utxos: &HashMap) -> Result<(), Error> { + pub fn spend_utxos( + &self, + spent: &[(OutPoint, InPoint)], + ) -> Result<(), Error> { + let mut txn = self.env.write_txn()?; + for (outpoint, inpoint) in spent { + let output = self.utxos.get(&txn, outpoint)?; + if let Some(output) = output { + self.utxos.delete(&mut txn, outpoint)?; + let spent_output = SpentOutput { + output, + inpoint: *inpoint, + }; + self.stxos.put(&mut txn, outpoint, &spent_output)?; + } + } + txn.commit()?; + Ok(()) + } + + pub fn put_utxos( + &self, + utxos: &HashMap, + ) -> Result<(), Error> { let mut txn = self.env.write_txn()?; for (outpoint, output) in utxos { self.utxos.put(&mut txn, outpoint, output)?; @@ -180,22 +315,27 @@ impl Wallet { Ok(addresses) } - pub fn authorize(&self, transaction: Transaction) -> Result { + pub fn authorize( + &self, + transaction: Transaction, + ) -> Result { let txn = self.env.read_txn()?; let mut authorizations = vec![]; - for input in &transaction.inputs { - let spent_utxo = self.utxos.get(&txn, input)?.ok_or(Error::NoUtxo)?; + for (outpoint, _) in &transaction.inputs { + let spent_utxo = + self.utxos.get(&txn, outpoint)?.ok_or(Error::NoUtxo)?; let index = self .address_to_index .get(&txn, &spent_utxo.address)? .ok_or(Error::NoIndex { - address: spent_utxo.address, - })?; + address: spent_utxo.address, + })?; let index = BigEndian::read_u32(&index); - let keypair = self.get_keypair(&txn, index)?; - let signature = crate::authorization::sign(&keypair, &transaction)?; + let signing_key = self.get_signing_key(&txn, index)?; + let signature = + crate::authorization::sign(&signing_key, &transaction)?; authorizations.push(Authorization { - public_key: keypair.public, + verifying_key: signing_key.verifying_key(), signature, }); } @@ -213,8 +353,8 @@ impl Wallet { .unwrap_or(([0; 4], [0; 20].into())); let last_index = BigEndian::read_u32(&last_index); let index = last_index + 1; - let keypair = self.get_keypair(&txn, index)?; - let address = get_address(&keypair.public); + let signing_key = self.get_signing_key(&txn, index)?; + let address = get_address(&signing_key.verifying_key()); let index = index.to_be_bytes(); self.index_to_address.put(&mut txn, &index, &address)?; self.address_to_index.put(&mut txn, &address, &index)?; @@ -232,40 +372,20 @@ impl Wallet { Ok(last_index) } - fn get_keypair(&self, txn: &RoTxn, index: u32) -> Result { + fn get_signing_key( + &self, + txn: &RoTxn, + index: u32, + ) -> Result { let seed = self.seed.get(txn, &0)?.ok_or(Error::NoSeed)?; - let xpriv = ExtendedSecretKey::from_seed(&seed)?; + let xpriv = ExtendedSigningKey::from_seed(seed)?; let derivation_path = DerivationPath::new([ ChildIndex::Hardened(1), ChildIndex::Hardened(0), ChildIndex::Hardened(0), ChildIndex::Hardened(index), ]); - let child = xpriv.derive(&derivation_path)?; - let public = child.public_key(); - let secret = child.secret_key; - Ok(ed25519_dalek::Keypair { secret, public }) + let xsigning_key = xpriv.derive(&derivation_path)?; + Ok(xsigning_key.signing_key) } } - -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error("heed error")] - Heed(#[from] heed::Error), - #[error("bip32 error")] - Bip32(#[from] ed25519_dalek_bip32::Error), - #[error("address {address} does not exist")] - AddressDoesNotExist { address: crate::types::Address }, - #[error("utxo doesn't exist")] - NoUtxo, - #[error("wallet doesn't have a seed")] - NoSeed, - #[error("no index for address {address}")] - NoIndex { address: Address }, - #[error("authorization error")] - Authorization(#[from] crate::authorization::Error), - #[error("io error")] - Io(#[from] std::io::Error), - #[error("not enough funds")] - NotEnoughFunds, -} diff --git a/mainchain b/mainchain new file mode 160000 index 0000000..64ad6a0 --- /dev/null +++ b/mainchain @@ -0,0 +1 @@ +Subproject commit 64ad6a0c75448dcedb1ee47b70d56f09f1ce0de1 diff --git a/rpc-api/Cargo.toml b/rpc-api/Cargo.toml new file mode 100644 index 0000000..2d43d95 --- /dev/null +++ b/rpc-api/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "thunder_app_rpc_api" +authors.workspace = true +edition.workspace = true +version.workspace = true + +[dependencies] +bip300301.workspace = true +jsonrpsee = { version = "0.20.0", features = ["macros"] } +thunder = { path = "../lib" } + +[lib] +name = "thunder_app_rpc_api" +path = "lib.rs" \ No newline at end of file diff --git a/rpc-api/lib.rs b/rpc-api/lib.rs new file mode 100644 index 0000000..ccfa156 --- /dev/null +++ b/rpc-api/lib.rs @@ -0,0 +1,76 @@ +//! RPC API + +use std::net::SocketAddr; + +use bip300301::bitcoin; +use jsonrpsee::{core::RpcResult, proc_macros::rpc}; +use thunder::types::{Address, Txid}; + +#[rpc(client, server)] +pub trait Rpc { + /// Get balance in sats + #[method(name = "balance")] + async fn balance(&self) -> RpcResult; + + /// Connect to a peer + #[method(name = "connect_peer")] + async fn connect_peer(&self, addr: SocketAddr) -> RpcResult<()>; + + /// Format a deposit address + #[method(name = "format_deposit_address")] + async fn format_deposit_address( + &self, + address: Address, + ) -> RpcResult; + + /// Generate a mnemonic seed phrase + #[method(name = "generate_mnemonic")] + async fn generate_mnemonic(&self) -> RpcResult; + + /// Get a new address + #[method(name = "get_new_address")] + async fn get_new_address(&self) -> RpcResult
; + + /// Get the current block count + #[method(name = "getblockcount")] + async fn getblockcount(&self) -> RpcResult; + + /// Attempt to mine a sidechain block + #[method(name = "mine")] + async fn mine(&self, fee: Option) -> RpcResult<()>; + + /// Remove a tx from the mempool + #[method(name = "remove_from_mempool")] + async fn remove_from_mempool(&self, txid: Txid) -> RpcResult<()>; + + /// Set the wallet seed from a mnemonic seed phrase + #[method(name = "set_seed_from_mnemonic")] + async fn set_seed_from_mnemonic(&self, mnemonic: String) -> RpcResult<()>; + + /// Get total sidechain wealth + #[method(name = "sidechain_wealth")] + async fn sidechain_wealth(&self) -> RpcResult; + + /// Stop the node + #[method(name = "stop")] + async fn stop(&self); + + /// Transfer funds to the specified address + #[method(name = "transfer")] + async fn transfer( + &self, + dest: Address, + value_sats: u64, + fee_sats: u64, + ) -> RpcResult; + + /// Initiate a withdrawal to the specified mainchain address + #[method(name = "withdraw")] + async fn withdraw( + &self, + mainchain_address: bitcoin::Address, + amount_sats: u64, + fee_sats: u64, + mainchain_fee_sats: u64, + ) -> RpcResult; +} diff --git a/rustfmt.toml b/rustfmt.toml index 3a26366..60a370b 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1 +1,2 @@ edition = "2021" +max_width = 80 diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..18ffd2a --- /dev/null +++ b/shell.nix @@ -0,0 +1,35 @@ +{ pkgs ? import {} }: + +pkgs.mkShell { + buildInputs = with pkgs; [ + autoconf + automake + boost + db4 + gcc + libevent + libtool + openssl + pkg-config + ]; + # Needed for X11 + shellHook = + let + common-libs = with pkgs; lib.makeLibraryPath [ + libGL + ]; + wayland-libs = with pkgs; lib.makeLibraryPath [ + libxkbcommon + wayland + ]; + x11-libs = with pkgs; lib.makeLibraryPath [ + xorg.libX11 + xorg.libXcursor + xorg.libXi + xorg.libXrandr + ]; + in '' + export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:${common-libs}:${wayland-libs}:${x11-libs}" + export BOOST_LIB_DIR="${pkgs.boost.out}/lib" + ''; +}